diff --git a/.clang-format b/.clang-format index d5740f6894..6b68254ec0 100644 --- a/.clang-format +++ b/.clang-format @@ -18,8 +18,8 @@ AllowShortLoopsOnASingleLine: true AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false -AlwaysBreakTemplateDeclarations: Yes -BinPackArguments: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true BinPackParameters: false BraceWrapping: AfterClass: true @@ -37,18 +37,18 @@ BraceWrapping: SplitEmptyFunction: false SplitEmptyRecord: false SplitEmptyNamespace: false -BreakBeforeBinaryOperators: All +BreakBeforeBinaryOperators: None BreakBeforeBraces: Custom BreakBeforeInheritanceComma: false BreakInheritanceList: BeforeColon -BreakBeforeTernaryOperators: true +BreakBeforeTernaryOperators: false BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeComma BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true -ColumnLimit: 75 +ColumnLimit: 78 CommentPragmas: '^ IWYU pragma:' -CompactNamespaces: false +CompactNamespaces: true ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 3744315ae3..dbf3c76dc3 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -24,3 +24,4 @@ _Is this a new feature request?_ #### Project File (.3MF) where problem occurs _Upload a PrusaSlicer Project File (.3MF) (`Plater -> Export plate as 3MF` for Slic3r PE 1.41.2 and older, `File -> Save` / `Save Project` for PrusaSlicer, Slic3r PE 1.42.0-alpha and newer)_ +_Images (PNG, GIF, JPEG), PDFs or text files could be drag & dropped to the issue directly, while all other files need to be zipped first (.zip, .gz)_ diff --git a/Build.PL b/Build.PL index ef17ec046b..1c3b0e3a7f 100644 --- a/Build.PL +++ b/Build.PL @@ -16,6 +16,8 @@ my %prereqs = qw( ExtUtils::MakeMaker 6.80 ExtUtils::ParseXS 3.22 ExtUtils::XSpp 0 + ExtUtils::XSpp::Cmd 0 + ExtUtils::CppGuess 0 ExtUtils::Typemaps 0 ExtUtils::Typemaps::Basic 0 File::Basename 0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 719bdd04e7..9b8a1c8480 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,13 +13,13 @@ if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) endif() if(DEFINED ENV{SLIC3R_STATIC}) - set(SLIC3R_STATIC_INITIAL $ENV{SLIC3R_STATIC}) + set(SLIC3R_STATIC_INITIAL $ENV{SLIC3R_STATIC}) else() - if (MSVC OR MINGW OR APPLE) - set(SLIC3R_STATIC_INITIAL 1) - else() - set(SLIC3R_STATIC_INITIAL 0) - endif() + if (MSVC OR MINGW OR APPLE) + set(SLIC3R_STATIC_INITIAL 1) + else() + set(SLIC3R_STATIC_INITIAL 0) + endif() endif() option(SLIC3R_STATIC "Compile PrusaSlicer with static libraries (Boost, TBB, glew)" ${SLIC3R_STATIC_INITIAL}) @@ -32,7 +32,6 @@ option(SLIC3R_MSVC_COMPILE_PARALLEL "Compile on Visual Studio in parallel" 1) option(SLIC3R_MSVC_PDB "Generate PDB files on MSVC in Release mode" 1) option(SLIC3R_PERL_XS "Compile XS Perl module and enable Perl unit and integration tests" 0) option(SLIC3R_ASAN "Enable ASan on Clang and GCC" 0) -option(SLIC3R_SYNTAXONLY "Only perform source code correctness checking, no binary output (UNIX only)" 0) set(SLIC3R_GTK "2" CACHE STRING "GTK version to use with wxWidgets on Linux") @@ -53,14 +52,30 @@ if (SLIC3R_GUI) add_definitions(-DSLIC3R_GUI) endif () +if (MSVC AND CMAKE_CXX_COMPILER_ID STREQUAL Clang) + set(IS_CLANG_CL TRUE) + + # clang-cl can interpret SYSTEM header paths if -imsvc is used + set(CMAKE_INCLUDE_SYSTEM_FLAG_CXX "-imsvc") + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall \ + -Wno-old-style-cast -Wno-reserved-id-macro -Wno-c++98-compat-pedantic") +else () + set(IS_CLANG_CL FALSE) +endif () + if (MSVC) - if (SLIC3R_MSVC_COMPILE_PARALLEL) - add_compile_options(/MP) + if (SLIC3R_MSVC_COMPILE_PARALLEL AND NOT IS_CLANG_CL) + add_compile_options(/MP) endif () # /bigobj (Increase Number of Sections in .Obj file) # error C3859: virtual memory range for PCH exceeded; please recompile with a command line option of '-Zm90' or greater # Generate symbols at every build target, even for the release. - add_compile_options(-bigobj -Zm316 /Zi) + add_compile_options(-bigobj -Zm520 /Zi) +endif () + +if (MINGW) + add_compile_options(-Wa,-mbig-obj) endif () # Display and check CMAKE_PREFIX_PATH @@ -89,8 +104,6 @@ enable_testing () # Enable C++11 language standard. set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) if(NOT WIN32) # Add DEBUG flags to debug builds. @@ -104,17 +117,17 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) # WIN10SDK_PATH is used to point CMake to the WIN10 SDK installation directory. # We pick it from environment if it is not defined in another way if(WIN32) - if(NOT DEFINED WIN10SDK_PATH) - if(DEFINED ENV{WIN10SDK_PATH}) - set(WIN10SDK_PATH "$ENV{WIN10SDK_PATH}") - endif() - endif() - if(DEFINED WIN10SDK_PATH AND NOT EXISTS "${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h") - message("WIN10SDK_PATH is invalid: ${WIN10SDK_PATH}") - message("${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h was not found") - message("STL fixing by the Netfabb service will not be compiled") - unset(WIN10SDK_PATH) - endif() + if(NOT DEFINED WIN10SDK_PATH) + if(DEFINED ENV{WIN10SDK_PATH}) + set(WIN10SDK_PATH "$ENV{WIN10SDK_PATH}") + endif() + endif() + if(DEFINED WIN10SDK_PATH AND NOT EXISTS "${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h") + message("WIN10SDK_PATH is invalid: ${WIN10SDK_PATH}") + message("${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h was not found") + message("STL fixing by the Netfabb service will not be compiled") + unset(WIN10SDK_PATH) + endif() if(WIN10SDK_PATH) message("Building with Win10 Netfabb STL fixing service support") add_definitions(-DHAS_WIN10SDK) @@ -149,30 +162,29 @@ endif() if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUXX) # Adding -fext-numeric-literals to enable GCC extensions on definitions of quad float literals, which are required by Boost. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fext-numeric-literals" ) - - if (SLIC3R_SYNTAXONLY) - set(CMAKE_CXX_ARCHIVE_CREATE "true") - set(CMAKE_C_ARCHIVE_CREATE "true") - set(CMAKE_CXX_ARCHIVE_APPEND "true") - set(CMAKE_C_ARCHIVE_APPEND "true") - set(CMAKE_RANLIB "true") - set(CMAKE_C_LINK_EXECUTABLE "true") - set(CMAKE_CXX_LINK_EXECUTABLE "true") - - set(CMAKE_C_COMPILE_OBJECT " -fsyntax-only -c && touch ") - set(CMAKE_CXX_COMPILE_OBJECT " -fsyntax-only -c && touch ") - endif () endif() -if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - add_compile_options(-Wall) +if (NOT MSVC AND ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")) + if (NOT MINGW) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall" ) + endif () set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-reorder" ) # On GCC and Clang, no return from a non-void function is a warning only. Here, we make it an error. add_compile_options(-Werror=return-type) - #removes LOTS of extraneous Eigen warnings - add_compile_options(-Wno-ignored-attributes) + #removes LOTS of extraneous Eigen warnings (GCC only supports it since 6.1) + #if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 6.1) + # add_compile_options(-Wno-ignored-attributes) # Tamas: Eigen include dirs are marked as SYSTEM + #endif() + + #GCC generates loads of -Wunknown-pragmas when compiling igl. The fix is not easy due to a bug in gcc, see + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66943 or + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53431 + # We will turn the warning of for GCC for now: + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + add_compile_options(-Wno-unknown-pragmas) + endif() if (SLIC3R_ASAN) add_compile_options(-fsanitize=address -fno-omit-frame-pointer) @@ -201,9 +213,12 @@ include_directories(${LIBDIR_BIN}/platform) include_directories(${LIBDIR}/clipper ${LIBDIR}/polypartition) if(WIN32) - # BOOST_ALL_NO_LIB: Avoid the automatic linking of Boost libraries on Windows. Rather rely on explicit linking. - add_definitions(-D_USE_MATH_DEFINES -D_WIN32 -DBOOST_ALL_NO_LIB -DBOOST_USE_WINAPI_VERSION=0x601 -D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS) -endif() + add_definitions(-D_USE_MATH_DEFINES -D_WIN32 -D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS) + if(MSVC) + # BOOST_ALL_NO_LIB: Avoid the automatic linking of Boost libraries on Windows. Rather rely on explicit linking. + add_definitions(-DBOOST_ALL_NO_LIB -DBOOST_USE_WINAPI_VERSION=0x601 ) + endif(MSVC) +endif(WIN32) add_definitions(-DwxUSE_UNICODE -D_UNICODE -DUNICODE -DWXINTL_NO_GETTEXT_MACRO) @@ -234,21 +249,40 @@ if(SLIC3R_STATIC) # set(Boost_USE_STATIC_RUNTIME ON) endif() #set(Boost_DEBUG ON) -# set(Boost_COMPILER "-vc120") +# set(Boost_COMPILER "-mgw81") if(NOT WIN32) # boost::process was introduced first in version 1.64.0 set(MINIMUM_BOOST_VERSION "1.64.0") endif() find_package(Boost ${MINIMUM_BOOST_VERSION} REQUIRED COMPONENTS system filesystem thread log locale regex) -if(Boost_FOUND) -# include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) - if (APPLE) - # BOOST_ASIO_DISABLE_KQUEUE : prevents a Boost ASIO bug on OS X: https://svn.boost.org/trac/boost/ticket/5339 - add_definitions(-DBOOST_ASIO_DISABLE_KQUEUE) - endif() - if(NOT SLIC3R_STATIC) - add_definitions(-DBOOST_LOG_DYN_LINK) - endif() + +add_library(boost_libs INTERFACE) +add_library(boost_headeronly INTERFACE) + +if (APPLE) + # BOOST_ASIO_DISABLE_KQUEUE : prevents a Boost ASIO bug on OS X: https://svn.boost.org/trac/boost/ticket/5339 + target_compile_definitions(boost_headeronly INTERFACE BOOST_ASIO_DISABLE_KQUEUE) +endif() + +if(NOT SLIC3R_STATIC) + target_compile_definitions(boost_headeronly INTERFACE BOOST_LOG_DYN_LINK) +endif() + +if(TARGET Boost::system) + message(STATUS "Boost::boost exists") + target_link_libraries(boost_headeronly INTERFACE Boost::boost) + target_link_libraries(boost_libs INTERFACE + boost_headeronly # includes the custom compile definitions as well + Boost::system + Boost::filesystem + Boost::thread + Boost::log + Boost::locale + Boost::regex + ) +else() + target_include_directories(boost_headeronly INTERFACE ${Boost_INCLUDE_DIRS}) + target_link_libraries(boost_libs INTERFACE boost_headeronly ${Boost_LIBRARIES}) endif() # Find and configure intel-tbb @@ -289,13 +323,13 @@ endif() # Find eigen3 or use bundled version if (NOT SLIC3R_STATIC) - find_package(Eigen3 3) + find_package(Eigen3 3.3) endif () -if (NOT Eigen3_FOUND) - set(Eigen3_FOUND 1) +if (NOT EIGEN3_FOUND) + set(EIGEN3_FOUND 1) set(EIGEN3_INCLUDE_DIR ${LIBDIR}/eigen/) endif () -include_directories(${EIGEN3_INCLUDE_DIR}) +include_directories(BEFORE SYSTEM ${EIGEN3_INCLUDE_DIR}) # Find expat or use bundled version # Always use the system libexpat on Linux. @@ -327,6 +361,10 @@ if (NOT GLEW_FOUND) endif () include_directories(${GLEW_INCLUDE_DIRS}) +# Find the Cereal serialization library +add_library(cereal INTERFACE) +target_include_directories(cereal INTERFACE include) + # l10n set(L10N_DIR "${SLIC3R_RESOURCES_DIR}/localization") add_custom_target(pot diff --git a/cmake/modules/PrecompiledHeader.cmake b/cmake/modules/PrecompiledHeader.cmake index e463021441..2f62a7dbeb 100644 --- a/cmake/modules/PrecompiledHeader.cmake +++ b/cmake/modules/PrecompiledHeader.cmake @@ -105,6 +105,9 @@ function(add_precompiled_header _target _input) cmake_parse_arguments(_PCH "FORCEINCLUDE" "SOURCE_CXX;SOURCE_C" "" ${ARGN}) get_filename_component(_input_we ${_input} NAME_WE) + get_filename_component(_input_full ${_input} ABSOLUTE) + file(TO_NATIVE_PATH "${_input_full}" _input_fullpath) + if(NOT _PCH_SOURCE_CXX) set(_PCH_SOURCE_CXX "${_input_we}.cpp") endif() @@ -138,16 +141,16 @@ function(add_precompiled_header _target _input) set_source_files_properties("${_source}" PROPERTIES OBJECT_OUTPUTS "${_pch_c_pch}") else() if(_source MATCHES \\.\(cpp|cxx|cc\)$) - set(_pch_compile_flags "${_pch_compile_flags} \"/Fp${_pch_cxx_pch}\" \"/Yu${_input}\"") + set(_pch_compile_flags "${_pch_compile_flags} \"/Fp${_pch_cxx_pch}\" \"/Yu${_input_fullpath}\"") set(_pch_source_cxx_needed TRUE) set_source_files_properties("${_source}" PROPERTIES OBJECT_DEPENDS "${_pch_cxx_pch}") else() - set(_pch_compile_flags "${_pch_compile_flags} \"/Fp${_pch_c_pch}\" \"/Yu${_input}\"") + set(_pch_compile_flags "${_pch_compile_flags} \"/Fp${_pch_c_pch}\" \"/Yu${_input_fullpath}\"") set(_pch_source_c_needed TRUE) set_source_files_properties("${_source}" PROPERTIES OBJECT_DEPENDS "${_pch_c_pch}") endif() if(_PCH_FORCEINCLUDE) - set(_pch_compile_flags "${_pch_compile_flags} /FI${_input}") + set(_pch_compile_flags "${_pch_compile_flags} /FI${_input_fullpath}") endif(_PCH_FORCEINCLUDE) endif() diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 5720901578..90ad6f0fa7 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -36,6 +36,12 @@ set(DESTDIR "${CMAKE_CURRENT_BINARY_DIR}/destdir" CACHE PATH "Destination direct option(DEP_DEBUG "Build debug variants (only applicable on Windows)" ON) option(DEP_WX_STABLE "Build against wxWidgets stable 3.0 as opposed to default 3.1 (Linux only)" OFF) +# On developer machines, it can be enabled to speed up compilation and suppress warnings coming from IGL. +# FIXME: +# Enabling this option is not safe. IGL will compile itself with its own version of Eigen while +# Slic3r compiles with a different version which will cause runtime errors. +# option(DEP_BUILD_IGL_STATIC "Build IGL as a static library. Might cause link errors and increase binary size." OFF) + message(STATUS "PrusaSlicer deps DESTDIR: ${DESTDIR}") message(STATUS "PrusaSlicer deps debug build: ${DEP_DEBUG}") @@ -70,7 +76,10 @@ elseif (APPLE) endif () include("deps-macos.cmake") -else () +elseif (MINGW) + message(STATUS "Building for MinGW...") + include("deps-mingw.cmake") +else() include("deps-linux.cmake") endif() @@ -83,7 +92,9 @@ if (MSVC) dep_libcurl dep_wxwidgets dep_gtest + dep_cereal dep_nlopt + # dep_qhull # Experimental dep_zlib # on Windows we still need zlib ) @@ -96,7 +107,10 @@ else() dep_libcurl dep_wxwidgets dep_gtest + dep_cereal dep_nlopt + dep_qhull + # dep_libigl # Not working, static build has different Eigen ) endif() diff --git a/deps/deps-macos.cmake b/deps/deps-macos.cmake index c7c6819e33..d22e4a2e2c 100644 --- a/deps/deps-macos.cmake +++ b/deps/deps-macos.cmake @@ -111,6 +111,6 @@ ExternalProject_Add(dep_wxwidgets --with-expat=builtin --disable-debug --disable-debug_flag - BUILD_COMMAND make "-j${NPROC}" && make -C locale allmo + BUILD_COMMAND make "-j${NPROC}" && PATH=/usr/local/opt/gettext/bin/:$ENV{PATH} make -C locale allmo INSTALL_COMMAND make install ) diff --git a/deps/deps-mingw.cmake b/deps/deps-mingw.cmake new file mode 100644 index 0000000000..bfe5f9fe43 --- /dev/null +++ b/deps/deps-mingw.cmake @@ -0,0 +1,76 @@ +set(DEP_CMAKE_OPTS "-DCMAKE_POSITION_INDEPENDENT_CODE=ON") +set(DEP_BOOST_TOOLSET "gcc") +set(DEP_BITS 64) + +find_package(Git REQUIRED) + +# TODO make sure to build tbb with -flifetime-dse=1 +include("deps-unix-common.cmake") + +ExternalProject_Add(dep_boost + EXCLUDE_FROM_ALL 1 + URL "https://dl.bintray.com/boostorg/release/1.70.0/source/boost_1_70_0.tar.gz" + URL_HASH SHA256=882b48708d211a5f48e60b0124cf5863c1534cd544ecd0664bb534a4b5d506e9 + BUILD_IN_SOURCE 1 + CONFIGURE_COMMAND bootstrap.bat + BUILD_COMMAND b2.exe + -j "${NPROC}" + --with-system + --with-filesystem + --with-thread + --with-log + --with-locale + --with-regex + "--prefix=${DESTDIR}/usr/local" + "address-model=${DEPS_BITS}" + "toolset=${DEP_BOOST_TOOLSET}" + link=static + define=BOOST_USE_WINAPI_VERSION=0x0502 + variant=release + threading=multi + boost.locale.icu=off + "${DEP_BOOST_DEBUG}" release install + INSTALL_COMMAND "" # b2 does that already +) + +ExternalProject_Add(dep_libcurl + EXCLUDE_FROM_ALL 1 + URL "https://curl.haxx.se/download/curl-7.58.0.tar.gz" + URL_HASH SHA256=cc245bf9a1a42a45df491501d97d5593392a03f7b4f07b952793518d97666115 + CMAKE_ARGS + -DBUILD_SHARED_LIBS=OFF + -DBUILD_TESTING=OFF + -DCURL_STATICLIB=ON + -DCURL_STATIC_CRT=ON + -DENABLE_THREADED_RESOLVER=ON + -DCURL_DISABLE_FTP=ON + -DCURL_DISABLE_LDAP=ON + -DCURL_DISABLE_LDAPS=ON + -DCURL_DISABLE_TELNET=ON + -DCURL_DISABLE_DICT=ON + -DCURL_DISABLE_FILE=ON + -DCURL_DISABLE_TFTP=ON + -DCURL_DISABLE_RTSP=ON + -DCURL_DISABLE_POP3=ON + -DCURL_DISABLE_IMAP=ON + -DCURL_DISABLE_SMTP=ON + -DCURL_DISABLE_GOPHER=ON + -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local + ${DEP_CMAKE_OPTS} +) + +ExternalProject_Add(dep_wxwidgets + EXCLUDE_FROM_ALL 1 + GIT_REPOSITORY "https://github.com/prusa3d/wxWidgets" + GIT_TAG v3.1.1-patched +# URL "https://github.com/wxWidgets/wxWidgets/releases/download/v3.1.1/wxWidgets-3.1.1.tar.bz2" +# URL_HASH SHA256=c925dfe17e8f8b09eb7ea9bfdcfcc13696a3e14e92750effd839f5e10726159e +# PATCH_COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}\\wxwidgets-pngprefix.h" src\\png\\pngprefix.h + CMAKE_ARGS + -DBUILD_SHARED_LIBS=OFF + -DwxUSE_LIBPNG=builtin + -DwxUSE_ZLIB=builtin + -DwxUSE_OPENGL=ON + -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local + ${DEP_CMAKE_OPTS} +) \ No newline at end of file diff --git a/deps/deps-unix-common.cmake b/deps/deps-unix-common.cmake index 3cf843f73d..6e559d05a3 100644 --- a/deps/deps-unix-common.cmake +++ b/deps/deps-unix-common.cmake @@ -1,6 +1,12 @@ # The unix common part expects DEP_CMAKE_OPTS to be set +if (MINGW) + set(TBB_MINGW_WORKAROUND "-flifetime-dse=1") +else () + set(TBB_MINGW_WORKAROUND "") +endif () + ExternalProject_Add(dep_tbb EXCLUDE_FROM_ALL 1 URL "https://github.com/wjakob/tbb/archive/a0dc9bf76d0120f917b641ed095360448cabc85b.tar.gz" @@ -8,6 +14,7 @@ ExternalProject_Add(dep_tbb CMAKE_ARGS -DTBB_BUILD_SHARED=OFF -DTBB_BUILD_TESTS=OFF + -DCMAKE_CXX_FLAGS=${TBB_MINGW_WORKAROUND} -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local ${DEP_CMAKE_OPTS} ) @@ -19,6 +26,16 @@ ExternalProject_Add(dep_gtest CMAKE_ARGS -DBUILD_GMOCK=OFF ${DEP_CMAKE_OPTS} -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local ) +ExternalProject_Add(dep_cereal + EXCLUDE_FROM_ALL 1 + URL "https://github.com/USCiLab/cereal/archive/v1.2.2.tar.gz" +# URL_HASH SHA256=c6dd7a5701fff8ad5ebb45a3dc8e757e61d52658de3918e38bab233e7fd3b4ae + CMAKE_ARGS + -DJUST_INSTALL_CEREAL=on + -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local + ${DEP_CMAKE_OPTS} +) + ExternalProject_Add(dep_nlopt EXCLUDE_FROM_ALL 1 URL "https://github.com/stevengj/nlopt/archive/v2.5.0.tar.gz" @@ -32,3 +49,44 @@ ExternalProject_Add(dep_nlopt -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local ${DEP_CMAKE_OPTS} ) +find_package(Git REQUIRED) + +ExternalProject_Add(dep_qhull + EXCLUDE_FROM_ALL 1 + URL "https://github.com/qhull/qhull/archive/v7.2.1.tar.gz" + URL_HASH SHA256=6fc251e0b75467e00943bfb7191e986fce0e1f8f6f0251f9c6ce5a843821ea78 + CMAKE_ARGS + -DBUILD_SHARED_LIBS=OFF + -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local + ${DEP_CMAKE_OPTS} + PATCH_COMMAND ${GIT_EXECUTABLE} apply --ignore-space-change --ignore-whitespace ${CMAKE_CURRENT_SOURCE_DIR}/qhull-mods.patch +) + +ExternalProject_Add(dep_libigl + EXCLUDE_FROM_ALL 1 + URL "https://github.com/libigl/libigl/archive/v2.0.0.tar.gz" + URL_HASH SHA256=42518e6b106c7209c73435fd260ed5d34edeb254852495b4c95dce2d95401328 + CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local + -DLIBIGL_BUILD_PYTHON=OFF + -DLIBIGL_BUILD_TESTS=OFF + -DLIBIGL_BUILD_TUTORIALS=OFF + -DLIBIGL_USE_STATIC_LIBRARY=OFF #${DEP_BUILD_IGL_STATIC} + -DLIBIGL_WITHOUT_COPYLEFT=OFF + -DLIBIGL_WITH_CGAL=OFF + -DLIBIGL_WITH_COMISO=OFF + -DLIBIGL_WITH_CORK=OFF + -DLIBIGL_WITH_EMBREE=OFF + -DLIBIGL_WITH_MATLAB=OFF + -DLIBIGL_WITH_MOSEK=OFF + -DLIBIGL_WITH_OPENGL=OFF + -DLIBIGL_WITH_OPENGL_GLFW=OFF + -DLIBIGL_WITH_OPENGL_GLFW_IMGUI=OFF + -DLIBIGL_WITH_PNG=OFF + -DLIBIGL_WITH_PYTHON=OFF + -DLIBIGL_WITH_TETGEN=OFF + -DLIBIGL_WITH_TRIANGLE=OFF + -DLIBIGL_WITH_XML=OFF + PATCH_COMMAND ${GIT_EXECUTABLE} apply --ignore-space-change --ignore-whitespace ${CMAKE_CURRENT_SOURCE_DIR}/igl-fixes.patch +) + diff --git a/deps/deps-windows.cmake b/deps/deps-windows.cmake index b3b31e5f37..85013fbddd 100644 --- a/deps/deps-windows.cmake +++ b/deps/deps-windows.cmake @@ -1,21 +1,38 @@ - +# https://cmake.org/cmake/help/latest/variable/MSVC_VERSION.html if (MSVC_VERSION EQUAL 1800) +# 1800 = VS 12.0 (v120 toolset) set(DEP_VS_VER "12") set(DEP_BOOST_TOOLSET "msvc-12.0") elseif (MSVC_VERSION EQUAL 1900) +# 1900 = VS 14.0 (v140 toolset) set(DEP_VS_VER "14") set(DEP_BOOST_TOOLSET "msvc-14.0") -elseif (MSVC_VERSION GREATER 1900) +elseif (MSVC_VERSION LESS 1920) +# 1910-1919 = VS 15.0 (v141 toolset) set(DEP_VS_VER "15") set(DEP_BOOST_TOOLSET "msvc-14.1") +elseif (MSVC_VERSION LESS 1930) +# 1920-1929 = VS 16.0 (v142 toolset) + set(DEP_VS_VER "16") + set(DEP_BOOST_TOOLSET "msvc-14.2") else () message(FATAL_ERROR "Unsupported MSVC version") endif () +if (CMAKE_CXX_COMPILER_ID STREQUAL Clang) + set(DEP_BOOST_TOOLSET "clang-win") +endif () + if (${DEPS_BITS} EQUAL 32) set(DEP_MSVC_GEN "Visual Studio ${DEP_VS_VER}") + set(DEP_PLATFORM "Win32") else () - set(DEP_MSVC_GEN "Visual Studio ${DEP_VS_VER} Win64") + if (DEP_VS_VER LESS 16) + set(DEP_MSVC_GEN "Visual Studio ${DEP_VS_VER} Win64") + else () + set(DEP_MSVC_GEN "Visual Studio ${DEP_VS_VER}") + endif () + set(DEP_PLATFORM "x64") endif () @@ -28,8 +45,8 @@ endif () ExternalProject_Add(dep_boost EXCLUDE_FROM_ALL 1 - URL "https://dl.bintray.com/boostorg/release/1.66.0/source/boost_1_66_0.tar.gz" - URL_HASH SHA256=bd0df411efd9a585e5a2212275f8762079fed8842264954675a4fddc46cfcf60 + URL "https://dl.bintray.com/boostorg/release/1.70.0/source/boost_1_70_0.tar.gz" + URL_HASH SHA256=882b48708d211a5f48e60b0124cf5863c1534cd544ecd0664bb534a4b5d506e9 BUILD_IN_SOURCE 1 CONFIGURE_COMMAND bootstrap.bat BUILD_COMMAND b2.exe @@ -57,6 +74,7 @@ ExternalProject_Add(dep_tbb URL "https://github.com/wjakob/tbb/archive/a0dc9bf76d0120f917b641ed095360448cabc85b.tar.gz" URL_HASH SHA256=0545cb6033bd1873fcae3ea304def720a380a88292726943ae3b9b207f322efe CMAKE_GENERATOR "${DEP_MSVC_GEN}" + CMAKE_GENERATOR_PLATFORM "${DEP_PLATFORM}" CMAKE_ARGS -DCMAKE_DEBUG_POSTFIX=_debug -DTBB_BUILD_SHARED=OFF @@ -81,6 +99,7 @@ ExternalProject_Add(dep_gtest URL "https://github.com/google/googletest/archive/release-1.8.1.tar.gz" URL_HASH SHA256=9bf1fe5182a604b4135edc1a425ae356c9ad15e9b23f9f12a02e80184c3a249c CMAKE_GENERATOR "${DEP_MSVC_GEN}" + CMAKE_GENERATOR_PLATFORM "${DEP_PLATFORM}" CMAKE_ARGS -DBUILD_GMOCK=OFF -Dgtest_force_shared_crt=ON @@ -100,11 +119,26 @@ if (${DEP_DEBUG}) endif () +ExternalProject_Add(dep_cereal + EXCLUDE_FROM_ALL 1 + URL "https://github.com/USCiLab/cereal/archive/v1.2.2.tar.gz" +# URL_HASH SHA256=c6dd7a5701fff8ad5ebb45a3dc8e757e61d52658de3918e38bab233e7fd3b4ae + CMAKE_GENERATOR "${DEP_MSVC_GEN}" + CMAKE_GENERATOR_PLATFORM "${DEP_PLATFORM}" + CMAKE_ARGS + -DJUST_INSTALL_CEREAL=on + "-DCMAKE_INSTALL_PREFIX:PATH=${DESTDIR}\\usr\\local" + BUILD_COMMAND msbuild /m /P:Configuration=Release INSTALL.vcxproj + INSTALL_COMMAND "" +) + + ExternalProject_Add(dep_nlopt EXCLUDE_FROM_ALL 1 URL "https://github.com/stevengj/nlopt/archive/v2.5.0.tar.gz" URL_HASH SHA256=c6dd7a5701fff8ad5ebb45a3dc8e757e61d52658de3918e38bab233e7fd3b4ae CMAKE_GENERATOR "${DEP_MSVC_GEN}" + CMAKE_GENERATOR_PLATFORM "${DEP_PLATFORM}" CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF -DNLOPT_PYTHON=OFF @@ -133,6 +167,7 @@ ExternalProject_Add(dep_zlib URL "https://zlib.net/zlib-1.2.11.tar.xz" URL_HASH SHA256=4ff941449631ace0d4d203e3483be9dbc9da454084111f97ea0a2114e19bf066 CMAKE_GENERATOR "${DEP_MSVC_GEN}" + CMAKE_GENERATOR_PLATFORM "${DEP_PLATFORM}" CMAKE_ARGS -DSKIP_INSTALL_FILES=ON # Prevent installation of man pages et al. "-DINSTALL_BIN_DIR=${CMAKE_CURRENT_BINARY_DIR}\\fallout" # I found no better way of preventing zlib from creating & installing DLLs :-/ @@ -199,6 +234,33 @@ if (${DEP_DEBUG}) ) endif () +find_package(Git REQUIRED) + +ExternalProject_Add(dep_qhull + EXCLUDE_FROM_ALL 1 + URL "https://github.com/qhull/qhull/archive/v7.2.1.tar.gz" + URL_HASH SHA256=6fc251e0b75467e00943bfb7191e986fce0e1f8f6f0251f9c6ce5a843821ea78 + CMAKE_GENERATOR "${DEP_MSVC_GEN}" + CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local + -DBUILD_SHARED_LIBS=OFF + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + -DCMAKE_DEBUG_POSTFIX=d + PATCH_COMMAND ${GIT_EXECUTABLE} apply --ignore-space-change --ignore-whitespace ${CMAKE_CURRENT_SOURCE_DIR}/qhull-mods.patch + BUILD_COMMAND msbuild /m /P:Configuration=Release INSTALL.vcxproj + INSTALL_COMMAND "" +) + +if (${DEP_DEBUG}) + ExternalProject_Get_Property(dep_qhull BINARY_DIR) + ExternalProject_Add_Step(dep_qhull build_debug + DEPENDEES build + DEPENDERS install + COMMAND msbuild /m /P:Configuration=Debug INSTALL.vcxproj + WORKING_DIRECTORY "${BINARY_DIR}" + ) +endif () + if (${DEPS_BITS} EQUAL 32) set(DEP_WXWIDGETS_TARGET "") @@ -208,6 +270,51 @@ else () set(DEP_WXWIDGETS_LIBDIR "vc_x64_lib") endif () +find_package(Git REQUIRED) + +ExternalProject_Add(dep_libigl + EXCLUDE_FROM_ALL 1 + URL "https://github.com/libigl/libigl/archive/v2.0.0.tar.gz" + URL_HASH SHA256=42518e6b106c7209c73435fd260ed5d34edeb254852495b4c95dce2d95401328 + CMAKE_GENERATOR "${DEP_MSVC_GEN}" + CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local + -DLIBIGL_BUILD_PYTHON=OFF + -DLIBIGL_BUILD_TESTS=OFF + -DLIBIGL_BUILD_TUTORIALS=OFF + -DLIBIGL_USE_STATIC_LIBRARY=OFF #${DEP_BUILD_IGL_STATIC} + -DLIBIGL_WITHOUT_COPYLEFT=OFF + -DLIBIGL_WITH_CGAL=OFF + -DLIBIGL_WITH_COMISO=OFF + -DLIBIGL_WITH_CORK=OFF + -DLIBIGL_WITH_EMBREE=OFF + -DLIBIGL_WITH_MATLAB=OFF + -DLIBIGL_WITH_MOSEK=OFF + -DLIBIGL_WITH_OPENGL=OFF + -DLIBIGL_WITH_OPENGL_GLFW=OFF + -DLIBIGL_WITH_OPENGL_GLFW_IMGUI=OFF + -DLIBIGL_WITH_PNG=OFF + -DLIBIGL_WITH_PYTHON=OFF + -DLIBIGL_WITH_TETGEN=OFF + -DLIBIGL_WITH_TRIANGLE=OFF + -DLIBIGL_WITH_XML=OFF + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + -DCMAKE_DEBUG_POSTFIX=d + PATCH_COMMAND ${GIT_EXECUTABLE} apply --ignore-space-change --ignore-whitespace ${CMAKE_CURRENT_SOURCE_DIR}/igl-fixes.patch + BUILD_COMMAND msbuild /m /P:Configuration=Release INSTALL.vcxproj + INSTALL_COMMAND "" +) + +if (${DEP_DEBUG}) + ExternalProject_Get_Property(dep_libigl BINARY_DIR) + ExternalProject_Add_Step(dep_libigl build_debug + DEPENDEES build + DEPENDERS install + COMMAND msbuild /m /P:Configuration=Debug INSTALL.vcxproj + WORKING_DIRECTORY "${BINARY_DIR}" + ) +endif () + ExternalProject_Add(dep_wxwidgets EXCLUDE_FROM_ALL 1 GIT_REPOSITORY "https://github.com/prusa3d/wxWidgets" diff --git a/deps/igl-fixes.patch b/deps/igl-fixes.patch new file mode 100644 index 0000000000..b0ff9205d0 --- /dev/null +++ b/deps/igl-fixes.patch @@ -0,0 +1,128 @@ +diff --git a/cmake/libigl-config.cmake.in b/cmake/libigl-config.cmake.in +index 317c745c..f9808e1e 100644 +--- a/cmake/libigl-config.cmake.in ++++ b/cmake/libigl-config.cmake.in +@@ -2,28 +2,28 @@ + + include(${CMAKE_CURRENT_LIST_DIR}/libigl-export.cmake) + +-if (TARGET igl::core) +- if (NOT TARGET Eigen3::Eigen) +- find_package(Eigen3 QUIET) +- if (NOT Eigen3_FOUND) +- # try with PkgCOnfig +- find_package(PkgConfig REQUIRED) +- pkg_check_modules(Eigen3 QUIET IMPORTED_TARGET eigen3) +- endif() +- +- if (NOT Eigen3_FOUND) +- message(FATAL_ERROR "Could not find required dependency Eigen3") +- set(libigl_core_FOUND FALSE) +- else() +- target_link_libraries(igl::core INTERFACE PkgConfig::Eigen3) +- set(libigl_core_FOUND TRUE) +- endif() +- else() +- target_link_libraries(igl::core INTERFACE Eigen3::Eigen) +- set(libigl_core_FOUND TRUE) +- endif() +- +-endif() ++# if (TARGET igl::core) ++# if (NOT TARGET Eigen3::Eigen) ++# find_package(Eigen3 QUIET) ++# if (NOT Eigen3_FOUND) ++# # try with PkgCOnfig ++# find_package(PkgConfig REQUIRED) ++# pkg_check_modules(Eigen3 QUIET IMPORTED_TARGET eigen3) ++# endif() ++# ++# if (NOT Eigen3_FOUND) ++# message(FATAL_ERROR "Could not find required dependency Eigen3") ++# set(libigl_core_FOUND FALSE) ++# else() ++# target_link_libraries(igl::core INTERFACE PkgConfig::Eigen3) ++# set(libigl_core_FOUND TRUE) ++# endif() ++# else() ++# target_link_libraries(igl::core INTERFACE Eigen3::Eigen) ++# set(libigl_core_FOUND TRUE) ++# endif() ++# ++# endif() + + check_required_components(libigl) + +diff --git a/cmake/libigl.cmake b/cmake/libigl.cmake +index 4b11007a..47e6c395 100644 +--- a/cmake/libigl.cmake ++++ b/cmake/libigl.cmake +@@ -445,6 +445,7 @@ function(install_dir_files dir_name) + if(NOT LIBIGL_USE_STATIC_LIBRARY) + file(GLOB public_sources + ${CMAKE_CURRENT_SOURCE_DIR}/include/igl${subpath}/*.cpp ++ ${CMAKE_CURRENT_SOURCE_DIR}/include/igl${subpath}/*.c + ) + endif() + list(APPEND files_to_install ${public_sources}) +diff --git a/include/igl/AABB.cpp b/include/igl/AABB.cpp +index 09537335..92e90cb7 100644 +--- a/include/igl/AABB.cpp ++++ b/include/igl/AABB.cpp +@@ -1071,5 +1071,11 @@ template void igl::AABB, 3>::init, 2>::init >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); + template double igl::AABB, 3>::squared_distance >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix const&, double, int&, Eigen::PlainObjectBase >&) const; ++template float igl::AABB const, 0, Eigen::Stride<0, 0> >, 3>::squared_distance const, 0, Eigen::Stride<0, 0> > >(Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::Matrix const&, int&, Eigen::PlainObjectBase >&) const; + template bool igl::AABB, 3>::intersect_ray >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix const&, Eigen::Matrix const&, igl::Hit&) const; ++template bool igl::AABB, 3>::intersect_ray >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix const&, Eigen::Matrix const&, std::vector&) const; ++ ++template void igl::AABB const, 0, Eigen::Stride<0, 0> >, 3>::init const, 0, Eigen::Stride<0, 0> > >(Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&); ++ ++template bool igl::AABB const, 0, Eigen::Stride<0, 0> >, 3>::intersect_ray const, 0, Eigen::Stride<0, 0> > >(Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::Matrix const&, Eigen::Matrix const&, std::vector >&) const; + #endif +diff --git a/include/igl/barycenter.cpp b/include/igl/barycenter.cpp +index 065f82aa..ec2d96cd 100644 +--- a/include/igl/barycenter.cpp ++++ b/include/igl/barycenter.cpp +@@ -54,4 +54,6 @@ template void igl::barycenter, Eigen::M + template void igl::barycenter, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); + template void igl::barycenter, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); + template void igl::barycenter, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); ++ ++template void igl::barycenter const, 0, Eigen::Stride<0, 0> >, Eigen::Map const, 0, Eigen::Stride<0, 0> >, Eigen::Matrix >(Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::PlainObjectBase >&); + #endif +diff --git a/include/igl/point_simplex_squared_distance.cpp b/include/igl/point_simplex_squared_distance.cpp +index 2b98bd28..c66d9ae1 100644 +--- a/include/igl/point_simplex_squared_distance.cpp ++++ b/include/igl/point_simplex_squared_distance.cpp +@@ -178,4 +178,6 @@ template void igl::point_simplex_squared_distance<3, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix::Index, double&, Eigen::MatrixBase >&, Eigen::PlainObjectBase >&); + template void igl::point_simplex_squared_distance<2, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix::Index, double&, Eigen::MatrixBase >&, Eigen::PlainObjectBase >&); + template void igl::point_simplex_squared_distance<2, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix::Index, double&, Eigen::MatrixBase >&, Eigen::PlainObjectBase >&); ++ ++template void igl::point_simplex_squared_distance<3, Eigen::Matrix, Eigen::Map const, 0, Eigen::Stride<0, 0> >, Eigen::Map const, 0, Eigen::Stride<0, 0> >, float, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::Map const, 0, Eigen::Stride<0, 0> >::Index, float&, Eigen::MatrixBase >&); + #endif +diff --git a/include/igl/ray_box_intersect.cpp b/include/igl/ray_box_intersect.cpp +index 4a88b89e..b547f8f8 100644 +--- a/include/igl/ray_box_intersect.cpp ++++ b/include/igl/ray_box_intersect.cpp +@@ -147,4 +147,6 @@ IGL_INLINE bool igl::ray_box_intersect( + #ifdef IGL_STATIC_LIBRARY + // Explicit template instantiation + template bool igl::ray_box_intersect, Eigen::Matrix, double>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::AlignedBox const&, double const&, double const&, double&, double&); ++ ++template bool igl::ray_box_intersect, Eigen::Matrix, float>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::AlignedBox const&, float const&, float const&, float&, float&); + #endif +diff --git a/include/igl/ray_mesh_intersect.cpp b/include/igl/ray_mesh_intersect.cpp +index 9a70a22b..4233e722 100644 +--- a/include/igl/ray_mesh_intersect.cpp ++++ b/include/igl/ray_mesh_intersect.cpp +@@ -83,4 +83,7 @@ IGL_INLINE bool igl::ray_mesh_intersect( + template bool igl::ray_mesh_intersect, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector >&); + template bool igl::ray_mesh_intersect, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::Hit&); + template bool igl::ray_mesh_intersect, Eigen::Matrix, Eigen::Matrix, Eigen::Block const, 1, -1, false> >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase const, 1, -1, false> > const&, igl::Hit&); ++template bool igl::ray_mesh_intersect, Eigen::Matrix, Eigen::Matrix, Eigen::Block const, 1, -1, false> >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase const, 1, -1, false> > const&, std::vector >&); ++ ++template bool igl::ray_mesh_intersect, Eigen::Matrix, Eigen::Map const, 0, Eigen::Stride<0, 0> >, Eigen::Block const, 0, Eigen::Stride<0, 0> > const, 1, -1, true> >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > const, 1, -1, true> > const&, std::vector >&); + #endif diff --git a/deps/qhull-mods.patch b/deps/qhull-mods.patch new file mode 100644 index 0000000000..94aeeca2f5 --- /dev/null +++ b/deps/qhull-mods.patch @@ -0,0 +1,121 @@ +From a31ae4781a4afa60e21c70e5b4ae784bcd447c8a Mon Sep 17 00:00:00 2001 +From: tamasmeszaros +Date: Thu, 6 Jun 2019 15:41:43 +0200 +Subject: [PATCH] prusa-slicer changes + +--- + CMakeLists.txt | 44 +++++++++++++++++++++++++++++++++++--- + Config.cmake.in | 2 ++ + src/libqhull_r/qhull_r-exports.def | 2 ++ + src/libqhull_r/user_r.h | 2 +- + 4 files changed, 46 insertions(+), 4 deletions(-) + create mode 100644 Config.cmake.in + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 59dff41..20c2ec5 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -61,7 +61,7 @@ + # $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $ + + project(qhull) +-cmake_minimum_required(VERSION 2.6) ++cmake_minimum_required(VERSION 3.0) + + # Define qhull_VERSION in CMakeLists.txt, Makefile, qhull-exports.def, qhull_p-exports.def, qhull_r-exports.def, qhull-warn.pri + set(qhull_VERSION2 "2015.2 2016/01/18") # not used, See global.c, global_r.c, rbox.c, rbox_r.c +@@ -610,10 +610,48 @@ add_test(NAME user_eg3 + # Define install + # --------------------------------------- + +-install(TARGETS ${qhull_TARGETS_INSTALL} ++install(TARGETS ${qhull_TARGETS_INSTALL} EXPORT QhullTargets + RUNTIME DESTINATION ${BIN_INSTALL_DIR} + LIBRARY DESTINATION ${LIB_INSTALL_DIR} +- ARCHIVE DESTINATION ${LIB_INSTALL_DIR}) ++ ARCHIVE DESTINATION ${LIB_INSTALL_DIR} ++ INCLUDES DESTINATION include) ++ ++include(CMakePackageConfigHelpers) ++ ++write_basic_package_version_file( ++ "${CMAKE_CURRENT_BINARY_DIR}/Qhull/QhullConfigVersion.cmake" ++ VERSION ${qhull_VERSION} ++ COMPATIBILITY AnyNewerVersion ++) ++ ++export(EXPORT QhullTargets ++ FILE "${CMAKE_CURRENT_BINARY_DIR}/Qhull/QhullTargets.cmake" ++ NAMESPACE Qhull:: ++) ++ ++configure_file(Config.cmake.in ++ "${CMAKE_CURRENT_BINARY_DIR}/Qhull/QhullConfig.cmake" ++ @ONLY ++) ++ ++set(ConfigPackageLocation lib/cmake/Qhull) ++install(EXPORT QhullTargets ++ FILE ++ QhullTargets.cmake ++ NAMESPACE ++ Qhull:: ++ DESTINATION ++ ${ConfigPackageLocation} ++) ++install( ++ FILES ++ "${CMAKE_CURRENT_BINARY_DIR}/Qhull/QhullConfig.cmake" ++ "${CMAKE_CURRENT_BINARY_DIR}/Qhull/QhullConfigVersion.cmake" ++ DESTINATION ++ ${ConfigPackageLocation} ++ COMPONENT ++ Devel ++) + + install(FILES ${libqhull_HEADERS} DESTINATION ${INCLUDE_INSTALL_DIR}/libqhull) + install(FILES ${libqhull_DOC} DESTINATION ${INCLUDE_INSTALL_DIR}/libqhull) +diff --git a/Config.cmake.in b/Config.cmake.in +new file mode 100644 +index 0000000..bc92bfe +--- /dev/null ++++ b/Config.cmake.in +@@ -0,0 +1,2 @@ ++include("${CMAKE_CURRENT_LIST_DIR}/QhullTargets.cmake") ++ +diff --git a/src/libqhull_r/qhull_r-exports.def b/src/libqhull_r/qhull_r-exports.def +index 325d57c..72f6ad0 100644 +--- a/src/libqhull_r/qhull_r-exports.def ++++ b/src/libqhull_r/qhull_r-exports.def +@@ -185,6 +185,7 @@ qh_memsetup + qh_memsize + qh_memstatistics + qh_memtotal ++qh_memcheck + qh_merge_degenredundant + qh_merge_nonconvex + qh_mergecycle +@@ -372,6 +373,7 @@ qh_settruncate + qh_setunique + qh_setvoronoi_all + qh_setzero ++qh_setendpointer + qh_sharpnewfacets + qh_skipfacet + qh_skipfilename +diff --git a/src/libqhull_r/user_r.h b/src/libqhull_r/user_r.h +index fc105b9..7cca65a 100644 +--- a/src/libqhull_r/user_r.h ++++ b/src/libqhull_r/user_r.h +@@ -139,7 +139,7 @@ Code flags -- + REALfloat = 1 all numbers are 'float' type + = 0 all numbers are 'double' type + */ +-#define REALfloat 0 ++#define REALfloat 1 + + #if (REALfloat == 1) + #define realT float +-- +2.16.2.windows.1 + diff --git a/doc/How to build - Mac OS.md b/doc/How to build - Mac OS.md index 42a71e10d7..082c560b7a 100644 --- a/doc/How to build - Mac OS.md +++ b/doc/How to build - Mac OS.md @@ -1,7 +1,15 @@ # Building PrusaSlicer on Mac OS -To build PrusaSlicer on Mac OS, you will need to install XCode, [CMake](https://cmake.org/) (available on Brew) and possibly git. +To build PrusaSlicer on Mac OS, you will need the following software: + +- XCode +- CMake +- git +- gettext + +XCode is available through Apple's App Store, the other three tools are available on +[brew](https://brew.sh/) (use `brew install cmake git gettext` to install them). ### Dependencies @@ -20,6 +28,9 @@ You can also customize the bundle output path using the `-DDESTDIR=` **Warning**: Once the dependency bundle is installed in a destdir, the destdir cannot be moved elsewhere. (This is because wxWidgets hardcodes the installation path.) +FIXME The Cereal serialization library needs a tiny patch on some old OSX clang installations +https://github.com/USCiLab/cereal/issues/339#issuecomment-246166717 + ### Building PrusaSlicer diff --git a/doc/How to build - Windows.md b/doc/How to build - Windows.md index 242af9dab2..b8829047bf 100644 --- a/doc/How to build - Windows.md +++ b/doc/How to build - Windows.md @@ -1,24 +1,37 @@ +# This how-to is out of date + +We have switched to MS Visual Studio 2019. + +We don't use MSVS 2013 any more. At the moment we are in the process of creating new pre-built dependency bundles +and updating this document. In the meantime, you will need to compile the dependencies yourself +[the same way as before](#building-the-dependencies-package-yourself) +except with CMake generators for MSVS 2019 instead of 2013. + +Thank you for understanding. + +--- + # Building PrusaSlicer on Microsoft Windows -The currently supported way of building PrusaSlicer on Windows is with CMake and MS Visual Studio 2013. +~~The currently supported way of building PrusaSlicer on Windows is with CMake and MS Visual Studio 2013. You can use the free [Visual Studio 2013 Community Edition](https://www.visualstudio.com/vs/older-downloads/). -CMake installer can be downloaded from [the official website](https://cmake.org/download/). +CMake installer can be downloaded from [the official website](https://cmake.org/download/).~~ -Building with newer versions of MSVS (2015, 2017) may work too as reported by some of our users. +~~Building with newer versions of MSVS (2015, 2017) may work too as reported by some of our users.~~ _Note:_ Thanks to [**@supermerill**](https://github.com/supermerill) for testing and inspiration for this guide. ### Dependencies On Windows PrusaSlicer is built against statically built libraries. -We provide a prebuilt package of all the needed dependencies. This package only works on Visual Studio 2013, so if you are using a newer version of Visual Studio, you need to compile the dependencies yourself as per [below](#building-the-dependencies-package-yourself). +~~We provide a prebuilt package of all the needed dependencies. This package only works on Visual Studio 2013, so~~ if you are using a newer version of Visual Studio, you need to compile the dependencies yourself as per [below](#building-the-dependencies-package-yourself). The package comes in a several variants: - - [64 bit, Release mode only](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=destdir-64.7z) (41 MB, 578 MB unpacked) - - [64 bit, Release and Debug mode](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=destdir-64-dev.7z) (88 MB, 1.3 GB unpacked) - - [32 bit, Release mode only](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=destdir-32.7z) (38 MB, 520 MB unpacked) - - [32 bit, Release and Debug mode](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=destdir-32-dev.7z) (74 MB, 1.1 GB unpacked) + - ~~64 bit, Release mode only (41 MB, 578 MB unpacked)~~ + - ~~64 bit, Release and Debug mode (88 MB, 1.3 GB unpacked)~~ + - ~~32 bit, Release mode only (38 MB, 520 MB unpacked)~~ + - ~~32 bit, Release and Debug mode (74 MB, 1.1 GB unpacked)~~ When unsure, use the _Release mode only_ variant, the _Release and Debug_ variant is only needed for debugging & development. diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 44100db8cf..94f0b5658f 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -50,7 +50,6 @@ use Slic3r::Point; use Slic3r::Polygon; use Slic3r::Polyline; use Slic3r::Print::Object; -use Slic3r::Print::Simple; use Slic3r::Surface; our $build = eval "use Slic3r::Build; 1"; diff --git a/lib/Slic3r/Print/Simple.pm b/lib/Slic3r/Print/Simple.pm deleted file mode 100644 index 2ab68f4d38..0000000000 --- a/lib/Slic3r/Print/Simple.pm +++ /dev/null @@ -1,104 +0,0 @@ -# A simple wrapper to quickly print a single model without a GUI. -# Used by the command line slic3r.pl, by command line utilities pdf-slic3s.pl and view-toolpaths.pl, -# and by the quick slice menu of the Slic3r GUI. -# -# It creates and owns an instance of Slic3r::Print to perform the slicing -# and it accepts an instance of Slic3r::Model from the outside. - -package Slic3r::Print::Simple; -use Moo; - -use Slic3r::Geometry qw(X Y); - -has '_print' => ( - is => 'ro', - default => sub { Slic3r::Print->new }, - handles => [qw(apply_config_perl_tests_only extruders output_filepath - total_used_filament total_extruded_volume - placeholder_parser process)], -); - -has 'duplicate' => ( - is => 'rw', - default => sub { 1 }, -); - -has 'scale' => ( - is => 'rw', - default => sub { 1 }, -); - -has 'rotate' => ( - is => 'rw', - default => sub { 0 }, -); - -has 'duplicate_grid' => ( - is => 'rw', - default => sub { [1,1] }, -); - -has 'print_center' => ( - is => 'rw', - default => sub { Slic3r::Pointf->new(100,100) }, -); - -has 'dont_arrange' => ( - is => 'rw', - default => sub { 0 }, -); - -has 'output_file' => ( - is => 'rw', -); - -sub set_model { - # $model is of type Slic3r::Model - my ($self, $model) = @_; - - # make method idempotent so that the object is reusable - $self->_print->clear_objects; - - # make sure all objects have at least one defined instance - my $need_arrange = $model->add_default_instances && ! $self->dont_arrange; - - # apply scaling and rotation supplied from command line if any - foreach my $instance (map @{$_->instances}, @{$model->objects}) { - $instance->set_scaling_factor($instance->scaling_factor * $self->scale); - $instance->set_rotation($instance->rotation + $self->rotate); - } - - if ($self->duplicate_grid->[X] > 1 || $self->duplicate_grid->[Y] > 1) { - $model->duplicate_objects_grid($self->duplicate_grid->[X], $self->duplicate_grid->[Y], $self->_print->config->duplicate_distance); - } elsif ($need_arrange) { - $model->duplicate_objects($self->duplicate, $self->_print->config->min_object_distance); - } elsif ($self->duplicate > 1) { - # if all input objects have defined position(s) apply duplication to the whole model - $model->duplicate($self->duplicate, $self->_print->config->min_object_distance); - } - $_->translate(0,0,-$_->bounding_box->z_min) for @{$model->objects}; - $model->center_instances_around_point($self->print_center) if (! $self->dont_arrange); - - foreach my $model_object (@{$model->objects}) { - $self->_print->auto_assign_extruders($model_object); - $self->_print->add_model_object($model_object); - } -} - -sub export_gcode { - my ($self) = @_; - $self->_print->validate; - $self->_print->export_gcode($self->output_file // ''); -} - -sub export_png { - my ($self) = @_; - - $self->_before_export; - - $self->_print->export_png(output_file => $self->output_file); - - $self->_after_export; -} - -1; diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index d1b99e48c4..570bca41bc 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -146,60 +146,66 @@ sub mesh { } sub model { - my ($model_name, %params) = @_; + my ($model_names, %params) = @_; + $model_names = [ $model_names ] if ! ref($model_names); - my $input_file = "${model_name}.stl"; - my $mesh = mesh($model_name, %params); -# $mesh->write_ascii("out/$input_file"); - my $model = Slic3r::Model->new; - my $object = $model->add_object(input_file => $input_file); - $model->set_material($model_name); - $object->add_volume(mesh => $mesh, material_id => $model_name); - $object->add_instance( - offset => Slic3r::Pointf->new(0,0), - # 3D full transform - rotation => Slic3r::Pointf3->new(0, 0, $params{rotation} // 0), - scaling_factor => Slic3r::Pointf3->new($params{scale} // 1, $params{scale} // 1, $params{scale} // 1), - # old transform -# rotation => $params{rotation} // 0, -# scaling_factor => $params{scale} // 1, - ); + + for my $model_name (@$model_names) { + my $input_file = "${model_name}.stl"; + my $mesh = mesh($model_name, %params); + # $mesh->write_ascii("out/$input_file"); + + my $object = $model->add_object(input_file => $input_file); + $model->set_material($model_name); + $object->add_volume(mesh => $mesh, material_id => $model_name); + $object->add_instance( + offset => Slic3r::Pointf->new(0,0), + # 3D full transform + rotation => Slic3r::Pointf3->new(0, 0, $params{rotation} // 0), + scaling_factor => Slic3r::Pointf3->new($params{scale} // 1, $params{scale} // 1, $params{scale} // 1), + # old transform + # rotation => $params{rotation} // 0, + # scaling_factor => $params{scale} // 1, + ); + } return $model; } sub init_print { my ($models, %params) = @_; + my $model; + if (ref($models) eq 'ARRAY') { + $model = model($models, %params); + } elsif (ref($models)) { + $model = $models; + } else { + $model = model([$models], %params); + } my $config = Slic3r::Config->new; $config->apply($params{config}) if $params{config}; $config->set('gcode_comments', 1) if $ENV{SLIC3R_TESTS_GCODE}; my $print = Slic3r::Print->new; - $print->apply_config_perl_tests_only($config); - - $models = [$models] if ref($models) ne 'ARRAY'; - $models = [ map { ref($_) ? $_ : model($_, %params) } @$models ]; - for my $model (@$models) { - die "Unknown model in test" if !defined $model; - if (defined $params{duplicate} && $params{duplicate} > 1) { - $model->duplicate($params{duplicate} // 1, $print->config->min_object_distance); - } - $model->arrange_objects($print->config->min_object_distance); - $model->center_instances_around_point($params{print_center} ? Slic3r::Pointf->new(@{$params{print_center}}) : Slic3r::Pointf->new(100,100)); - foreach my $model_object (@{$model->objects}) { - $print->auto_assign_extruders($model_object); - $print->add_model_object($model_object); - } + die "Unknown model in test" if !defined $model; + if (defined $params{duplicate} && $params{duplicate} > 1) { + $model->duplicate($params{duplicate} // 1, $config->min_object_distance); } - # Call apply_config_perl_tests_only one more time, so that the layer height profiles are updated over all PrintObjects. - $print->apply_config_perl_tests_only($config); + $model->arrange_objects($config->min_object_distance); + $model->center_instances_around_point($params{print_center} ? Slic3r::Pointf->new(@{$params{print_center}}) : Slic3r::Pointf->new(100,100)); + foreach my $model_object (@{$model->objects}) { + $model_object->ensure_on_bed; + $print->auto_assign_extruders($model_object); + } + + $print->apply($model, $config); $print->validate; # We return a proxy object in order to keep $models alive as required by the Print API. return Slic3r::Test::Print->new( - print => $print, - models => $models, + print => $print, + model => $model, ); } @@ -250,7 +256,7 @@ sub add_facet { package Slic3r::Test::Print; use Moo; -has 'print' => (is => 'ro', required => 1, handles => [qw(process apply_config_perl_tests_only)]); -has 'models' => (is => 'ro', required => 1); +has 'print' => (is => 'ro', required => 1, handles => [qw(process apply)]); +has 'model' => (is => 'ro', required => 1); 1; diff --git a/resources/icons/cross_focus.svg b/resources/icons/cross_focus.svg new file mode 100644 index 0000000000..ed004099ff --- /dev/null +++ b/resources/icons/cross_focus.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + diff --git a/resources/icons/drop_to_bed.svg b/resources/icons/drop_to_bed.svg new file mode 100644 index 0000000000..832b1b1bd6 --- /dev/null +++ b/resources/icons/drop_to_bed.svg @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/resources/icons/edit_layers_all.svg b/resources/icons/edit_layers_all.svg new file mode 100644 index 0000000000..fe4f26c52c --- /dev/null +++ b/resources/icons/edit_layers_all.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + diff --git a/resources/icons/edit_layers_some.svg b/resources/icons/edit_layers_some.svg new file mode 100644 index 0000000000..fb1e7f8b17 --- /dev/null +++ b/resources/icons/edit_layers_some.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/resources/icons/eye_closed.svg b/resources/icons/eye_closed.svg new file mode 100644 index 0000000000..127d53ca3b --- /dev/null +++ b/resources/icons/eye_closed.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/resources/icons/eye_open.svg b/resources/icons/eye_open.svg new file mode 100644 index 0000000000..a87cf3a83f --- /dev/null +++ b/resources/icons/eye_open.svg @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/resources/icons/lock_closed_f.svg b/resources/icons/lock_closed_f.svg new file mode 100644 index 0000000000..2920ea0aae --- /dev/null +++ b/resources/icons/lock_closed_f.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/resources/icons/lock_open_f.svg b/resources/icons/lock_open_f.svg new file mode 100644 index 0000000000..9440d9266d --- /dev/null +++ b/resources/icons/lock_open_f.svg @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/resources/icons/mirroring_off.svg b/resources/icons/mirroring_off.svg new file mode 100644 index 0000000000..4ed9da7482 --- /dev/null +++ b/resources/icons/mirroring_off.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/mirroring_on.svg b/resources/icons/mirroring_on.svg new file mode 100644 index 0000000000..8481246a3c --- /dev/null +++ b/resources/icons/mirroring_on.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/mirroring_transparent.png b/resources/icons/mirroring_transparent.png new file mode 100644 index 0000000000..841010fcc1 Binary files /dev/null and b/resources/icons/mirroring_transparent.png differ diff --git a/resources/icons/redo.svg b/resources/icons/redo.svg new file mode 100644 index 0000000000..9109779bbc --- /dev/null +++ b/resources/icons/redo.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/resources/icons/redo_toolbar.svg b/resources/icons/redo_toolbar.svg new file mode 100644 index 0000000000..ad073244f8 --- /dev/null +++ b/resources/icons/redo_toolbar.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/resources/icons/row.png b/resources/icons/row.png new file mode 100644 index 0000000000..18a6034fd5 Binary files /dev/null and b/resources/icons/row.png differ diff --git a/resources/icons/table.png b/resources/icons/table.png new file mode 100644 index 0000000000..3bc0bd32fc Binary files /dev/null and b/resources/icons/table.png differ diff --git a/resources/icons/undo_toolbar.svg b/resources/icons/undo_toolbar.svg new file mode 100644 index 0000000000..699ccd807f --- /dev/null +++ b/resources/icons/undo_toolbar.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/resources/localization/PrusaSlicer.pot b/resources/localization/PrusaSlicer.pot index 30c41434f8..bdad4a76d8 100644 --- a/resources/localization/PrusaSlicer.pot +++ b/resources/localization/PrusaSlicer.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-05-20 15:59+0200\n" +"POT-Creation-Date: 2019-08-06 09:54+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,46 +17,46 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: src/slic3r/GUI/AboutDialog.cpp:39 src/slic3r/GUI/AboutDialog.cpp:286 +#: src/slic3r/GUI/AboutDialog.cpp:39 src/slic3r/GUI/AboutDialog.cpp:289 msgid "Portions copyright" msgstr "" -#: src/slic3r/GUI/AboutDialog.cpp:122 src/slic3r/GUI/AboutDialog.cpp:251 +#: src/slic3r/GUI/AboutDialog.cpp:125 src/slic3r/GUI/AboutDialog.cpp:254 msgid "Copyright" msgstr "" #. TRN "Slic3r _is licensed under the_ License" -#: src/slic3r/GUI/AboutDialog.cpp:124 +#: src/slic3r/GUI/AboutDialog.cpp:127 msgid "" "License agreements of all following programs (libraries) are part of " "application license agreement" msgstr "" -#: src/slic3r/GUI/AboutDialog.cpp:194 +#: src/slic3r/GUI/AboutDialog.cpp:197 #, possible-c-format msgid "About %s" msgstr "" -#: src/slic3r/GUI/AboutDialog.cpp:226 src/slic3r/GUI/MainFrame.cpp:59 +#: src/slic3r/GUI/AboutDialog.cpp:229 src/slic3r/GUI/MainFrame.cpp:60 msgid "Version" msgstr "" #. TRN "Slic3r _is licensed under the_ License" -#: src/slic3r/GUI/AboutDialog.cpp:253 +#: src/slic3r/GUI/AboutDialog.cpp:256 msgid "is licensed under the" msgstr "" -#: src/slic3r/GUI/AboutDialog.cpp:254 +#: src/slic3r/GUI/AboutDialog.cpp:257 msgid "GNU Affero General Public License, version 3" msgstr "" -#: src/slic3r/GUI/AboutDialog.cpp:255 +#: src/slic3r/GUI/AboutDialog.cpp:258 msgid "" "PrusaSlicer is based on Slic3r by Alessandro Ranellucci and the RepRap " "community." msgstr "" -#: src/slic3r/GUI/AboutDialog.cpp:256 +#: src/slic3r/GUI/AboutDialog.cpp:259 msgid "" "Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, " "Petr Ledvina, Joseph Lenox, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and " @@ -64,8 +64,9 @@ msgid "" msgstr "" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:92 -#: src/slic3r/GUI/BackgroundSlicingProcess.cpp:408 -msgid "Copying of the temporary G-code to the output G-code failed" +msgid "" +"Copying of the temporary G-code to the output G-code failed. Maybe the SD " +"card is write locked?" msgstr "" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:93 @@ -86,115 +87,152 @@ msgstr "" msgid "Masked SLA file exported to %1%" msgstr "" +#: src/slic3r/GUI/BackgroundSlicingProcess.cpp:408 +msgid "Copying of the temporary G-code to the output G-code failed" +msgstr "" + #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:417 msgid "Scheduling upload to `%1%`. See Window -> Print Host Upload Queue" msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:60 +#: src/slic3r/GUI/BedShapeDialog.cpp:65 msgid "Shape" msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:68 +#: src/slic3r/GUI/BedShapeDialog.cpp:72 msgid "Rectangular" msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:72 -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:232 src/slic3r/GUI/Plater.cpp:136 -#: src/slic3r/GUI/Tab.cpp:2294 +#: src/slic3r/GUI/BedShapeDialog.cpp:76 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:391 src/slic3r/GUI/Plater.cpp:145 +#: src/slic3r/GUI/Tab.cpp:2469 msgid "Size" msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:73 +#: src/slic3r/GUI/BedShapeDialog.cpp:77 msgid "Size in X and Y of the rectangular plate." msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:79 +#: src/slic3r/GUI/BedShapeDialog.cpp:83 msgid "Origin" msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:80 +#: src/slic3r/GUI/BedShapeDialog.cpp:84 msgid "" "Distance of the 0,0 G-code coordinate from the front left corner of the " "rectangle." msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:84 +#: src/slic3r/GUI/BedShapeDialog.cpp:88 msgid "Circular" msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:87 src/slic3r/GUI/ConfigWizard.cpp:118 -#: src/slic3r/GUI/ConfigWizard.cpp:565 src/slic3r/GUI/ConfigWizard.cpp:579 -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:229 -#: src/slic3r/GUI/WipeTowerDialog.cpp:84 src/libslic3r/PrintConfig.cpp:60 -#: src/libslic3r/PrintConfig.cpp:67 src/libslic3r/PrintConfig.cpp:76 -#: src/libslic3r/PrintConfig.cpp:211 src/libslic3r/PrintConfig.cpp:286 -#: src/libslic3r/PrintConfig.cpp:294 src/libslic3r/PrintConfig.cpp:344 -#: src/libslic3r/PrintConfig.cpp:354 src/libslic3r/PrintConfig.cpp:474 -#: src/libslic3r/PrintConfig.cpp:485 src/libslic3r/PrintConfig.cpp:503 -#: src/libslic3r/PrintConfig.cpp:681 src/libslic3r/PrintConfig.cpp:1201 -#: src/libslic3r/PrintConfig.cpp:1262 src/libslic3r/PrintConfig.cpp:1280 -#: src/libslic3r/PrintConfig.cpp:1298 src/libslic3r/PrintConfig.cpp:1350 -#: src/libslic3r/PrintConfig.cpp:1360 src/libslic3r/PrintConfig.cpp:1481 -#: src/libslic3r/PrintConfig.cpp:1489 src/libslic3r/PrintConfig.cpp:1530 -#: src/libslic3r/PrintConfig.cpp:1538 src/libslic3r/PrintConfig.cpp:1548 -#: src/libslic3r/PrintConfig.cpp:1556 src/libslic3r/PrintConfig.cpp:1564 -#: src/libslic3r/PrintConfig.cpp:1647 src/libslic3r/PrintConfig.cpp:1863 -#: src/libslic3r/PrintConfig.cpp:1933 src/libslic3r/PrintConfig.cpp:1967 -#: src/libslic3r/PrintConfig.cpp:2160 src/libslic3r/PrintConfig.cpp:2167 -#: src/libslic3r/PrintConfig.cpp:2174 src/libslic3r/PrintConfig.cpp:2204 -#: src/libslic3r/PrintConfig.cpp:2214 src/libslic3r/PrintConfig.cpp:2224 -#: src/libslic3r/PrintConfig.cpp:2332 src/libslic3r/PrintConfig.cpp:2407 -#: src/libslic3r/PrintConfig.cpp:2416 src/libslic3r/PrintConfig.cpp:2425 -#: src/libslic3r/PrintConfig.cpp:2435 src/libslic3r/PrintConfig.cpp:2479 -#: src/libslic3r/PrintConfig.cpp:2489 src/libslic3r/PrintConfig.cpp:2508 -#: src/libslic3r/PrintConfig.cpp:2518 src/libslic3r/PrintConfig.cpp:2527 -#: src/libslic3r/PrintConfig.cpp:2545 src/libslic3r/PrintConfig.cpp:2560 -#: src/libslic3r/PrintConfig.cpp:2574 src/libslic3r/PrintConfig.cpp:2587 -#: src/libslic3r/PrintConfig.cpp:2597 +#: src/slic3r/GUI/BedShapeDialog.cpp:91 src/slic3r/GUI/ConfigWizard.cpp:118 +#: src/slic3r/GUI/ConfigWizard.cpp:571 src/slic3r/GUI/ConfigWizard.cpp:585 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:388 +#: src/slic3r/GUI/WipeTowerDialog.cpp:84 src/slic3r/GUI/wxExtensions.cpp:486 +#: src/libslic3r/PrintConfig.cpp:70 src/libslic3r/PrintConfig.cpp:77 +#: src/libslic3r/PrintConfig.cpp:86 src/libslic3r/PrintConfig.cpp:220 +#: src/libslic3r/PrintConfig.cpp:295 src/libslic3r/PrintConfig.cpp:303 +#: src/libslic3r/PrintConfig.cpp:353 src/libslic3r/PrintConfig.cpp:363 +#: src/libslic3r/PrintConfig.cpp:488 src/libslic3r/PrintConfig.cpp:499 +#: src/libslic3r/PrintConfig.cpp:517 src/libslic3r/PrintConfig.cpp:695 +#: src/libslic3r/PrintConfig.cpp:1215 src/libslic3r/PrintConfig.cpp:1276 +#: src/libslic3r/PrintConfig.cpp:1294 src/libslic3r/PrintConfig.cpp:1312 +#: src/libslic3r/PrintConfig.cpp:1364 src/libslic3r/PrintConfig.cpp:1374 +#: src/libslic3r/PrintConfig.cpp:1495 src/libslic3r/PrintConfig.cpp:1503 +#: src/libslic3r/PrintConfig.cpp:1544 src/libslic3r/PrintConfig.cpp:1552 +#: src/libslic3r/PrintConfig.cpp:1562 src/libslic3r/PrintConfig.cpp:1570 +#: src/libslic3r/PrintConfig.cpp:1578 src/libslic3r/PrintConfig.cpp:1661 +#: src/libslic3r/PrintConfig.cpp:1878 src/libslic3r/PrintConfig.cpp:1948 +#: src/libslic3r/PrintConfig.cpp:1982 src/libslic3r/PrintConfig.cpp:2176 +#: src/libslic3r/PrintConfig.cpp:2183 src/libslic3r/PrintConfig.cpp:2190 +#: src/libslic3r/PrintConfig.cpp:2220 src/libslic3r/PrintConfig.cpp:2230 +#: src/libslic3r/PrintConfig.cpp:2240 src/libslic3r/PrintConfig.cpp:2403 +#: src/libslic3r/PrintConfig.cpp:2478 src/libslic3r/PrintConfig.cpp:2487 +#: src/libslic3r/PrintConfig.cpp:2496 src/libslic3r/PrintConfig.cpp:2506 +#: src/libslic3r/PrintConfig.cpp:2550 src/libslic3r/PrintConfig.cpp:2560 +#: src/libslic3r/PrintConfig.cpp:2572 src/libslic3r/PrintConfig.cpp:2592 +#: src/libslic3r/PrintConfig.cpp:2602 src/libslic3r/PrintConfig.cpp:2613 +#: src/libslic3r/PrintConfig.cpp:2631 src/libslic3r/PrintConfig.cpp:2646 +#: src/libslic3r/PrintConfig.cpp:2660 src/libslic3r/PrintConfig.cpp:2673 +#: src/libslic3r/PrintConfig.cpp:2683 src/libslic3r/PrintConfig.cpp:2704 +#: src/libslic3r/PrintConfig.cpp:2715 src/libslic3r/PrintConfig.cpp:2725 +#: src/libslic3r/PrintConfig.cpp:2735 msgid "mm" msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:88 src/libslic3r/PrintConfig.cpp:678 +#: src/slic3r/GUI/BedShapeDialog.cpp:92 src/libslic3r/PrintConfig.cpp:692 msgid "Diameter" msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:89 +#: src/slic3r/GUI/BedShapeDialog.cpp:93 msgid "" "Diameter of the print bed. It is assumed that origin (0,0) is located in the " "center." msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:93 src/slic3r/GUI/GUI_Preview.cpp:245 +#: src/slic3r/GUI/BedShapeDialog.cpp:97 src/slic3r/GUI/GUI_Preview.cpp:246 #: src/libslic3r/GCode/PreviewData.cpp:175 msgid "Custom" msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:97 +#: src/slic3r/GUI/BedShapeDialog.cpp:101 msgid "Load shape from STL..." msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:143 +#: src/slic3r/GUI/BedShapeDialog.cpp:154 msgid "Settings" msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:316 -msgid "Choose a file to import bed shape from (STL/OBJ/AMF/3MF/PRUSA):" +#: src/slic3r/GUI/BedShapeDialog.cpp:171 +msgid "Texture" msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:333 src/slic3r/GUI/GUI_ObjectList.cpp:1442 -msgid "Error!" +#: src/slic3r/GUI/BedShapeDialog.cpp:181 src/slic3r/GUI/BedShapeDialog.cpp:249 +msgid "Load..." msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:342 +#: src/slic3r/GUI/BedShapeDialog.cpp:189 src/slic3r/GUI/BedShapeDialog.cpp:257 +#: src/slic3r/GUI/Tab.cpp:3204 +msgid "Remove" +msgstr "" + +#: src/slic3r/GUI/BedShapeDialog.cpp:239 +msgid "Model" +msgstr "" + +#: src/slic3r/GUI/BedShapeDialog.cpp:464 +msgid "Choose an STL file to import bed shape from:" +msgstr "" + +#: src/slic3r/GUI/BedShapeDialog.cpp:471 src/slic3r/GUI/BedShapeDialog.cpp:520 +#: src/slic3r/GUI/BedShapeDialog.cpp:543 +msgid "Invalid file format." +msgstr "" + +#: src/slic3r/GUI/BedShapeDialog.cpp:482 +msgid "Error! Invalid model" +msgstr "" + +#: src/slic3r/GUI/BedShapeDialog.cpp:490 msgid "The selected file contains no geometry." msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:346 +#: src/slic3r/GUI/BedShapeDialog.cpp:494 msgid "" "The selected file contains several disjoint areas. This is not supported." msgstr "" -#: src/slic3r/GUI/BedShapeDialog.hpp:45 src/slic3r/GUI/ConfigWizard.cpp:530 +#: src/slic3r/GUI/BedShapeDialog.cpp:509 +msgid "Choose a file to import bed texture from (PNG/SVG):" +msgstr "" + +#: src/slic3r/GUI/BedShapeDialog.cpp:532 +msgid "Choose an STL file to import bed model from:" +msgstr "" + +#: src/slic3r/GUI/BedShapeDialog.hpp:59 src/slic3r/GUI/ConfigWizard.cpp:530 msgid "Bed Shape" msgstr "" @@ -268,7 +306,7 @@ msgstr "" msgid "slic3r version" msgstr "" -#: src/slic3r/GUI/ConfigSnapshotDialog.cpp:46 src/slic3r/GUI/Preset.cpp:1282 +#: src/slic3r/GUI/ConfigSnapshotDialog.cpp:46 src/slic3r/GUI/Preset.cpp:1307 msgid "print" msgstr "" @@ -276,11 +314,11 @@ msgstr "" msgid "filaments" msgstr "" -#: src/slic3r/GUI/ConfigSnapshotDialog.cpp:48 src/slic3r/GUI/Preset.cpp:1286 +#: src/slic3r/GUI/ConfigSnapshotDialog.cpp:48 src/slic3r/GUI/Preset.cpp:1311 msgid "printer" msgstr "" -#: src/slic3r/GUI/ConfigSnapshotDialog.cpp:52 src/slic3r/GUI/Tab.cpp:934 +#: src/slic3r/GUI/ConfigSnapshotDialog.cpp:52 src/slic3r/GUI/Tab.cpp:939 msgid "vendor" msgstr "" @@ -329,11 +367,11 @@ msgstr "" msgid "All standard" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:189 src/slic3r/GUI/Tab.cpp:3038 +#: src/slic3r/GUI/ConfigWizard.cpp:189 src/slic3r/GUI/Tab.cpp:3254 msgid "All" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:190 src/slic3r/GUI/Plater.cpp:432 +#: src/slic3r/GUI/ConfigWizard.cpp:190 src/slic3r/GUI/Plater.cpp:470 #: src/libslic3r/GCode/PreviewData.cpp:162 msgid "None" msgstr "" @@ -352,7 +390,7 @@ msgstr "" msgid "Welcome" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:304 src/slic3r/GUI/GUI_App.cpp:713 +#: src/slic3r/GUI/ConfigWizard.cpp:304 src/slic3r/GUI/GUI_App.cpp:747 #, possible-c-format msgid "Run %s" msgstr "" @@ -399,7 +437,7 @@ msgstr "" msgid "Updates" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:415 src/slic3r/GUI/Preferences.cpp:61 +#: src/slic3r/GUI/ConfigWizard.cpp:415 src/slic3r/GUI/Preferences.cpp:69 msgid "Check for application updates" msgstr "" @@ -412,7 +450,7 @@ msgid "" "notification mechanisms, no automatic installation is done." msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:425 src/slic3r/GUI/Preferences.cpp:69 +#: src/slic3r/GUI/ConfigWizard.cpp:425 src/slic3r/GUI/Preferences.cpp:77 msgid "Update built-in Presets automatically" msgstr "" @@ -450,7 +488,7 @@ msgstr "" msgid "Firmware Type" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:492 src/slic3r/GUI/Tab.cpp:1957 +#: src/slic3r/GUI/ConfigWizard.cpp:492 src/slic3r/GUI/Tab.cpp:2100 msgid "Firmware" msgstr "" @@ -466,180 +504,183 @@ msgstr "" msgid "Set the shape of your printer's bed." msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:547 +#: src/slic3r/GUI/ConfigWizard.cpp:553 msgid "Filament and Nozzle Diameters" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:547 +#: src/slic3r/GUI/ConfigWizard.cpp:553 msgid "Print Diameters" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:561 +#: src/slic3r/GUI/ConfigWizard.cpp:567 msgid "Enter the diameter of your printer's hot end nozzle." msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:564 +#: src/slic3r/GUI/ConfigWizard.cpp:570 msgid "Nozzle Diameter:" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:574 +#: src/slic3r/GUI/ConfigWizard.cpp:580 msgid "Enter the diameter of your filament." msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:575 +#: src/slic3r/GUI/ConfigWizard.cpp:581 msgid "" "Good precision is required, so use a caliper and do multiple measurements " "along the filament, then compute the average." msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:578 +#: src/slic3r/GUI/ConfigWizard.cpp:584 msgid "Filament Diameter:" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:612 +#: src/slic3r/GUI/ConfigWizard.cpp:618 msgid "Extruder and Bed Temperatures" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:612 +#: src/slic3r/GUI/ConfigWizard.cpp:618 msgid "Temperatures" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:628 +#: src/slic3r/GUI/ConfigWizard.cpp:634 msgid "Enter the temperature needed for extruding your filament." msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:629 +#: src/slic3r/GUI/ConfigWizard.cpp:635 msgid "A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS." msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:632 +#: src/slic3r/GUI/ConfigWizard.cpp:638 msgid "Extrusion Temperature:" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:633 src/slic3r/GUI/ConfigWizard.cpp:647 +#: src/slic3r/GUI/ConfigWizard.cpp:639 src/slic3r/GUI/ConfigWizard.cpp:653 msgid "°C" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:642 +#: src/slic3r/GUI/ConfigWizard.cpp:648 msgid "" "Enter the bed temperature needed for getting your filament to stick to your " "heated bed." msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:643 +#: src/slic3r/GUI/ConfigWizard.cpp:649 msgid "" "A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have " "no heated bed." msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:646 +#: src/slic3r/GUI/ConfigWizard.cpp:652 msgid "Bed Temperature:" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:1109 +#: src/slic3r/GUI/ConfigWizard.cpp:1115 msgid "Select all standard printers" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:1112 +#: src/slic3r/GUI/ConfigWizard.cpp:1118 msgid "< &Back" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:1113 +#: src/slic3r/GUI/ConfigWizard.cpp:1119 msgid "&Next >" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:1114 +#: src/slic3r/GUI/ConfigWizard.cpp:1120 msgid "&Finish" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:1115 src/slic3r/GUI/FirmwareDialog.cpp:147 -#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:37 -#: src/slic3r/GUI/ProgressStatusBar.cpp:28 +#: src/slic3r/GUI/ConfigWizard.cpp:1121 src/slic3r/GUI/FirmwareDialog.cpp:151 +#: src/slic3r/GUI/ProgressStatusBar.cpp:27 msgid "Cancel" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:1129 +#: src/slic3r/GUI/ConfigWizard.cpp:1135 msgid "Prusa FFF Technology Printers" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:1132 +#: src/slic3r/GUI/ConfigWizard.cpp:1138 msgid "Prusa MSLA Technology Printers" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:1201 +#: src/slic3r/GUI/ConfigWizard.cpp:1207 msgid "Configuration Assistant" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:1202 +#: src/slic3r/GUI/ConfigWizard.cpp:1208 msgid "Configuration &Assistant" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:1204 +#: src/slic3r/GUI/ConfigWizard.cpp:1210 msgid "Configuration Wizard" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:1205 +#: src/slic3r/GUI/ConfigWizard.cpp:1211 msgid "Configuration &Wizard" msgstr "" -#: src/slic3r/GUI/Field.cpp:117 +#: src/slic3r/GUI/Field.cpp:125 msgid "default value" msgstr "" -#: src/slic3r/GUI/Field.cpp:120 +#: src/slic3r/GUI/Field.cpp:128 msgid "parameter name" msgstr "" -#: src/slic3r/GUI/Field.cpp:148 +#: src/slic3r/GUI/Field.cpp:139 +msgid "N/A" +msgstr "" + +#: src/slic3r/GUI/Field.cpp:158 #, possible-c-format msgid "%s doesn't support percentage" msgstr "" -#: src/slic3r/GUI/Field.cpp:162 src/slic3r/GUI/Field.cpp:185 +#: src/slic3r/GUI/Field.cpp:174 src/slic3r/GUI/Field.cpp:197 msgid "Invalid numeric input." msgstr "" -#: src/slic3r/GUI/Field.cpp:167 +#: src/slic3r/GUI/Field.cpp:179 msgid "Input value is out of range" msgstr "" -#: src/slic3r/GUI/Field.cpp:193 +#: src/slic3r/GUI/Field.cpp:206 #, possible-c-format msgid "" -"Do you mean %d%% instead of %d %s?\n" -"Select YES if you want to change this value to %d%%, \n" -"or NO if you are sure that %d %s is a correct value." +"Do you mean %s%% instead of %s %s?\n" +"Select YES if you want to change this value to %s%%, \n" +"or NO if you are sure that %s %s is a correct value." msgstr "" -#: src/slic3r/GUI/Field.cpp:196 +#: src/slic3r/GUI/Field.cpp:209 msgid "Parameter validation" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:146 +#: src/slic3r/GUI/FirmwareDialog.cpp:150 msgid "Flash!" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:148 +#: src/slic3r/GUI/FirmwareDialog.cpp:152 msgid "Flashing in progress. Please do not disconnect the printer!" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:192 +#: src/slic3r/GUI/FirmwareDialog.cpp:199 msgid "Flashing failed" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:273 +#: src/slic3r/GUI/FirmwareDialog.cpp:282 msgid "Flashing succeeded!" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:274 +#: src/slic3r/GUI/FirmwareDialog.cpp:283 msgid "Flashing failed. Please see the avrdude log below." msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:275 +#: src/slic3r/GUI/FirmwareDialog.cpp:284 msgid "Flashing cancelled." msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:313 +#: src/slic3r/GUI/FirmwareDialog.cpp:332 #, possible-c-format msgid "" "This firmware hex file does not match the printer model.\n" @@ -650,13 +691,13 @@ msgid "" "Please only continue if you are sure this is the right thing to do." msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:400 src/slic3r/GUI/FirmwareDialog.cpp:436 +#: src/slic3r/GUI/FirmwareDialog.cpp:419 src/slic3r/GUI/FirmwareDialog.cpp:454 #, possible-c-format msgid "" "Multiple %s devices found. Please only connect one at a time for flashing." msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:417 +#: src/slic3r/GUI/FirmwareDialog.cpp:436 #, possible-c-format msgid "" "The %s device was not found.\n" @@ -664,947 +705,1198 @@ msgid "" "connector ..." msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:530 +#: src/slic3r/GUI/FirmwareDialog.cpp:548 #, possible-c-format msgid "The %s device could not have been found" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:608 +#: src/slic3r/GUI/FirmwareDialog.cpp:645 #, possible-c-format msgid "Error accessing port at %s: %s" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:610 +#: src/slic3r/GUI/FirmwareDialog.cpp:647 #, possible-c-format msgid "Error: %s" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:740 +#: src/slic3r/GUI/FirmwareDialog.cpp:777 msgid "Firmware flasher" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:765 +#: src/slic3r/GUI/FirmwareDialog.cpp:802 msgid "Firmware image:" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:768 src/slic3r/GUI/Tab.cpp:1718 -#: src/slic3r/GUI/Tab.cpp:1774 +#: src/slic3r/GUI/FirmwareDialog.cpp:805 src/slic3r/GUI/Tab.cpp:1824 +#: src/slic3r/GUI/Tab.cpp:1880 msgid "Browse" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:770 +#: src/slic3r/GUI/FirmwareDialog.cpp:807 msgid "Serial port:" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:772 +#: src/slic3r/GUI/FirmwareDialog.cpp:809 msgid "Autodetected" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:773 +#: src/slic3r/GUI/FirmwareDialog.cpp:810 msgid "Rescan" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:780 +#: src/slic3r/GUI/FirmwareDialog.cpp:817 msgid "Progress:" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:783 +#: src/slic3r/GUI/FirmwareDialog.cpp:820 msgid "Status:" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:784 +#: src/slic3r/GUI/FirmwareDialog.cpp:821 msgid "Ready" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:804 +#: src/slic3r/GUI/FirmwareDialog.cpp:841 msgid "Advanced: Output log" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:815 +#: src/slic3r/GUI/FirmwareDialog.cpp:852 #: src/slic3r/GUI/PrintHostDialogs.cpp:161 msgid "Close" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:863 +#: src/slic3r/GUI/FirmwareDialog.cpp:903 msgid "" "Are you sure you want to cancel firmware flashing?\n" "This could leave your printer in an unusable state!" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:864 +#: src/slic3r/GUI/FirmwareDialog.cpp:904 msgid "Confirmation" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:867 +#: src/slic3r/GUI/FirmwareDialog.cpp:907 msgid "Cancelling..." msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:720 +#: src/slic3r/GUI/GLCanvas3D.cpp:526 +msgid "Layers heights" +msgstr "" + +#: src/slic3r/GUI/GLCanvas3D.cpp:623 msgid "An object outside the print area was detected" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:721 +#: src/slic3r/GUI/GLCanvas3D.cpp:624 msgid "A toolpath outside the print area was detected" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:722 +#: src/slic3r/GUI/GLCanvas3D.cpp:625 msgid "SLA supports outside the print area were detected" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:723 +#: src/slic3r/GUI/GLCanvas3D.cpp:626 msgid "Some objects are not visible when editing supports" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:725 +#: src/slic3r/GUI/GLCanvas3D.cpp:628 msgid "" "An object outside the print area was detected\n" "Resolve the current problem to continue slicing" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:1694 -msgid "Last frame" +#: src/slic3r/GUI/GLCanvas3D.cpp:1711 +msgid "Mirror Object" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:1698 -msgid "ms" +#: src/slic3r/GUI/GLCanvas3D.cpp:2872 +msgid "Move Object" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:3434 +#: src/slic3r/GUI/GLCanvas3D.cpp:3389 src/slic3r/GUI/GLCanvas3D.cpp:3609 +#: src/slic3r/GUI/MainFrame.cpp:559 +msgid "Undo" +msgstr "" + +#: src/slic3r/GUI/GLCanvas3D.cpp:3389 src/slic3r/GUI/GLCanvas3D.cpp:3639 +#: src/slic3r/GUI/MainFrame.cpp:562 +msgid "Redo" +msgstr "" + +#: src/slic3r/GUI/GLCanvas3D.cpp:3395 +#, possible-c-format +msgid "%s Stack" +msgstr "" + +#: src/slic3r/GUI/GLCanvas3D.cpp:3413 +#, possible-c-format +msgid "%s %d Action" +msgstr "" + +#: src/slic3r/GUI/GLCanvas3D.cpp:3460 msgid "Add..." msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:3444 src/slic3r/GUI/GUI_ObjectList.cpp:1277 -#: src/slic3r/GUI/Plater.cpp:2994 src/slic3r/GUI/Plater.cpp:3013 -#: src/slic3r/GUI/Tab.cpp:2988 +#: src/slic3r/GUI/GLCanvas3D.cpp:3468 src/slic3r/GUI/GUI_ObjectList.cpp:1434 +#: src/slic3r/GUI/Plater.cpp:3467 src/slic3r/GUI/Plater.cpp:3486 +#: src/slic3r/GUI/Tab.cpp:3204 msgid "Delete" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:3455 src/slic3r/GUI/Plater.cpp:3375 +#: src/slic3r/GUI/GLCanvas3D.cpp:3477 src/slic3r/GUI/Plater.cpp:4075 msgid "Delete all" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:3466 src/slic3r/GUI/KBShortcutsDialog.cpp:134 +#: src/slic3r/GUI/GLCanvas3D.cpp:3486 src/slic3r/GUI/KBShortcutsDialog.cpp:134 +#: src/slic3r/GUI/Plater.cpp:2636 msgid "Arrange" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:3480 +#: src/slic3r/GUI/GLCanvas3D.cpp:3486 +msgid "Arrange selection" +msgstr "" + +#: src/slic3r/GUI/GLCanvas3D.cpp:3498 msgid "Copy" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:3491 +#: src/slic3r/GUI/GLCanvas3D.cpp:3507 msgid "Paste" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:3505 +#: src/slic3r/GUI/GLCanvas3D.cpp:3519 msgid "Add instance" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:3517 +#: src/slic3r/GUI/GLCanvas3D.cpp:3530 msgid "Remove instance" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:3532 +#: src/slic3r/GUI/GLCanvas3D.cpp:3543 msgid "Split to objects" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:3544 src/slic3r/GUI/GUI_ObjectList.cpp:1129 +#: src/slic3r/GUI/GLCanvas3D.cpp:3553 src/slic3r/GUI/GUI_ObjectList.cpp:1280 msgid "Split to parts" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:3559 +#: src/slic3r/GUI/GLCanvas3D.cpp:3566 msgid "Layers editing" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:35 -#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:195 -msgid "Rotate lower part upwards" +#: src/slic3r/GUI/GLCanvas3D.cpp:5623 +msgid "Selection-Add from rectangle" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:36 -#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:198 -msgid "Perform cut" +#: src/slic3r/GUI/GLCanvas3D.cpp:5642 +msgid "Selection-Remove from rectangle" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:43 -msgid "Cut object:" -msgstr "" - -#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:88 -#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:188 src/libslic3r/PrintConfig.cpp:3049 +#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:40 +#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:144 src/libslic3r/PrintConfig.cpp:3176 msgid "Cut" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:193 +#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:149 msgid "Keep upper part" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:194 +#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:150 msgid "Keep lower part" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp:32 +#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:151 +msgid "Rotate lower part upwards" +msgstr "" + +#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:154 +msgid "Perform cut" +msgstr "" + +#: src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp:45 msgid "Place on face" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoMove.cpp:52 +#: src/slic3r/GUI/Gizmos/GLGizmoMove.cpp:48 msgid "Move" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoMove.cpp:178 +#: src/slic3r/GUI/Gizmos/GLGizmoMove.cpp:177 msgid "Position (mm)" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoMove.cpp:178 +#: src/slic3r/GUI/Gizmos/GLGizmoMove.cpp:177 msgid "Displacement (mm)" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp:458 -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:305 -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:324 -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:342 -#: src/libslic3r/PrintConfig.cpp:3098 +#: src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp:449 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:466 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:485 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:503 +#: src/libslic3r/PrintConfig.cpp:3225 msgid "Rotate" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp:491 +#: src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp:482 msgid "Rotation (deg)" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoScale.cpp:53 -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:231 -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:325 -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:343 -#: src/libslic3r/PrintConfig.cpp:3113 +#: src/slic3r/GUI/Gizmos/GLGizmoScale.cpp:47 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:390 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:486 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:504 +#: src/libslic3r/PrintConfig.cpp:3240 msgid "Scale" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoScale.cpp:291 +#: src/slic3r/GUI/Gizmos/GLGizmoScale.cpp:292 msgid "Scale (%)" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:840 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:44 msgid "Head diameter" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:856 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:45 msgid "Lock supports under new islands" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:860 -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1249 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:46 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1427 msgid "Remove selected points" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:864 -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:921 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:47 msgid "Remove all points" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:869 -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1252 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:48 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1430 msgid "Apply changes" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:874 -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1253 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:49 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1431 msgid "Discard changes" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:881 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:50 msgid "Minimal points distance" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:882 -#: src/libslic3r/PrintConfig.cpp:2534 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:51 +#: src/libslic3r/PrintConfig.cpp:2620 msgid "Support points density" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:911 -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1255 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:52 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1433 msgid "Auto-generate points" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:917 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:53 msgid "Manual editing" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:934 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:54 msgid "Clipping of view" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:935 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:55 msgid "Reset direction" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1007 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:531 +msgid "Add support point" +msgstr "" + +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:720 +msgid "Delete support point" +msgstr "" + +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:925 +msgid "Change point head diameter" +msgstr "" + +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:991 +msgid "Support parameter change" +msgstr "" + +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1099 msgid "SLA Support Points" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1034 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1138 msgid "Do you want to save your manually edited support points?" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1035 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1139 msgid "Save changes?" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1178 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1183 +msgid "Move support point" +msgstr "" + +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1282 +msgid "Support points edit" +msgstr "" + +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1333 msgid "" "Autogeneration will erase all manually edited points.\n" "\n" "Are you sure you want to do it?\n" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1180 src/slic3r/GUI/GUI.cpp:283 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1335 src/slic3r/GUI/GUI.cpp:289 #: src/slic3r/GUI/WipeTowerDialog.cpp:44 src/slic3r/GUI/WipeTowerDialog.cpp:328 msgid "Warning" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1212 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1338 +msgid "Autogenerate support points" +msgstr "" + +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1390 msgid "SLA gizmo keyboard shortcuts" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1223 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1401 msgid "Note: some shortcuts work in (non)editing mode only." msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1241 -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1244 -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1245 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1419 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1422 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1423 msgid "Left click" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1241 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1419 msgid "Add point" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1242 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1420 msgid "Right click" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1242 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1420 msgid "Remove point" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1243 -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1246 -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1247 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1421 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1424 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1425 msgid "Drag" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1243 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1421 msgid "Move point" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1244 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1422 msgid "Add point to selection" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1245 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1423 msgid "Remove point from selection" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1246 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1424 msgid "Select by rectangle" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1247 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1425 msgid "Deselect by rectangle" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1248 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1426 msgid "Select all points" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1250 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1428 msgid "Mouse wheel" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1250 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1428 msgid "Move clipping plane" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1251 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1429 msgid "Reset clipping plane" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1254 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1432 msgid "Switch to editing mode" msgstr "" -#: src/slic3r/GUI/GUI.cpp:142 src/slic3r/GUI/Tab.cpp:2847 +#: src/slic3r/GUI/GUI.cpp:141 src/slic3r/GUI/Tab.cpp:3063 msgid "It's impossible to print multi-part object(s) with SLA technology." msgstr "" -#: src/slic3r/GUI/GUI.cpp:143 +#: src/slic3r/GUI/GUI.cpp:142 msgid "Please check and fix your object list." msgstr "" -#: src/slic3r/GUI/GUI.cpp:144 src/slic3r/GUI/Tab.cpp:2849 +#: src/slic3r/GUI/GUI.cpp:143 src/slic3r/GUI/Plater.cpp:2213 +#: src/slic3r/GUI/Tab.cpp:3065 msgid "Attention!" msgstr "" -#: src/slic3r/GUI/GUI.cpp:277 +#: src/slic3r/GUI/GUI.cpp:283 msgid "Notice" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:401 +#: src/slic3r/GUI/GUI_App.cpp:435 msgid "Changing of an application language" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:409 src/slic3r/GUI/GUI_App.cpp:418 +#: src/slic3r/GUI/GUI_App.cpp:443 src/slic3r/GUI/GUI_App.cpp:452 msgid "Recreating" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:422 +#: src/slic3r/GUI/GUI_App.cpp:456 msgid "Loading of current presets" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:430 +#: src/slic3r/GUI/GUI_App.cpp:464 msgid "Loading of a mode view" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:510 +#: src/slic3r/GUI/GUI_App.cpp:544 msgid "Choose one file (3MF/AMF):" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:522 +#: src/slic3r/GUI/GUI_App.cpp:556 msgid "Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:564 +#: src/slic3r/GUI/GUI_App.cpp:598 msgid "Select the language" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:565 +#: src/slic3r/GUI/GUI_App.cpp:599 msgid "Language" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:716 +#: src/slic3r/GUI/GUI_App.cpp:750 msgid "&Configuration Snapshots" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:716 +#: src/slic3r/GUI/GUI_App.cpp:750 msgid "Inspect / activate configuration snapshots" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:717 +#: src/slic3r/GUI/GUI_App.cpp:751 msgid "Take Configuration &Snapshot" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:717 +#: src/slic3r/GUI/GUI_App.cpp:751 msgid "Capture a configuration snapshot" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:720 +#: src/slic3r/GUI/GUI_App.cpp:754 msgid "&Preferences" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:726 +#: src/slic3r/GUI/GUI_App.cpp:760 msgid "Application preferences" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:729 src/slic3r/GUI/wxExtensions.cpp:2555 +#: src/slic3r/GUI/GUI_App.cpp:763 src/slic3r/GUI/wxExtensions.cpp:2882 msgid "Simple" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:729 +#: src/slic3r/GUI/GUI_App.cpp:763 msgid "Simple View Mode" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:730 src/slic3r/GUI/GUI_ObjectList.cpp:85 -#: src/slic3r/GUI/GUI_ObjectList.cpp:541 src/slic3r/GUI/Tab.cpp:1032 -#: src/slic3r/GUI/Tab.cpp:1047 src/slic3r/GUI/Tab.cpp:1145 -#: src/slic3r/GUI/Tab.cpp:1148 src/slic3r/GUI/Tab.cpp:1551 -#: src/slic3r/GUI/Tab.cpp:1977 src/slic3r/GUI/Tab.cpp:3492 -#: src/slic3r/GUI/wxExtensions.cpp:2556 src/libslic3r/PrintConfig.cpp:73 -#: src/libslic3r/PrintConfig.cpp:188 src/libslic3r/PrintConfig.cpp:351 -#: src/libslic3r/PrintConfig.cpp:999 src/libslic3r/PrintConfig.cpp:2210 +#: src/slic3r/GUI/GUI_App.cpp:764 src/slic3r/GUI/GUI_ObjectList.cpp:93 +#: src/slic3r/GUI/GUI_ObjectList.cpp:567 src/slic3r/GUI/Tab.cpp:1037 +#: src/slic3r/GUI/Tab.cpp:1052 src/slic3r/GUI/Tab.cpp:1150 +#: src/slic3r/GUI/Tab.cpp:1153 src/slic3r/GUI/Tab.cpp:1649 +#: src/slic3r/GUI/Tab.cpp:2120 src/slic3r/GUI/Tab.cpp:3699 +#: src/slic3r/GUI/wxExtensions.cpp:2883 src/libslic3r/PrintConfig.cpp:83 +#: src/libslic3r/PrintConfig.cpp:197 src/libslic3r/PrintConfig.cpp:360 +#: src/libslic3r/PrintConfig.cpp:1013 src/libslic3r/PrintConfig.cpp:2226 msgid "Advanced" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:730 +#: src/slic3r/GUI/GUI_App.cpp:764 msgid "Advanced View Mode" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:731 src/slic3r/GUI/wxExtensions.cpp:2557 +#: src/slic3r/GUI/GUI_App.cpp:765 src/slic3r/GUI/wxExtensions.cpp:2884 msgid "Expert" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:731 +#: src/slic3r/GUI/GUI_App.cpp:765 msgid "Expert View Mode" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:736 +#: src/slic3r/GUI/GUI_App.cpp:770 msgid "Mode" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:736 +#: src/slic3r/GUI/GUI_App.cpp:770 #, possible-c-format msgid "%s View Mode" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:738 +#: src/slic3r/GUI/GUI_App.cpp:772 msgid "Change Application &Language" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:740 +#: src/slic3r/GUI/GUI_App.cpp:774 msgid "Flash printer &firmware" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:740 +#: src/slic3r/GUI/GUI_App.cpp:774 msgid "Upload a firmware image into an Arduino based printer" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:752 +#: src/slic3r/GUI/GUI_App.cpp:786 msgid "Taking configuration snapshot" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:752 +#: src/slic3r/GUI/GUI_App.cpp:786 msgid "Snapshot name" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:795 +#: src/slic3r/GUI/GUI_App.cpp:829 msgid "" "Switching the language will trigger application restart.\n" "You will lose content of the plater." msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:797 +#: src/slic3r/GUI/GUI_App.cpp:831 msgid "Do you want to proceed?" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:798 +#: src/slic3r/GUI/GUI_App.cpp:832 msgid "Language selection" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:817 +#: src/slic3r/GUI/GUI_App.cpp:855 msgid "&Configuration" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:837 +#: src/slic3r/GUI/GUI_App.cpp:877 msgid "The presets on the following tabs were modified" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:837 src/slic3r/GUI/Tab.cpp:2835 +#: src/slic3r/GUI/GUI_App.cpp:877 src/slic3r/GUI/Tab.cpp:3051 msgid "Discard changes and continue anyway?" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:838 +#: src/slic3r/GUI/GUI_App.cpp:880 msgid "Unsaved Presets" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:28 src/slic3r/GUI/GUI_ObjectList.cpp:77 -#: src/slic3r/GUI/GUI_ObjectList.cpp:533 src/libslic3r/PrintConfig.cpp:57 -#: src/libslic3r/PrintConfig.cpp:151 src/libslic3r/PrintConfig.cpp:382 -#: src/libslic3r/PrintConfig.cpp:439 src/libslic3r/PrintConfig.cpp:447 -#: src/libslic3r/PrintConfig.cpp:853 src/libslic3r/PrintConfig.cpp:1037 -#: src/libslic3r/PrintConfig.cpp:1340 src/libslic3r/PrintConfig.cpp:1406 -#: src/libslic3r/PrintConfig.cpp:1587 src/libslic3r/PrintConfig.cpp:2022 -#: src/libslic3r/PrintConfig.cpp:2079 +#: src/slic3r/GUI/GUI_ObjectList.cpp:30 src/slic3r/GUI/GUI_ObjectList.cpp:84 +#: src/slic3r/GUI/GUI_ObjectList.cpp:558 src/libslic3r/PrintConfig.cpp:67 +#: src/libslic3r/PrintConfig.cpp:160 src/libslic3r/PrintConfig.cpp:392 +#: src/libslic3r/PrintConfig.cpp:453 src/libslic3r/PrintConfig.cpp:461 +#: src/libslic3r/PrintConfig.cpp:867 src/libslic3r/PrintConfig.cpp:1051 +#: src/libslic3r/PrintConfig.cpp:1354 src/libslic3r/PrintConfig.cpp:1420 +#: src/libslic3r/PrintConfig.cpp:1601 src/libslic3r/PrintConfig.cpp:2037 +#: src/libslic3r/PrintConfig.cpp:2095 msgid "Layers and Perimeters" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:29 src/slic3r/GUI/GUI_ObjectList.cpp:78 -#: src/slic3r/GUI/GUI_ObjectList.cpp:534 src/slic3r/GUI/Plater.cpp:446 -#: src/slic3r/GUI/Tab.cpp:1036 src/slic3r/GUI/Tab.cpp:1037 -#: src/slic3r/GUI/Tab.cpp:1395 src/libslic3r/PrintConfig.cpp:168 -#: src/libslic3r/PrintConfig.cpp:390 src/libslic3r/PrintConfig.cpp:740 -#: src/libslic3r/PrintConfig.cpp:754 src/libslic3r/PrintConfig.cpp:791 -#: src/libslic3r/PrintConfig.cpp:944 src/libslic3r/PrintConfig.cpp:954 -#: src/libslic3r/PrintConfig.cpp:972 src/libslic3r/PrintConfig.cpp:990 -#: src/libslic3r/PrintConfig.cpp:1009 src/libslic3r/PrintConfig.cpp:1694 -#: src/libslic3r/PrintConfig.cpp:1711 +#: src/slic3r/GUI/GUI_ObjectList.cpp:31 src/slic3r/GUI/GUI_ObjectList.cpp:85 +#: src/slic3r/GUI/GUI_ObjectList.cpp:559 src/slic3r/GUI/Plater.cpp:498 +#: src/slic3r/GUI/Tab.cpp:1041 src/slic3r/GUI/Tab.cpp:1042 +#: src/slic3r/GUI/Tab.cpp:1394 src/libslic3r/PrintConfig.cpp:177 +#: src/libslic3r/PrintConfig.cpp:400 src/libslic3r/PrintConfig.cpp:420 +#: src/libslic3r/PrintConfig.cpp:754 src/libslic3r/PrintConfig.cpp:768 +#: src/libslic3r/PrintConfig.cpp:805 src/libslic3r/PrintConfig.cpp:958 +#: src/libslic3r/PrintConfig.cpp:968 src/libslic3r/PrintConfig.cpp:986 +#: src/libslic3r/PrintConfig.cpp:1004 src/libslic3r/PrintConfig.cpp:1023 +#: src/libslic3r/PrintConfig.cpp:1708 src/libslic3r/PrintConfig.cpp:1725 msgid "Infill" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:30 src/slic3r/GUI/GUI_ObjectList.cpp:79 -#: src/slic3r/GUI/GUI_ObjectList.cpp:535 src/slic3r/GUI/GUI_Preview.cpp:242 -#: src/slic3r/GUI/Tab.cpp:1065 src/slic3r/GUI/Tab.cpp:1066 -#: src/libslic3r/PrintConfig.cpp:335 src/libslic3r/PrintConfig.cpp:1467 -#: src/libslic3r/PrintConfig.cpp:1815 src/libslic3r/PrintConfig.cpp:1821 -#: src/libslic3r/PrintConfig.cpp:1829 src/libslic3r/PrintConfig.cpp:1841 -#: src/libslic3r/PrintConfig.cpp:1851 src/libslic3r/PrintConfig.cpp:1859 -#: src/libslic3r/PrintConfig.cpp:1874 src/libslic3r/PrintConfig.cpp:1895 -#: src/libslic3r/PrintConfig.cpp:1906 src/libslic3r/PrintConfig.cpp:1922 -#: src/libslic3r/PrintConfig.cpp:1931 src/libslic3r/PrintConfig.cpp:1940 -#: src/libslic3r/PrintConfig.cpp:1951 src/libslic3r/PrintConfig.cpp:1965 -#: src/libslic3r/PrintConfig.cpp:1973 src/libslic3r/PrintConfig.cpp:1974 -#: src/libslic3r/PrintConfig.cpp:1983 src/libslic3r/PrintConfig.cpp:1991 -#: src/libslic3r/PrintConfig.cpp:2005 src/libslic3r/GCode/PreviewData.cpp:172 +#: src/slic3r/GUI/GUI_ObjectList.cpp:32 src/slic3r/GUI/GUI_ObjectList.cpp:86 +#: src/slic3r/GUI/GUI_ObjectList.cpp:560 src/slic3r/GUI/GUI_Preview.cpp:243 +#: src/slic3r/GUI/Tab.cpp:1070 src/slic3r/GUI/Tab.cpp:1071 +#: src/libslic3r/PrintConfig.cpp:344 src/libslic3r/PrintConfig.cpp:1481 +#: src/libslic3r/PrintConfig.cpp:1830 src/libslic3r/PrintConfig.cpp:1836 +#: src/libslic3r/PrintConfig.cpp:1844 src/libslic3r/PrintConfig.cpp:1856 +#: src/libslic3r/PrintConfig.cpp:1866 src/libslic3r/PrintConfig.cpp:1874 +#: src/libslic3r/PrintConfig.cpp:1889 src/libslic3r/PrintConfig.cpp:1910 +#: src/libslic3r/PrintConfig.cpp:1921 src/libslic3r/PrintConfig.cpp:1937 +#: src/libslic3r/PrintConfig.cpp:1946 src/libslic3r/PrintConfig.cpp:1955 +#: src/libslic3r/PrintConfig.cpp:1966 src/libslic3r/PrintConfig.cpp:1980 +#: src/libslic3r/PrintConfig.cpp:1988 src/libslic3r/PrintConfig.cpp:1989 +#: src/libslic3r/PrintConfig.cpp:1998 src/libslic3r/PrintConfig.cpp:2006 +#: src/libslic3r/PrintConfig.cpp:2020 src/libslic3r/GCode/PreviewData.cpp:172 msgid "Support material" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:33 src/slic3r/GUI/GUI_ObjectList.cpp:81 -#: src/slic3r/GUI/GUI_ObjectList.cpp:537 src/slic3r/GUI/Tab.cpp:1125 -#: src/slic3r/GUI/Tab.cpp:1881 src/libslic3r/PrintConfig.cpp:457 -#: src/libslic3r/PrintConfig.cpp:965 src/libslic3r/PrintConfig.cpp:1375 -#: src/libslic3r/PrintConfig.cpp:1703 src/libslic3r/PrintConfig.cpp:1887 -#: src/libslic3r/PrintConfig.cpp:1913 src/libslic3r/PrintConfig.cpp:2186 -#: src/libslic3r/PrintConfig.cpp:2194 -msgid "Extruders" +#: src/slic3r/GUI/GUI_ObjectList.cpp:35 src/slic3r/GUI/GUI_ObjectList.cpp:90 +#: src/slic3r/GUI/GUI_ObjectList.cpp:564 src/libslic3r/PrintConfig.cpp:2202 +#: src/libslic3r/PrintConfig.cpp:2210 +msgid "Wipe options" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:39 +#: src/slic3r/GUI/GUI_ObjectList.cpp:41 msgid "Pad and Support" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:45 +#: src/slic3r/GUI/GUI_ObjectList.cpp:47 msgid "Add part" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:46 +#: src/slic3r/GUI/GUI_ObjectList.cpp:48 msgid "Add modifier" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:47 +#: src/slic3r/GUI/GUI_ObjectList.cpp:49 msgid "Add support enforcer" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:48 +#: src/slic3r/GUI/GUI_ObjectList.cpp:50 msgid "Add support blocker" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:80 src/slic3r/GUI/GUI_ObjectList.cpp:536 -#: src/slic3r/GUI/GUI_Preview.cpp:221 src/slic3r/GUI/Tab.cpp:1090 -#: src/libslic3r/PrintConfig.cpp:200 src/libslic3r/PrintConfig.cpp:427 -#: src/libslic3r/PrintConfig.cpp:882 src/libslic3r/PrintConfig.cpp:1010 -#: src/libslic3r/PrintConfig.cpp:1396 src/libslic3r/PrintConfig.cpp:1633 -#: src/libslic3r/PrintConfig.cpp:1682 src/libslic3r/PrintConfig.cpp:1733 -#: src/libslic3r/PrintConfig.cpp:2064 +#: src/slic3r/GUI/GUI_ObjectList.cpp:87 src/slic3r/GUI/GUI_ObjectList.cpp:561 +#: src/slic3r/GUI/GUI_Preview.cpp:222 src/slic3r/GUI/Tab.cpp:1095 +#: src/libslic3r/PrintConfig.cpp:209 src/libslic3r/PrintConfig.cpp:441 +#: src/libslic3r/PrintConfig.cpp:896 src/libslic3r/PrintConfig.cpp:1024 +#: src/libslic3r/PrintConfig.cpp:1410 src/libslic3r/PrintConfig.cpp:1647 +#: src/libslic3r/PrintConfig.cpp:1696 src/libslic3r/PrintConfig.cpp:1747 +#: src/libslic3r/PrintConfig.cpp:2080 msgid "Speed" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:82 src/slic3r/GUI/GUI_ObjectList.cpp:538 -#: src/libslic3r/PrintConfig.cpp:417 src/libslic3r/PrintConfig.cpp:524 -#: src/libslic3r/PrintConfig.cpp:841 src/libslic3r/PrintConfig.cpp:973 -#: src/libslic3r/PrintConfig.cpp:1384 src/libslic3r/PrintConfig.cpp:1723 -#: src/libslic3r/PrintConfig.cpp:1896 src/libslic3r/PrintConfig.cpp:2053 +#: src/slic3r/GUI/GUI_ObjectList.cpp:88 src/slic3r/GUI/GUI_ObjectList.cpp:562 +#: src/slic3r/GUI/Tab.cpp:1130 src/slic3r/GUI/Tab.cpp:1997 +#: src/libslic3r/PrintConfig.cpp:471 src/libslic3r/PrintConfig.cpp:979 +#: src/libslic3r/PrintConfig.cpp:1389 src/libslic3r/PrintConfig.cpp:1717 +#: src/libslic3r/PrintConfig.cpp:1902 src/libslic3r/PrintConfig.cpp:1928 +msgid "Extruders" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:89 src/slic3r/GUI/GUI_ObjectList.cpp:563 +#: src/libslic3r/PrintConfig.cpp:431 src/libslic3r/PrintConfig.cpp:538 +#: src/libslic3r/PrintConfig.cpp:855 src/libslic3r/PrintConfig.cpp:987 +#: src/libslic3r/PrintConfig.cpp:1398 src/libslic3r/PrintConfig.cpp:1737 +#: src/libslic3r/PrintConfig.cpp:1911 src/libslic3r/PrintConfig.cpp:2069 msgid "Extrusion Width" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:87 src/slic3r/GUI/GUI_ObjectList.cpp:543 -#: src/slic3r/GUI/Plater.cpp:428 src/slic3r/GUI/Tab.cpp:3454 -#: src/slic3r/GUI/Tab.cpp:3455 src/libslic3r/PrintConfig.cpp:2398 -#: src/libslic3r/PrintConfig.cpp:2405 src/libslic3r/PrintConfig.cpp:2414 -#: src/libslic3r/PrintConfig.cpp:2423 src/libslic3r/PrintConfig.cpp:2433 -#: src/libslic3r/PrintConfig.cpp:2459 src/libslic3r/PrintConfig.cpp:2466 -#: src/libslic3r/PrintConfig.cpp:2477 src/libslic3r/PrintConfig.cpp:2487 -#: src/libslic3r/PrintConfig.cpp:2496 src/libslic3r/PrintConfig.cpp:2506 -#: src/libslic3r/PrintConfig.cpp:2515 src/libslic3r/PrintConfig.cpp:2525 -#: src/libslic3r/PrintConfig.cpp:2535 src/libslic3r/PrintConfig.cpp:2543 +#: src/slic3r/GUI/GUI_ObjectList.cpp:95 src/slic3r/GUI/GUI_ObjectList.cpp:569 +#: src/slic3r/GUI/Plater.cpp:466 src/slic3r/GUI/Tab.cpp:3655 +#: src/slic3r/GUI/Tab.cpp:3656 src/libslic3r/PrintConfig.cpp:2469 +#: src/libslic3r/PrintConfig.cpp:2476 src/libslic3r/PrintConfig.cpp:2485 +#: src/libslic3r/PrintConfig.cpp:2494 src/libslic3r/PrintConfig.cpp:2504 +#: src/libslic3r/PrintConfig.cpp:2530 src/libslic3r/PrintConfig.cpp:2537 +#: src/libslic3r/PrintConfig.cpp:2548 src/libslic3r/PrintConfig.cpp:2558 +#: src/libslic3r/PrintConfig.cpp:2567 src/libslic3r/PrintConfig.cpp:2580 +#: src/libslic3r/PrintConfig.cpp:2590 src/libslic3r/PrintConfig.cpp:2599 +#: src/libslic3r/PrintConfig.cpp:2609 src/libslic3r/PrintConfig.cpp:2621 +#: src/libslic3r/PrintConfig.cpp:2629 msgid "Supports" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:88 src/slic3r/GUI/GUI_ObjectList.cpp:544 -#: src/slic3r/GUI/Tab.cpp:3482 src/slic3r/GUI/Tab.cpp:3483 -#: src/libslic3r/PrintConfig.cpp:2551 src/libslic3r/PrintConfig.cpp:2558 -#: src/libslic3r/PrintConfig.cpp:2572 src/libslic3r/PrintConfig.cpp:2582 -#: src/libslic3r/PrintConfig.cpp:2595 src/libslic3r/PrintConfig.cpp:2604 +#: src/slic3r/GUI/GUI_ObjectList.cpp:96 src/slic3r/GUI/GUI_ObjectList.cpp:570 +#: src/slic3r/GUI/Tab.cpp:3684 src/slic3r/GUI/Tab.cpp:3685 +#: src/libslic3r/PrintConfig.cpp:2637 src/libslic3r/PrintConfig.cpp:2644 +#: src/libslic3r/PrintConfig.cpp:2658 src/libslic3r/PrintConfig.cpp:2668 +#: src/libslic3r/PrintConfig.cpp:2681 src/libslic3r/PrintConfig.cpp:2690 +#: src/libslic3r/PrintConfig.cpp:2701 src/libslic3r/PrintConfig.cpp:2712 +#: src/libslic3r/PrintConfig.cpp:2722 src/libslic3r/PrintConfig.cpp:2732 msgid "Pad" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:205 +#: src/slic3r/GUI/GUI_ObjectList.cpp:217 msgid "Name" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:259 +#: src/slic3r/GUI/GUI_ObjectList.cpp:271 #, possible-c-format msgid "Auto-repaired (%d errors):\n" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:266 +#: src/slic3r/GUI/GUI_ObjectList.cpp:278 msgid "degenerate facets" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:267 +#: src/slic3r/GUI/GUI_ObjectList.cpp:279 msgid "edges fixed" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:268 +#: src/slic3r/GUI/GUI_ObjectList.cpp:280 msgid "facets removed" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:269 +#: src/slic3r/GUI/GUI_ObjectList.cpp:281 msgid "facets added" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:270 +#: src/slic3r/GUI/GUI_ObjectList.cpp:282 msgid "facets reversed" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:271 +#: src/slic3r/GUI/GUI_ObjectList.cpp:283 msgid "backwards edges" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:279 +#: src/slic3r/GUI/GUI_ObjectList.cpp:291 msgid "Right button click the icon to fix STL through Netfabb" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:308 +#: src/slic3r/GUI/GUI_ObjectList.cpp:325 msgid "Right button click the icon to change the object settings" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:359 src/slic3r/GUI/GUI_ObjectList.cpp:380 -#: src/slic3r/GUI/GUI_ObjectList.cpp:392 src/slic3r/GUI/GUI_ObjectList.cpp:2850 -#: src/slic3r/GUI/GUI_ObjectList.cpp:2860 -#: src/slic3r/GUI/GUI_ObjectList.cpp:2892 src/slic3r/GUI/wxExtensions.cpp:543 -#: src/slic3r/GUI/wxExtensions.cpp:568 +#: src/slic3r/GUI/GUI_ObjectList.cpp:375 src/slic3r/GUI/GUI_ObjectList.cpp:396 +#: src/slic3r/GUI/GUI_ObjectList.cpp:408 src/slic3r/GUI/GUI_ObjectList.cpp:3508 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3518 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3550 src/slic3r/GUI/wxExtensions.cpp:576 +#: src/slic3r/GUI/wxExtensions.cpp:633 src/slic3r/GUI/wxExtensions.cpp:658 +#: src/slic3r/GUI/wxExtensions.cpp:794 msgid "default" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:364 src/slic3r/GUI/Tab.cpp:1515 -#: src/libslic3r/PrintConfig.cpp:456 +#: src/slic3r/GUI/GUI_ObjectList.cpp:380 src/slic3r/GUI/Tab.cpp:1613 +#: src/libslic3r/PrintConfig.cpp:470 msgid "Extruder" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:883 src/slic3r/GUI/GUI_ObjectList.cpp:1159 -#: src/slic3r/GUI/GUI_ObjectList.cpp:1165 -#: src/slic3r/GUI/GUI_ObjectList.cpp:1388 +#: src/slic3r/GUI/GUI_ObjectList.cpp:493 +msgid "Rename Object" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:493 +msgid "Rename Sub-object" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:934 src/slic3r/GUI/GUI_ObjectList.cpp:3346 +msgid "Instances to Separated Objects" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:952 +msgid "Remove Volume(s)" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1007 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1316 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1322 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1556 #, possible-c-format msgid "Quick Add Settings (%s)" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:946 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1077 msgid "Select showing settings" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1079 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1126 +msgid "Add Settings for Layers" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1127 +msgid "Add Settings for Sub-object" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1128 +msgid "Add Settings for Object" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1189 +msgid "Add Settings Bundle for Layers" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1190 +msgid "Add Settings Bundle for Sub-object" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1191 +msgid "Add Settings Bundle for Object" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1230 msgid "Load" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1084 -#: src/slic3r/GUI/GUI_ObjectList.cpp:1109 -#: src/slic3r/GUI/GUI_ObjectList.cpp:1112 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1235 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1260 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1263 msgid "Box" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1084 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1235 msgid "Cylinder" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1084 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1235 msgid "Sphere" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1084 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1235 msgid "Slab" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1138 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1287 +msgid "Edit Layers" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1295 msgid "Add settings" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1205 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1362 msgid "Change type" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1212 -#: src/slic3r/GUI/GUI_ObjectList.cpp:1342 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1369 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1510 msgid "Set as a Separated Object" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1218 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1375 msgid "Rename" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1229 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1386 msgid "Fix through the Netfabb" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1239 src/slic3r/GUI/Plater.cpp:3023 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1396 src/slic3r/GUI/Plater.cpp:3496 msgid "Export as STL" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1246 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1403 msgid "Change extruder" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1265 src/libslic3r/PrintConfig.cpp:300 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1422 src/libslic3r/PrintConfig.cpp:309 msgid "Default" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1271 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1428 msgid "Select new extruder for the object/part" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1342 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1440 +msgid "Scale to print volume" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1440 +msgid "Scale the selected object to fit the print volume" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1510 msgid "Set as a Separated Objects" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1555 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1585 +msgid "Load Part" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1617 +msgid "Error!" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1662 +msgid "Add Generic Subobject" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1669 msgid "Generic" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1698 -msgid "You can't delete the last solid part from object." +#: src/slic3r/GUI/GUI_ObjectList.cpp:1770 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1872 +msgid "Last instance of an object cannot be deleted." msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1715 -msgid "You can't delete the last intance from object." +#: src/slic3r/GUI/GUI_ObjectList.cpp:1782 +msgid "Delete Settings" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1741 src/slic3r/GUI/Plater.cpp:2343 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1806 +msgid "Delete All Instances from Object" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1822 +msgid "Delete Layers Range" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1853 +msgid "From Object List You can't delete the last solid part from object." +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1857 +msgid "Delete Subobject" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1876 +msgid "Delete Instance" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1900 src/slic3r/GUI/Plater.cpp:2793 msgid "" "The selected object couldn't be split because it contains only one part." msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1850 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1904 +msgid "Split to Parts" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1950 +msgid "Add Layers" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:2075 msgid "Group manipulation" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1862 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2087 msgid "Object manipulation" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1872 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2100 msgid "Object Settings to modify" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1876 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2104 msgid "Part Settings to modify" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1885 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2109 +msgid "Layer range Settings to modify" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:2115 msgid "Part manipulation" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1891 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2121 msgid "Instance manipulation" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2416 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2128 +msgid "Layers Editing" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:2128 +msgid "Layer Editing" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:2303 +msgid "Delete Selected Item" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:2415 +msgid "Delete Selected" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:2484 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2513 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2531 +msgid "Add New Layers Range" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:2590 +msgid "Edit Layers Range" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:2867 +msgid "Selection-Remove from list" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:2875 +msgid "Selection-Add from list" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:2993 msgid "Object or Instance" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2416 -#: src/slic3r/GUI/GUI_ObjectList.cpp:2547 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2994 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3127 msgid "Part" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2418 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2994 +msgid "Layer" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:2996 msgid "Unsupported selection" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2419 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2997 #, possible-c-format msgid "You started your selection with %s Item." msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2420 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2998 #, possible-c-format msgid "In this mode you can select only other %s Items%s" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2423 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3001 msgid "of a current Object" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2428 -#: src/slic3r/GUI/GUI_ObjectList.cpp:2501 src/slic3r/GUI/Plater.cpp:117 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3006 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3081 src/slic3r/GUI/Plater.cpp:126 msgid "Info" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2542 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3122 msgid "You can't change a type of the last solid part of the object." msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2547 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3127 msgid "Modifier" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2547 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3127 msgid "Support Enforcer" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2547 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3127 msgid "Support Blocker" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2549 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3129 msgid "Type:" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2549 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3129 msgid "Select type of part" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2713 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3134 +msgid "Change Part Type" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:3368 msgid "Enter new name" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2713 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3368 msgid "Renaming" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2729 -#: src/slic3r/GUI/GUI_ObjectList.cpp:2823 src/slic3r/GUI/Tab.cpp:3335 -#: src/slic3r/GUI/Tab.cpp:3339 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3384 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3480 src/slic3r/GUI/Tab.cpp:3536 +#: src/slic3r/GUI/Tab.cpp:3540 msgid "The supplied name is not valid;" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2730 -#: src/slic3r/GUI/GUI_ObjectList.cpp:2824 src/slic3r/GUI/Tab.cpp:3336 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3385 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3481 src/slic3r/GUI/Tab.cpp:3537 msgid "the following characters are not allowed:" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2840 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3498 msgid "Set extruder for selected items" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2841 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3499 msgid "Select extruder number for selected objects and/or parts" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2854 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3512 msgid "Select extruder number:" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2855 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3513 msgid "This extruder will be set for selected items" msgstr "" -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:40 -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:83 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:62 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:105 msgid "World coordinates" msgstr "" -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:41 -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:84 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:63 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:106 msgid "Local coordinates" msgstr "" -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:60 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:82 msgid "Select coordinate space, in which the transformation will be performed." msgstr "" -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:102 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:125 msgid "Object Manipulation" msgstr "" -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:153 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:176 msgid "Object name" msgstr "" -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:229 -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:282 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:212 +#, possible-c-format +msgid "Toggle %c axis mirroring" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:245 +msgid "Set Mirror" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:285 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:290 +msgid "Reset scale" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:303 +msgid "Reset rotation" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:328 +msgid "Reset Rotation" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:340 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:355 +msgid "Drop to bed" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:388 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:443 msgid "Position" msgstr "" -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:230 -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:283 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:389 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:444 msgid "Rotation" msgstr "" -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:284 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:445 msgid "Scale factors" msgstr "" -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:341 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:502 msgid "Translate" msgstr "" -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:640 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:554 +msgid "" +"You cann't use non-uniform scaling mode for multiple objects/parts selection" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:715 +msgid "Set Position" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:746 +msgid "Set Orientation" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:811 +msgid "Set Scale" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:895 msgid "" "The currently manipulated object is tilted (rotation angles are not " "multiples of 90°).\n" @@ -1613,7 +1905,7 @@ msgid "" "once the rotation is embedded into the object coordinates." msgstr "" -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:643 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:898 msgid "" "This operation is irreversible.\n" "Do you want to proceed?" @@ -1623,117 +1915,127 @@ msgstr "" msgid "Additional Settings" msgstr "" -#: src/slic3r/GUI/GUI_ObjectSettings.cpp:83 +#: src/slic3r/GUI/GUI_ObjectSettings.cpp:94 msgid "Remove parameter" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:215 +#: src/slic3r/GUI/GUI_ObjectSettings.cpp:100 +#, possible-c-format +msgid "Delete Option %s" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectSettings.cpp:144 +#, possible-c-format +msgid "Change Option %s" +msgstr "" + +#: src/slic3r/GUI/GUI_Preview.cpp:216 msgid "View" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:218 src/slic3r/GUI/GUI_Preview.cpp:544 +#: src/slic3r/GUI/GUI_Preview.cpp:219 src/slic3r/GUI/GUI_Preview.cpp:554 #: src/libslic3r/GCode/PreviewData.cpp:394 msgid "Feature type" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:219 src/libslic3r/PrintConfig.cpp:469 +#: src/slic3r/GUI/GUI_Preview.cpp:220 src/libslic3r/PrintConfig.cpp:483 msgid "Height" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:220 src/libslic3r/PrintConfig.cpp:2172 +#: src/slic3r/GUI/GUI_Preview.cpp:221 src/libslic3r/PrintConfig.cpp:2188 msgid "Width" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:222 +#: src/slic3r/GUI/GUI_Preview.cpp:223 msgid "Volumetric flow rate" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:223 src/slic3r/GUI/GUI_Preview.cpp:321 -#: src/slic3r/GUI/GUI_Preview.cpp:487 src/slic3r/GUI/GUI_Preview.cpp:544 -#: src/slic3r/GUI/GUI_Preview.cpp:720 src/libslic3r/GCode/PreviewData.cpp:404 +#: src/slic3r/GUI/GUI_Preview.cpp:224 src/slic3r/GUI/GUI_Preview.cpp:328 +#: src/slic3r/GUI/GUI_Preview.cpp:506 src/slic3r/GUI/GUI_Preview.cpp:553 +#: src/slic3r/GUI/GUI_Preview.cpp:749 src/libslic3r/GCode/PreviewData.cpp:404 msgid "Tool" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:224 src/slic3r/GUI/GUI_Preview.cpp:542 +#: src/slic3r/GUI/GUI_Preview.cpp:225 src/slic3r/GUI/GUI_Preview.cpp:551 #: src/libslic3r/GCode/PreviewData.cpp:406 msgid "Color Print" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:227 +#: src/slic3r/GUI/GUI_Preview.cpp:228 msgid "Show" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:230 src/slic3r/GUI/GUI_Preview.cpp:231 +#: src/slic3r/GUI/GUI_Preview.cpp:231 src/slic3r/GUI/GUI_Preview.cpp:232 msgid "Feature types" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:233 src/libslic3r/GCode/PreviewData.cpp:163 +#: src/slic3r/GUI/GUI_Preview.cpp:234 src/libslic3r/GCode/PreviewData.cpp:163 msgid "Perimeter" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:234 src/libslic3r/GCode/PreviewData.cpp:164 +#: src/slic3r/GUI/GUI_Preview.cpp:235 src/libslic3r/GCode/PreviewData.cpp:164 msgid "External perimeter" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:235 src/libslic3r/GCode/PreviewData.cpp:165 +#: src/slic3r/GUI/GUI_Preview.cpp:236 src/libslic3r/GCode/PreviewData.cpp:165 msgid "Overhang perimeter" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:236 src/libslic3r/GCode/PreviewData.cpp:166 +#: src/slic3r/GUI/GUI_Preview.cpp:237 src/libslic3r/GCode/PreviewData.cpp:166 msgid "Internal infill" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:237 src/libslic3r/PrintConfig.cpp:1722 -#: src/libslic3r/PrintConfig.cpp:1732 src/libslic3r/GCode/PreviewData.cpp:167 +#: src/slic3r/GUI/GUI_Preview.cpp:238 src/libslic3r/PrintConfig.cpp:1736 +#: src/libslic3r/PrintConfig.cpp:1746 src/libslic3r/GCode/PreviewData.cpp:167 msgid "Solid infill" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:238 src/libslic3r/PrintConfig.cpp:2052 -#: src/libslic3r/PrintConfig.cpp:2063 src/libslic3r/GCode/PreviewData.cpp:168 +#: src/slic3r/GUI/GUI_Preview.cpp:239 src/libslic3r/PrintConfig.cpp:2068 +#: src/libslic3r/PrintConfig.cpp:2079 src/libslic3r/GCode/PreviewData.cpp:168 msgid "Top solid infill" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:239 src/libslic3r/GCode/PreviewData.cpp:169 +#: src/slic3r/GUI/GUI_Preview.cpp:240 src/libslic3r/GCode/PreviewData.cpp:169 msgid "Bridge infill" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:240 src/libslic3r/PrintConfig.cpp:881 +#: src/slic3r/GUI/GUI_Preview.cpp:241 src/libslic3r/PrintConfig.cpp:895 #: src/libslic3r/GCode/PreviewData.cpp:170 msgid "Gap fill" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:241 src/slic3r/GUI/Tab.cpp:1056 +#: src/slic3r/GUI/GUI_Preview.cpp:242 src/slic3r/GUI/Tab.cpp:1061 #: src/libslic3r/GCode/PreviewData.cpp:171 msgid "Skirt" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:243 src/libslic3r/PrintConfig.cpp:1939 +#: src/slic3r/GUI/GUI_Preview.cpp:244 src/libslic3r/PrintConfig.cpp:1954 #: src/libslic3r/GCode/PreviewData.cpp:173 msgid "Support material interface" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:244 src/slic3r/GUI/Tab.cpp:1136 +#: src/slic3r/GUI/GUI_Preview.cpp:245 src/slic3r/GUI/Tab.cpp:1141 #: src/libslic3r/GCode/PreviewData.cpp:174 msgid "Wipe tower" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:249 src/libslic3r/PrintConfig.cpp:2086 +#: src/slic3r/GUI/GUI_Preview.cpp:250 src/libslic3r/PrintConfig.cpp:2102 msgid "Travel" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:250 +#: src/slic3r/GUI/GUI_Preview.cpp:251 msgid "Retractions" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:251 +#: src/slic3r/GUI/GUI_Preview.cpp:252 msgid "Unretractions" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:252 +#: src/slic3r/GUI/GUI_Preview.cpp:253 msgid "Shells" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:13 src/slic3r/GUI/MainFrame.cpp:608 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:13 src/slic3r/GUI/MainFrame.cpp:672 msgid "Keyboard Shortcuts" msgstr "" @@ -1749,8 +2051,8 @@ msgstr "" msgid "Load Config from .ini/amf/3mf/gcode" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:107 src/slic3r/GUI/Plater.cpp:740 -#: src/slic3r/GUI/Plater.cpp:3907 src/libslic3r/PrintConfig.cpp:3000 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:107 src/slic3r/GUI/Plater.cpp:822 +#: src/slic3r/GUI/Plater.cpp:4687 src/libslic3r/PrintConfig.cpp:3127 msgid "Export G-code" msgstr "" @@ -1877,642 +2179,675 @@ msgstr "" #: src/slic3r/GUI/KBShortcutsDialog.cpp:147 msgid "" +"Press to scale selection to fit print volume\n" +"in Gizmo scale" +msgstr "" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:148 +msgid "" "Press to activate deselection rectangle\n" "or to scale or rotate selected objects\n" "around their own center" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:148 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:149 msgid "Press to activate one direction scaling in Gizmo scale" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:149 -msgid "Zoom to Bed" -msgstr "" - #: src/slic3r/GUI/KBShortcutsDialog.cpp:150 -msgid "Zoom to all objects in scene, if none selected" +msgid "Change camera type" msgstr "" #: src/slic3r/GUI/KBShortcutsDialog.cpp:151 -msgid "Zoom to selected object" +msgid "Zoom to Bed" msgstr "" #: src/slic3r/GUI/KBShortcutsDialog.cpp:152 -msgid "Zoom in" +msgid "Zoom to all objects in scene, if none selected" msgstr "" #: src/slic3r/GUI/KBShortcutsDialog.cpp:153 -msgid "Zoom out" +msgid "Zoom to selected object" msgstr "" #: src/slic3r/GUI/KBShortcutsDialog.cpp:154 -msgid "Unselect gizmo / Clear selection" +msgid "Zoom in" +msgstr "" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:155 +msgid "Zoom out" msgstr "" #: src/slic3r/GUI/KBShortcutsDialog.cpp:156 +msgid "Unselect gizmo / Clear selection" +msgstr "" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:158 +msgid "Toggle picking pass texture rendering on/off" +msgstr "" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:161 msgid "Plater Shortcuts" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:171 -#: src/slic3r/GUI/KBShortcutsDialog.cpp:182 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:176 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:187 msgid "Arrow Up" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:171 -#: src/slic3r/GUI/KBShortcutsDialog.cpp:173 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:176 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:178 msgid "Upper Layer" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:172 -#: src/slic3r/GUI/KBShortcutsDialog.cpp:183 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:177 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:188 msgid "Arrow Down" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:172 -#: src/slic3r/GUI/KBShortcutsDialog.cpp:174 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:177 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:179 msgid "Lower Layer" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:176 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:181 msgid "Preview Shortcuts" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:182 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:187 msgid "Move current slider thumb Up" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:183 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:188 msgid "Move current slider thumb Down" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:184 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:189 msgid "Arrow Left" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:184 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:189 msgid "Set upper thumb to current slider thumb" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:185 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:190 msgid "Arrow Right" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:185 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:190 msgid "Set lower thumb to current slider thumb" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:186 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:191 msgid "Add color change marker for current layer" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:187 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:192 msgid "Delete color change marker for current layer" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:189 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:194 msgid "Layers Slider Shortcuts" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:61 +#: src/slic3r/GUI/MainFrame.cpp:62 msgid "" " - Remember to check for updates at http://github.com/prusa3d/PrusaSlicer/" "releases" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:150 +#: src/slic3r/GUI/MainFrame.cpp:157 msgid "based on Slic3r" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:180 +#: src/slic3r/GUI/MainFrame.cpp:187 msgid "Plater" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:374 +#: src/slic3r/GUI/MainFrame.cpp:393 msgid "&New Project" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:374 +#: src/slic3r/GUI/MainFrame.cpp:393 msgid "Start a new project" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:377 +#: src/slic3r/GUI/MainFrame.cpp:396 msgid "&Open Project" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:377 +#: src/slic3r/GUI/MainFrame.cpp:396 msgid "Open a project file" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:380 +#: src/slic3r/GUI/MainFrame.cpp:401 +msgid "Recent projects" +msgstr "" + +#: src/slic3r/GUI/MainFrame.cpp:410 +msgid "The selected project is no more available" +msgstr "" + +#: src/slic3r/GUI/MainFrame.cpp:410 src/slic3r/GUI/MainFrame.cpp:747 +#: src/slic3r/GUI/PrintHostDialogs.cpp:231 +msgid "Error" +msgstr "" + +#: src/slic3r/GUI/MainFrame.cpp:434 msgid "&Save Project" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:380 +#: src/slic3r/GUI/MainFrame.cpp:434 msgid "Save current project file" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:384 src/slic3r/GUI/MainFrame.cpp:386 +#: src/slic3r/GUI/MainFrame.cpp:438 src/slic3r/GUI/MainFrame.cpp:440 msgid "Save Project &as" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:384 src/slic3r/GUI/MainFrame.cpp:386 +#: src/slic3r/GUI/MainFrame.cpp:438 src/slic3r/GUI/MainFrame.cpp:440 msgid "Save current project file as" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:394 +#: src/slic3r/GUI/MainFrame.cpp:448 msgid "Import STL/OBJ/AM&F/3MF" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:394 +#: src/slic3r/GUI/MainFrame.cpp:448 msgid "Load a model" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:398 +#: src/slic3r/GUI/MainFrame.cpp:452 msgid "Import &Config" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:398 +#: src/slic3r/GUI/MainFrame.cpp:452 msgid "Load exported configuration file" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:400 +#: src/slic3r/GUI/MainFrame.cpp:454 msgid "Import Config from &project" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:400 +#: src/slic3r/GUI/MainFrame.cpp:454 msgid "Load configuration from project file" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:403 +#: src/slic3r/GUI/MainFrame.cpp:457 msgid "Import Config &Bundle" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:403 +#: src/slic3r/GUI/MainFrame.cpp:457 msgid "Load presets from a bundle" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:405 +#: src/slic3r/GUI/MainFrame.cpp:459 msgid "&Import" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:408 src/slic3r/GUI/MainFrame.cpp:644 +#: src/slic3r/GUI/MainFrame.cpp:462 src/slic3r/GUI/MainFrame.cpp:708 msgid "Export &G-code" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:408 +#: src/slic3r/GUI/MainFrame.cpp:462 msgid "Export current plate as G-code" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:413 +#: src/slic3r/GUI/MainFrame.cpp:466 src/slic3r/GUI/MainFrame.cpp:709 +msgid "S&end G-code" +msgstr "" + +#: src/slic3r/GUI/MainFrame.cpp:466 +msgid "Send to print current plate as G-code" +msgstr "" + +#: src/slic3r/GUI/MainFrame.cpp:471 msgid "Export plate as &STL" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:413 +#: src/slic3r/GUI/MainFrame.cpp:471 msgid "Export current plate as STL" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:416 +#: src/slic3r/GUI/MainFrame.cpp:474 msgid "Export plate as STL including supports" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:416 +#: src/slic3r/GUI/MainFrame.cpp:474 msgid "Export current plate as STL including supports" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:419 +#: src/slic3r/GUI/MainFrame.cpp:477 msgid "Export plate as &AMF" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:419 +#: src/slic3r/GUI/MainFrame.cpp:477 msgid "Export current plate as AMF" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:423 +#: src/slic3r/GUI/MainFrame.cpp:481 msgid "Export &Config" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:423 +#: src/slic3r/GUI/MainFrame.cpp:481 msgid "Export current configuration to file" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:425 +#: src/slic3r/GUI/MainFrame.cpp:483 msgid "Export Config &Bundle" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:425 +#: src/slic3r/GUI/MainFrame.cpp:483 msgid "Export all presets to file" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:427 +#: src/slic3r/GUI/MainFrame.cpp:485 msgid "&Export" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:433 +#: src/slic3r/GUI/MainFrame.cpp:491 msgid "Quick Slice" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:433 +#: src/slic3r/GUI/MainFrame.cpp:491 msgid "Slice a file into a G-code" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:439 +#: src/slic3r/GUI/MainFrame.cpp:497 msgid "Quick Slice and Save As" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:439 +#: src/slic3r/GUI/MainFrame.cpp:497 msgid "Slice a file into a G-code, save as" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:445 +#: src/slic3r/GUI/MainFrame.cpp:503 msgid "Repeat Last Quick Slice" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:445 +#: src/slic3r/GUI/MainFrame.cpp:503 msgid "Repeat last quick slice" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:453 +#: src/slic3r/GUI/MainFrame.cpp:511 msgid "(Re)Slice No&w" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:453 +#: src/slic3r/GUI/MainFrame.cpp:511 msgid "Start new slicing process" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:457 +#: src/slic3r/GUI/MainFrame.cpp:515 msgid "&Repair STL file" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:457 +#: src/slic3r/GUI/MainFrame.cpp:515 msgid "Automatically repair an STL file" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:460 +#: src/slic3r/GUI/MainFrame.cpp:518 msgid "&Quit" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:460 +#: src/slic3r/GUI/MainFrame.cpp:518 #, possible-c-format msgid "Quit %s" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:485 +#: src/slic3r/GUI/MainFrame.cpp:543 msgid "&Select all" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:488 +#: src/slic3r/GUI/MainFrame.cpp:544 msgid "Selects all objects" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:491 +#: src/slic3r/GUI/MainFrame.cpp:546 msgid "D&eselect all" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:492 +#: src/slic3r/GUI/MainFrame.cpp:547 msgid "Deselects all objects" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:496 +#: src/slic3r/GUI/MainFrame.cpp:550 msgid "&Delete selected" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:497 +#: src/slic3r/GUI/MainFrame.cpp:551 msgid "Deletes the current selection" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:499 +#: src/slic3r/GUI/MainFrame.cpp:553 msgid "Delete &all" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:500 +#: src/slic3r/GUI/MainFrame.cpp:554 msgid "Deletes all objects" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:504 +#: src/slic3r/GUI/MainFrame.cpp:558 +msgid "&Undo" +msgstr "" + +#: src/slic3r/GUI/MainFrame.cpp:561 +msgid "&Redo" +msgstr "" + +#: src/slic3r/GUI/MainFrame.cpp:566 msgid "&Copy" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:505 +#: src/slic3r/GUI/MainFrame.cpp:567 msgid "Copy selection to clipboard" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:507 +#: src/slic3r/GUI/MainFrame.cpp:569 msgid "&Paste" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:508 +#: src/slic3r/GUI/MainFrame.cpp:570 msgid "Paste clipboard" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:517 +#: src/slic3r/GUI/MainFrame.cpp:579 msgid "&Plater Tab" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:517 +#: src/slic3r/GUI/MainFrame.cpp:579 msgid "Show the plater" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:524 +#: src/slic3r/GUI/MainFrame.cpp:586 msgid "P&rint Settings Tab" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:524 +#: src/slic3r/GUI/MainFrame.cpp:586 msgid "Show the print settings" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:526 src/slic3r/GUI/MainFrame.cpp:648 +#: src/slic3r/GUI/MainFrame.cpp:588 src/slic3r/GUI/MainFrame.cpp:711 msgid "&Filament Settings Tab" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:526 +#: src/slic3r/GUI/MainFrame.cpp:588 msgid "Show the filament settings" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:529 +#: src/slic3r/GUI/MainFrame.cpp:591 msgid "Print&er Settings Tab" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:529 +#: src/slic3r/GUI/MainFrame.cpp:591 msgid "Show the printer settings" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:533 +#: src/slic3r/GUI/MainFrame.cpp:595 msgid "3&D" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:533 +#: src/slic3r/GUI/MainFrame.cpp:595 msgid "Show the 3D editing view" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:536 +#: src/slic3r/GUI/MainFrame.cpp:598 msgid "Pre&view" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:536 +#: src/slic3r/GUI/MainFrame.cpp:598 msgid "Show the 3D slices preview" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:555 +#: src/slic3r/GUI/MainFrame.cpp:617 msgid "Print &Host Upload Queue" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:555 +#: src/slic3r/GUI/MainFrame.cpp:617 msgid "Display the Print Host Upload Queue window" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:564 +#: src/slic3r/GUI/MainFrame.cpp:626 msgid "Iso" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:564 +#: src/slic3r/GUI/MainFrame.cpp:626 msgid "Iso View" msgstr "" #. TRN To be shown in the main menu View->Top -#: src/slic3r/GUI/MainFrame.cpp:568 -msgid "Top" -msgstr "" - #. TRN To be shown in Print Settings "Top solid layers" -#: src/libslic3r/PrintConfig.cpp:2078 -msgctxt "Layers" +#: src/slic3r/GUI/MainFrame.cpp:630 src/libslic3r/PrintConfig.cpp:2094 msgid "Top" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:568 +#: src/slic3r/GUI/MainFrame.cpp:630 msgid "Top View" msgstr "" #. TRN To be shown in the main menu View->Bottom -#: src/slic3r/GUI/MainFrame.cpp:571 -msgid "Bottom" -msgstr "" - #. TRN To be shown in Print Settings "Bottom solid layers" -#: src/libslic3r/PrintConfig.cpp:150 -msgctxt "Layers" +#: src/slic3r/GUI/MainFrame.cpp:633 src/libslic3r/PrintConfig.cpp:159 msgid "Bottom" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:571 +#: src/slic3r/GUI/MainFrame.cpp:633 msgid "Bottom View" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:573 +#: src/slic3r/GUI/MainFrame.cpp:635 msgid "Front" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:573 +#: src/slic3r/GUI/MainFrame.cpp:635 msgid "Front View" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:575 src/libslic3r/PrintConfig.cpp:1597 +#: src/slic3r/GUI/MainFrame.cpp:637 src/libslic3r/PrintConfig.cpp:1611 msgid "Rear" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:575 +#: src/slic3r/GUI/MainFrame.cpp:637 msgid "Rear View" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:577 +#: src/slic3r/GUI/MainFrame.cpp:639 msgid "Left" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:577 +#: src/slic3r/GUI/MainFrame.cpp:639 msgid "Left View" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:579 +#: src/slic3r/GUI/MainFrame.cpp:641 msgid "Right" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:579 +#: src/slic3r/GUI/MainFrame.cpp:641 msgid "Right View" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:586 +#: src/slic3r/GUI/MainFrame.cpp:648 msgid "Prusa 3D &Drivers" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:586 +#: src/slic3r/GUI/MainFrame.cpp:648 msgid "Open the Prusa3D drivers download page in your browser" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:588 +#: src/slic3r/GUI/MainFrame.cpp:650 msgid "Software &Releases" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:588 +#: src/slic3r/GUI/MainFrame.cpp:650 msgid "Open the software releases page in your browser" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:594 +#: src/slic3r/GUI/MainFrame.cpp:656 #, possible-c-format msgid "%s &Website" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:595 +#: src/slic3r/GUI/MainFrame.cpp:657 #, possible-c-format msgid "Open the %s website in your browser" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:601 +#: src/slic3r/GUI/MainFrame.cpp:663 msgid "System &Info" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:601 +#: src/slic3r/GUI/MainFrame.cpp:663 msgid "Show system information" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:603 +#: src/slic3r/GUI/MainFrame.cpp:665 msgid "Show &Configuration Folder" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:603 +#: src/slic3r/GUI/MainFrame.cpp:665 msgid "Show user configuration folder (datadir)" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:605 +#: src/slic3r/GUI/MainFrame.cpp:667 msgid "Report an I&ssue" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:605 +#: src/slic3r/GUI/MainFrame.cpp:667 #, possible-c-format msgid "Report an issue on %s" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:607 +#: src/slic3r/GUI/MainFrame.cpp:669 #, possible-c-format msgid "&About %s" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:607 +#: src/slic3r/GUI/MainFrame.cpp:669 msgid "Show about dialog" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:610 +#: src/slic3r/GUI/MainFrame.cpp:672 msgid "Show the list of the keyboard shortcuts" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:618 +#: src/slic3r/GUI/MainFrame.cpp:680 msgid "&File" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:619 +#: src/slic3r/GUI/MainFrame.cpp:681 msgid "&Edit" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:620 +#: src/slic3r/GUI/MainFrame.cpp:682 msgid "&Window" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:621 +#: src/slic3r/GUI/MainFrame.cpp:683 msgid "&View" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:624 +#: src/slic3r/GUI/MainFrame.cpp:686 msgid "&Help" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:646 src/slic3r/GUI/Plater.cpp:3907 -msgid "Export" +#: src/slic3r/GUI/MainFrame.cpp:708 +msgid "E&xport" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:648 +#: src/slic3r/GUI/MainFrame.cpp:709 +msgid "S&end to print" +msgstr "" + +#: src/slic3r/GUI/MainFrame.cpp:711 msgid "Mate&rial Settings Tab" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:669 +#: src/slic3r/GUI/MainFrame.cpp:732 msgid "Choose a file to slice (STL/OBJ/AMF/3MF/PRUSA):" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:683 +#: src/slic3r/GUI/MainFrame.cpp:746 msgid "No previously sliced file." msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:684 src/slic3r/GUI/PrintHostDialogs.cpp:231 -msgid "Error" -msgstr "" - -#: src/slic3r/GUI/MainFrame.cpp:689 +#: src/slic3r/GUI/MainFrame.cpp:752 msgid "Previously sliced file (" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:689 +#: src/slic3r/GUI/MainFrame.cpp:752 msgid ") not found." msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:690 +#: src/slic3r/GUI/MainFrame.cpp:753 msgid "File Not Found" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:725 +#: src/slic3r/GUI/MainFrame.cpp:788 #, possible-c-format msgid "Save %s file as:" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:725 +#: src/slic3r/GUI/MainFrame.cpp:788 msgid "SVG" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:725 +#: src/slic3r/GUI/MainFrame.cpp:788 msgid "G-code" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:740 +#: src/slic3r/GUI/MainFrame.cpp:803 msgid "Save zip file as:" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:750 src/slic3r/GUI/Plater.cpp:2476 -#: src/slic3r/GUI/Plater.cpp:3695 src/slic3r/GUI/Tab.cpp:1165 -#: src/slic3r/GUI/Tab.cpp:3493 +#: src/slic3r/GUI/MainFrame.cpp:815 src/slic3r/GUI/Plater.cpp:2933 +#: src/slic3r/GUI/Plater.cpp:4418 src/slic3r/GUI/Tab.cpp:1170 +#: src/slic3r/GUI/Tab.cpp:3700 msgid "Slicing" msgstr "" #. TRN "Processing input_file_basename" -#: src/slic3r/GUI/MainFrame.cpp:754 +#: src/slic3r/GUI/MainFrame.cpp:817 #, possible-c-format msgid "Processing %s" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:777 +#: src/slic3r/GUI/MainFrame.cpp:840 msgid " was successfully sliced." msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:779 +#: src/slic3r/GUI/MainFrame.cpp:842 msgid "Slicing Done!" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:794 +#: src/slic3r/GUI/MainFrame.cpp:857 msgid "Select the STL file to repair:" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:807 +#: src/slic3r/GUI/MainFrame.cpp:870 msgid "Save OBJ file (less prone to coordinate errors than STL) as:" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:822 +#: src/slic3r/GUI/MainFrame.cpp:885 msgid "Your file was repaired." msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:822 src/libslic3r/PrintConfig.cpp:3094 +#: src/slic3r/GUI/MainFrame.cpp:885 src/libslic3r/PrintConfig.cpp:3221 msgid "Repair" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:836 +#: src/slic3r/GUI/MainFrame.cpp:899 msgid "Save configuration as:" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:856 src/slic3r/GUI/MainFrame.cpp:920 +#: src/slic3r/GUI/MainFrame.cpp:919 src/slic3r/GUI/MainFrame.cpp:983 msgid "Select configuration to load:" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:893 +#: src/slic3r/GUI/MainFrame.cpp:956 msgid "Save presets bundle as:" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:944 +#: src/slic3r/GUI/MainFrame.cpp:1007 #, possible-c-format msgid "%d presets successfully imported." msgstr "" @@ -2527,493 +2862,590 @@ msgstr "" msgid "%s has encountered an error" msgstr "" -#: src/slic3r/GUI/Plater.cpp:137 +#: src/slic3r/GUI/Plater.cpp:146 msgid "Volume" msgstr "" -#: src/slic3r/GUI/Plater.cpp:138 +#: src/slic3r/GUI/Plater.cpp:147 msgid "Facets" msgstr "" -#: src/slic3r/GUI/Plater.cpp:139 +#: src/slic3r/GUI/Plater.cpp:148 msgid "Materials" msgstr "" -#: src/slic3r/GUI/Plater.cpp:142 +#: src/slic3r/GUI/Plater.cpp:151 msgid "Manifold" msgstr "" -#: src/slic3r/GUI/Plater.cpp:192 +#: src/slic3r/GUI/Plater.cpp:201 msgid "Sliced Info" msgstr "" -#: src/slic3r/GUI/Plater.cpp:211 src/slic3r/GUI/Plater.cpp:1049 +#: src/slic3r/GUI/Plater.cpp:220 src/slic3r/GUI/Plater.cpp:1135 msgid "Used Filament (m)" msgstr "" -#: src/slic3r/GUI/Plater.cpp:212 +#: src/slic3r/GUI/Plater.cpp:221 msgid "Used Filament (mm³)" msgstr "" -#: src/slic3r/GUI/Plater.cpp:213 +#: src/slic3r/GUI/Plater.cpp:222 msgid "Used Filament (g)" msgstr "" -#: src/slic3r/GUI/Plater.cpp:214 +#: src/slic3r/GUI/Plater.cpp:223 msgid "Used Material (unit)" msgstr "" -#: src/slic3r/GUI/Plater.cpp:215 src/slic3r/GUI/Plater.cpp:1064 -#: src/libslic3r/PrintConfig.cpp:728 +#: src/slic3r/GUI/Plater.cpp:224 src/slic3r/GUI/Plater.cpp:1150 +#: src/libslic3r/PrintConfig.cpp:742 msgid "Cost" msgstr "" -#: src/slic3r/GUI/Plater.cpp:216 src/slic3r/GUI/Plater.cpp:1036 -#: src/slic3r/GUI/Plater.cpp:1078 +#: src/slic3r/GUI/Plater.cpp:225 src/slic3r/GUI/Plater.cpp:1122 +#: src/slic3r/GUI/Plater.cpp:1164 msgid "Estimated printing time" msgstr "" -#: src/slic3r/GUI/Plater.cpp:217 +#: src/slic3r/GUI/Plater.cpp:226 msgid "Number of tool changes" msgstr "" -#: src/slic3r/GUI/Plater.cpp:291 +#: src/slic3r/GUI/Plater.cpp:317 msgid "Click to edit preset" msgstr "" -#: src/slic3r/GUI/Plater.cpp:431 +#: src/slic3r/GUI/Plater.cpp:469 msgid "Select what kind of support do you need" msgstr "" -#: src/slic3r/GUI/Plater.cpp:433 src/libslic3r/PrintConfig.cpp:1850 -#: src/libslic3r/PrintConfig.cpp:2458 +#: src/slic3r/GUI/Plater.cpp:471 src/libslic3r/PrintConfig.cpp:1865 +#: src/libslic3r/PrintConfig.cpp:2529 msgid "Support on build plate only" msgstr "" -#: src/slic3r/GUI/Plater.cpp:434 src/slic3r/GUI/Plater.cpp:527 +#: src/slic3r/GUI/Plater.cpp:472 src/slic3r/GUI/Plater.cpp:587 msgid "For support enforcers only" msgstr "" -#: src/slic3r/GUI/Plater.cpp:435 +#: src/slic3r/GUI/Plater.cpp:473 msgid "Everywhere" msgstr "" -#: src/slic3r/GUI/Plater.cpp:453 src/slic3r/GUI/Tab.cpp:1062 +#: src/slic3r/GUI/Plater.cpp:505 src/slic3r/GUI/Tab.cpp:1067 msgid "Brim" msgstr "" -#: src/slic3r/GUI/Plater.cpp:455 +#: src/slic3r/GUI/Plater.cpp:507 msgid "" "This flag enables the brim that will be printed around each object on the " "first layer." msgstr "" -#: src/slic3r/GUI/Plater.cpp:463 +#: src/slic3r/GUI/Plater.cpp:515 msgid "Purging volumes" msgstr "" -#: src/slic3r/GUI/Plater.cpp:688 +#: src/slic3r/GUI/Plater.cpp:766 msgid "Print settings" msgstr "" -#: src/slic3r/GUI/Plater.cpp:689 src/slic3r/GUI/Tab.cpp:1506 -#: src/slic3r/GUI/Tab.cpp:1507 +#: src/slic3r/GUI/Plater.cpp:767 src/slic3r/GUI/Tab.cpp:1604 +#: src/slic3r/GUI/Tab.cpp:1605 msgid "Filament" msgstr "" -#: src/slic3r/GUI/Plater.cpp:690 +#: src/slic3r/GUI/Plater.cpp:768 msgid "SLA print settings" msgstr "" -#: src/slic3r/GUI/Plater.cpp:691 src/slic3r/GUI/Preset.cpp:1285 +#: src/slic3r/GUI/Plater.cpp:769 src/slic3r/GUI/Preset.cpp:1310 msgid "SLA material" msgstr "" -#: src/slic3r/GUI/Plater.cpp:692 +#: src/slic3r/GUI/Plater.cpp:770 msgid "Printer" msgstr "" -#: src/slic3r/GUI/Plater.cpp:738 src/slic3r/GUI/Plater.cpp:3908 +#: src/slic3r/GUI/Plater.cpp:820 src/slic3r/GUI/Plater.cpp:4688 msgid "Send to printer" msgstr "" -#: src/slic3r/GUI/Plater.cpp:741 src/slic3r/GUI/Plater.cpp:2476 -#: src/slic3r/GUI/Plater.cpp:3698 +#: src/slic3r/GUI/Plater.cpp:823 src/slic3r/GUI/Plater.cpp:2933 +#: src/slic3r/GUI/Plater.cpp:4421 msgid "Slice now" msgstr "" -#: src/slic3r/GUI/Plater.cpp:881 +#: src/slic3r/GUI/Plater.cpp:963 msgid "Hold Shift to Slice & Export G-code" msgstr "" -#: src/slic3r/GUI/Plater.cpp:982 +#: src/slic3r/GUI/Plater.cpp:1068 #, possible-c-format msgid "%d (%d shells)" msgstr "" -#: src/slic3r/GUI/Plater.cpp:987 +#: src/slic3r/GUI/Plater.cpp:1073 #, possible-c-format msgid "Auto-repaired (%d errors)" msgstr "" -#: src/slic3r/GUI/Plater.cpp:990 +#: src/slic3r/GUI/Plater.cpp:1076 #, possible-c-format msgid "" "%d degenerate facets, %d edges fixed, %d facets removed, %d facets added, %d " "facets reversed, %d backwards edges" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1000 +#: src/slic3r/GUI/Plater.cpp:1086 msgid "Yes" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1023 +#: src/slic3r/GUI/Plater.cpp:1109 msgid "Used Material (ml)" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1026 +#: src/slic3r/GUI/Plater.cpp:1112 msgid "object(s)" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1026 +#: src/slic3r/GUI/Plater.cpp:1112 msgid "supports and pad" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1051 src/slic3r/GUI/Plater.cpp:1066 +#: src/slic3r/GUI/Plater.cpp:1137 src/slic3r/GUI/Plater.cpp:1152 msgid "objects" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1051 src/slic3r/GUI/Plater.cpp:1066 +#: src/slic3r/GUI/Plater.cpp:1137 src/slic3r/GUI/Plater.cpp:1152 msgid "wipe tower" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1081 +#: src/slic3r/GUI/Plater.cpp:1167 msgid "normal mode" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1085 +#: src/slic3r/GUI/Plater.cpp:1171 src/slic3r/GUI/Plater.cpp:1180 +msgid "Color " +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:1176 msgid "stealth mode" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1631 +#: src/slic3r/GUI/Plater.cpp:1271 +msgid "Load File" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:1275 +msgid "Load Files" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:1503 +msgid "ERROR: not enough resources to execute a new job." +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:2056 +msgid "New Project" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:2173 msgid "Loading" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1641 +#: src/slic3r/GUI/Plater.cpp:2183 #, possible-c-format msgid "Processing input file %s\n" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1699 +#: src/slic3r/GUI/Plater.cpp:2211 +msgid "" +"You can't to load SLA project if there is at least one multi-part object on " +"the bed" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:2212 src/slic3r/GUI/Tab.cpp:3064 +msgid "Please check your object list before preset changing." +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:2255 msgid "" "This file contains several objects positioned at multiple heights. Instead " "of considering them as multiple objects, should I consider\n" "this file as a single object having multiple parts?\n" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1702 src/slic3r/GUI/Plater.cpp:1810 +#: src/slic3r/GUI/Plater.cpp:2258 src/slic3r/GUI/Plater.cpp:2310 msgid "Multi-part object detected" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1753 +#: src/slic3r/GUI/Plater.cpp:2265 msgid "" "This file cannot be loaded in a simple mode. Do you want to switch to an " "advanced mode?\n" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1754 +#: src/slic3r/GUI/Plater.cpp:2266 msgid "Detected advanced data" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1787 +#: src/slic3r/GUI/Plater.cpp:2287 #, possible-c-format msgid "" "You can't to add the object(s) from %s because of one or some of them " "is(are) multi-part" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1807 +#: src/slic3r/GUI/Plater.cpp:2307 msgid "" "Multiple objects were loaded for a multi-material printer.\n" "Instead of considering them as multiple objects, should I consider\n" "these files to represent a single object having multiple parts?\n" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1823 +#: src/slic3r/GUI/Plater.cpp:2323 msgid "Loaded" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1921 +#: src/slic3r/GUI/Plater.cpp:2418 msgid "" "Your object appears to be too large, so it was automatically scaled down to " "fit your print bed." msgstr "" -#: src/slic3r/GUI/Plater.cpp:1922 +#: src/slic3r/GUI/Plater.cpp:2419 msgid "Object too large?" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1979 +#: src/slic3r/GUI/Plater.cpp:2476 msgid "Export STL file:" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1986 +#: src/slic3r/GUI/Plater.cpp:2483 msgid "Export AMF file:" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1992 +#: src/slic3r/GUI/Plater.cpp:2489 msgid "Save file as:" msgstr "" -#: src/slic3r/GUI/Plater.cpp:2160 -msgid "Arranging canceled" +#: src/slic3r/GUI/Plater.cpp:2592 +msgid "Delete Object" msgstr "" -#: src/slic3r/GUI/Plater.cpp:2163 +#: src/slic3r/GUI/Plater.cpp:2603 +msgid "Reset Project" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:2630 src/slic3r/GUI/Plater.cpp:3517 +msgid "Mirror" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:2643 +msgid "Optimize Rotation" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:2689 msgid "Arranging" msgstr "" -#: src/slic3r/GUI/Plater.cpp:2200 +#: src/slic3r/GUI/Plater.cpp:2712 msgid "Could not arrange model objects! Some geometries may be invalid." msgstr "" -#: src/slic3r/GUI/Plater.cpp:2207 +#: src/slic3r/GUI/Plater.cpp:2718 +msgid "Arranging canceled." +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:2719 msgid "Arranging done." msgstr "" -#: src/slic3r/GUI/Plater.cpp:2248 -msgid "Orientation search canceled" -msgstr "" - -#: src/slic3r/GUI/Plater.cpp:2253 +#: src/slic3r/GUI/Plater.cpp:2735 msgid "Searching for optimal orientation" msgstr "" -#: src/slic3r/GUI/Plater.cpp:2315 +#: src/slic3r/GUI/Plater.cpp:2768 +msgid "Orientation search canceled." +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:2769 msgid "Orientation found." msgstr "" -#: src/slic3r/GUI/Plater.cpp:2335 +#: src/slic3r/GUI/Plater.cpp:2785 msgid "" "The selected object can't be split because it contains more than one volume/" "material." msgstr "" -#: src/slic3r/GUI/Plater.cpp:2461 +#: src/slic3r/GUI/Plater.cpp:2796 +msgid "Split to Objects" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:2918 msgid "Invalid data" msgstr "" -#: src/slic3r/GUI/Plater.cpp:2470 +#: src/slic3r/GUI/Plater.cpp:2927 msgid "Ready to slice" msgstr "" -#: src/slic3r/GUI/Plater.cpp:2508 src/slic3r/GUI/PrintHostDialogs.cpp:232 +#: src/slic3r/GUI/Plater.cpp:2965 src/slic3r/GUI/PrintHostDialogs.cpp:232 msgid "Cancelling" msgstr "" -#: src/slic3r/GUI/Plater.cpp:2525 +#: src/slic3r/GUI/Plater.cpp:2982 msgid "Another export job is currently running." msgstr "" -#: src/slic3r/GUI/Plater.cpp:2786 -msgid "Export failed" -msgstr "" - -#: src/slic3r/GUI/Plater.cpp:2791 src/slic3r/GUI/PrintHostDialogs.cpp:233 -msgid "Cancelled" -msgstr "" - -#: src/slic3r/GUI/Plater.cpp:2877 src/slic3r/GUI/Plater.cpp:2889 -#: src/slic3r/GUI/Plater.cpp:3000 -msgid "Increase copies" -msgstr "" - -#: src/slic3r/GUI/Plater.cpp:2994 src/slic3r/GUI/Plater.cpp:3013 -msgid "Remove the selected object" -msgstr "" - -#: src/slic3r/GUI/Plater.cpp:3000 -msgid "Place one more copy of the selected object" -msgstr "" - -#: src/slic3r/GUI/Plater.cpp:3002 -msgid "Decrease copies" -msgstr "" - -#: src/slic3r/GUI/Plater.cpp:3002 -msgid "Remove one copy of the selected object" -msgstr "" - -#: src/slic3r/GUI/Plater.cpp:3004 -msgid "Set number of copies" -msgstr "" - -#: src/slic3r/GUI/Plater.cpp:3004 -msgid "Change the number of copies of the selected object" -msgstr "" - -#: src/slic3r/GUI/Plater.cpp:3020 +#: src/slic3r/GUI/Plater.cpp:3036 src/slic3r/GUI/Plater.cpp:3493 msgid "Reload from Disk" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3020 +#: src/slic3r/GUI/Plater.cpp:3072 +msgid "Fix Throught NetFabb" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:3254 +msgid "Export failed" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:3259 src/slic3r/GUI/PrintHostDialogs.cpp:233 +msgid "Cancelled" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:3347 src/slic3r/GUI/Plater.cpp:3359 +#: src/slic3r/GUI/Plater.cpp:3473 +msgid "Increase copies" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:3467 src/slic3r/GUI/Plater.cpp:3486 +msgid "Remove the selected object" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:3473 +msgid "Place one more copy of the selected object" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:3475 +msgid "Decrease copies" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:3475 +msgid "Remove one copy of the selected object" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:3477 +msgid "Set number of copies" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:3477 +msgid "Change the number of copies of the selected object" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:3493 msgid "Reload the selected file from Disk" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3023 +#: src/slic3r/GUI/Plater.cpp:3496 msgid "Export the selected object as STL file" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3035 +#: src/slic3r/GUI/Plater.cpp:3510 msgid "Along X axis" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3035 +#: src/slic3r/GUI/Plater.cpp:3510 msgid "Mirror the selected object along the X axis" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3037 +#: src/slic3r/GUI/Plater.cpp:3512 msgid "Along Y axis" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3037 +#: src/slic3r/GUI/Plater.cpp:3512 msgid "Mirror the selected object along the Y axis" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3039 +#: src/slic3r/GUI/Plater.cpp:3514 msgid "Along Z axis" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3039 +#: src/slic3r/GUI/Plater.cpp:3514 msgid "Mirror the selected object along the Z axis" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3042 -msgid "Mirror" -msgstr "" - -#: src/slic3r/GUI/Plater.cpp:3042 +#: src/slic3r/GUI/Plater.cpp:3517 msgid "Mirror the selected object" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3054 +#: src/slic3r/GUI/Plater.cpp:3529 msgid "To objects" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3054 src/slic3r/GUI/Plater.cpp:3070 +#: src/slic3r/GUI/Plater.cpp:3529 src/slic3r/GUI/Plater.cpp:3549 msgid "Split the selected object into individual objects" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3056 +#: src/slic3r/GUI/Plater.cpp:3531 msgid "To parts" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3056 src/slic3r/GUI/Plater.cpp:3084 +#: src/slic3r/GUI/Plater.cpp:3531 src/slic3r/GUI/Plater.cpp:3563 msgid "Split the selected object into individual sub-parts" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3059 src/slic3r/GUI/Plater.cpp:3070 -#: src/slic3r/GUI/Plater.cpp:3084 src/libslic3r/PrintConfig.cpp:3118 +#: src/slic3r/GUI/Plater.cpp:3534 src/slic3r/GUI/Plater.cpp:3549 +#: src/slic3r/GUI/Plater.cpp:3563 src/libslic3r/PrintConfig.cpp:3245 msgid "Split" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3059 +#: src/slic3r/GUI/Plater.cpp:3534 msgid "Split the selected object" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3076 +#: src/slic3r/GUI/Plater.cpp:3555 msgid "Optimize orientation" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3076 +#: src/slic3r/GUI/Plater.cpp:3555 msgid "Optimize the rotation of the object for better print results." msgstr "" -#: src/slic3r/GUI/Plater.cpp:3127 +#: src/slic3r/GUI/Plater.cpp:3595 msgid "3D editor view" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3138 src/slic3r/GUI/Tab.cpp:2325 +#: src/slic3r/GUI/Plater.cpp:3603 src/slic3r/GUI/Tab.cpp:2534 msgid "Preview" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3375 +#: src/slic3r/GUI/Plater.cpp:3831 +msgid "" +"%1% printer was active at the time the target Undo / Redo snapshot was " +"taken. Switching to %1% printer requires reloading of %1% presets." +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:3992 +msgid "Load Project" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:4016 +msgid "Import Object" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:4020 +msgid "Import Objects" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:4075 msgid "All objects will be removed, continue ?" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3511 +#: src/slic3r/GUI/Plater.cpp:4083 +msgid "Delete Selected Objects" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:4091 +msgid "Increase Instances" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:4127 +msgid "Decrease Instances" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:4163 +#, possible-c-format +msgid "Set numbers of copies to %d" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:4193 +msgid "Cut by Plane" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:4225 msgid "Save G-code file as:" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3511 +#: src/slic3r/GUI/Plater.cpp:4225 msgid "Save SL1 file as:" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3623 +#: src/slic3r/GUI/Plater.cpp:4337 #, possible-c-format msgid "STL file exported to %s" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3639 +#: src/slic3r/GUI/Plater.cpp:4353 #, possible-c-format msgid "AMF file exported to %s" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3642 +#: src/slic3r/GUI/Plater.cpp:4356 #, possible-c-format msgid "Error exporting AMF file %s" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3668 +#: src/slic3r/GUI/Plater.cpp:4382 #, possible-c-format msgid "3MF file exported to %s" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3673 +#: src/slic3r/GUI/Plater.cpp:4387 #, possible-c-format msgid "Error exporting 3MF file %s" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3908 +#: src/slic3r/GUI/Plater.cpp:4687 +msgid "Export" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:4688 msgid "Send G-code" msgstr "" -#: src/slic3r/GUI/Preferences.cpp:19 src/slic3r/GUI/Tab.cpp:1849 -#: src/slic3r/GUI/Tab.cpp:2050 +#: src/slic3r/GUI/Plater.cpp:4772 +msgid "Paste From Clipboard" +msgstr "" + +#: src/slic3r/GUI/Preferences.cpp:22 src/slic3r/GUI/Tab.cpp:1955 +#: src/slic3r/GUI/Tab.cpp:2193 msgid "General" msgstr "" -#: src/slic3r/GUI/Preferences.cpp:36 +#: src/slic3r/GUI/Preferences.cpp:44 msgid "Remember output directory" msgstr "" -#: src/slic3r/GUI/Preferences.cpp:38 +#: src/slic3r/GUI/Preferences.cpp:46 msgid "" "If this is enabled, Slic3r will prompt the last output directory instead of " "the one containing the input files." msgstr "" -#: src/slic3r/GUI/Preferences.cpp:44 +#: src/slic3r/GUI/Preferences.cpp:52 msgid "Auto-center parts" msgstr "" -#: src/slic3r/GUI/Preferences.cpp:46 +#: src/slic3r/GUI/Preferences.cpp:54 msgid "" "If this is enabled, Slic3r will auto-center objects around the print bed " "center." msgstr "" -#: src/slic3r/GUI/Preferences.cpp:52 +#: src/slic3r/GUI/Preferences.cpp:60 msgid "Background processing" msgstr "" -#: src/slic3r/GUI/Preferences.cpp:54 +#: src/slic3r/GUI/Preferences.cpp:62 msgid "" "If this is enabled, Slic3r will pre-process objects as soon as they're " "loaded in order to save time when exporting G-code." msgstr "" -#: src/slic3r/GUI/Preferences.cpp:63 +#: src/slic3r/GUI/Preferences.cpp:71 msgid "" "If enabled, PrusaSlicer will check for the new versions of itself online. " "When a new version becomes available a notification is displayed at the next " @@ -3021,7 +3453,7 @@ msgid "" "notification mechanisms, no automatic installation is done." msgstr "" -#: src/slic3r/GUI/Preferences.cpp:71 +#: src/slic3r/GUI/Preferences.cpp:79 msgid "" "If enabled, Slic3r downloads updates of built-in system presets in the " "background. These updates are downloaded into a separate temporary location. " @@ -3029,76 +3461,90 @@ msgid "" "startup." msgstr "" -#: src/slic3r/GUI/Preferences.cpp:76 +#: src/slic3r/GUI/Preferences.cpp:84 msgid "Suppress \" - default - \" presets" msgstr "" -#: src/slic3r/GUI/Preferences.cpp:78 +#: src/slic3r/GUI/Preferences.cpp:86 msgid "" "Suppress \" - default - \" presets in the Print / Filament / Printer " "selections once there are any other valid presets available." msgstr "" -#: src/slic3r/GUI/Preferences.cpp:84 +#: src/slic3r/GUI/Preferences.cpp:92 msgid "Show incompatible print and filament presets" msgstr "" -#: src/slic3r/GUI/Preferences.cpp:86 +#: src/slic3r/GUI/Preferences.cpp:94 msgid "" "When checked, the print and filament presets are shown in the preset editor " "even if they are marked as incompatible with the active printer" msgstr "" -#: src/slic3r/GUI/Preferences.cpp:93 -msgid "Use legacy OpenGL 1.1 rendering" -msgstr "" - -#: src/slic3r/GUI/Preferences.cpp:95 -msgid "" -"If you have rendering issues caused by a buggy OpenGL 2.0 driver, you may " -"try to check this checkbox. This will disable the layer height editing and " -"anti aliasing, so it is likely better to upgrade your graphics driver." -msgstr "" - -#: src/slic3r/GUI/Preferences.cpp:103 +#: src/slic3r/GUI/Preferences.cpp:101 msgid "Use Retina resolution for the 3D scene" msgstr "" -#: src/slic3r/GUI/Preferences.cpp:105 +#: src/slic3r/GUI/Preferences.cpp:103 msgid "" "If enabled, the 3D scene will be rendered in Retina resolution. If you are " "experiencing 3D performance problems, disabling this option may help." msgstr "" -#: src/slic3r/GUI/Preferences.cpp:130 +#: src/slic3r/GUI/Preferences.cpp:110 +msgid "Use perspective camera" +msgstr "" + +#: src/slic3r/GUI/Preferences.cpp:112 +msgid "" +"If enabled, use perspective camera. If not enabled, use orthographic camera." +msgstr "" + +#: src/slic3r/GUI/Preferences.cpp:117 +msgid "Use custom size for toolbar icons" +msgstr "" + +#: src/slic3r/GUI/Preferences.cpp:119 +msgid "If enabled, you can change size of toolbar icons manually." +msgstr "" + +#: src/slic3r/GUI/Preferences.cpp:144 #, possible-c-format msgid "You need to restart %s to make the changes effective." msgstr "" +#: src/slic3r/GUI/Preferences.cpp:192 +msgid "Icon size in a respect to the default size" +msgstr "" + +#: src/slic3r/GUI/Preferences.cpp:207 +msgid "Select toolbar icon size in respect to the default one." +msgstr "" + #: src/slic3r/GUI/Preset.cpp:212 msgid "modified" msgstr "" -#: src/slic3r/GUI/Preset.cpp:938 src/slic3r/GUI/Preset.cpp:978 -#: src/slic3r/GUI/Preset.cpp:1043 src/slic3r/GUI/Preset.cpp:1075 -#: src/slic3r/GUI/PresetBundle.cpp:1478 src/slic3r/GUI/PresetBundle.cpp:1543 +#: src/slic3r/GUI/Preset.cpp:963 src/slic3r/GUI/Preset.cpp:1003 +#: src/slic3r/GUI/Preset.cpp:1068 src/slic3r/GUI/Preset.cpp:1100 +#: src/slic3r/GUI/PresetBundle.cpp:1480 src/slic3r/GUI/PresetBundle.cpp:1545 msgid "System presets" msgstr "" -#: src/slic3r/GUI/Preset.cpp:982 src/slic3r/GUI/Preset.cpp:1079 -#: src/slic3r/GUI/PresetBundle.cpp:1548 +#: src/slic3r/GUI/Preset.cpp:1007 src/slic3r/GUI/Preset.cpp:1104 +#: src/slic3r/GUI/PresetBundle.cpp:1550 msgid "User presets" msgstr "" -#: src/slic3r/GUI/Preset.cpp:1011 src/slic3r/GUI/Tab.cpp:241 +#: src/slic3r/GUI/Preset.cpp:1036 src/slic3r/GUI/Tab.cpp:241 msgid "Add a new printer" msgstr "" -#: src/slic3r/GUI/Preset.cpp:1283 +#: src/slic3r/GUI/Preset.cpp:1308 msgid "filament" msgstr "" -#: src/slic3r/GUI/Preset.cpp:1284 +#: src/slic3r/GUI/Preset.cpp:1309 msgid "SLA print" msgstr "" @@ -3293,10 +3739,10 @@ msgid "Time" msgstr "" #: src/slic3r/GUI/RammingChart.cpp:76 src/slic3r/GUI/WipeTowerDialog.cpp:82 -#: src/libslic3r/PrintConfig.cpp:613 src/libslic3r/PrintConfig.cpp:657 -#: src/libslic3r/PrintConfig.cpp:672 src/libslic3r/PrintConfig.cpp:2278 -#: src/libslic3r/PrintConfig.cpp:2287 src/libslic3r/PrintConfig.cpp:2347 -#: src/libslic3r/PrintConfig.cpp:2354 +#: src/libslic3r/PrintConfig.cpp:627 src/libslic3r/PrintConfig.cpp:671 +#: src/libslic3r/PrintConfig.cpp:686 src/libslic3r/PrintConfig.cpp:2349 +#: src/libslic3r/PrintConfig.cpp:2358 src/libslic3r/PrintConfig.cpp:2418 +#: src/libslic3r/PrintConfig.cpp:2425 msgid "s" msgstr "" @@ -3304,20 +3750,20 @@ msgstr "" msgid "Volumetric speed" msgstr "" -#: src/slic3r/GUI/RammingChart.cpp:81 src/libslic3r/PrintConfig.cpp:570 -#: src/libslic3r/PrintConfig.cpp:1220 +#: src/slic3r/GUI/RammingChart.cpp:81 src/libslic3r/PrintConfig.cpp:584 +#: src/libslic3r/PrintConfig.cpp:1234 msgid "mm³/s" msgstr "" -#: src/slic3r/GUI/SysInfoDialog.cpp:44 +#: src/slic3r/GUI/SysInfoDialog.cpp:78 msgid "System Information" msgstr "" -#: src/slic3r/GUI/SysInfoDialog.cpp:120 +#: src/slic3r/GUI/SysInfoDialog.cpp:154 msgid "Copy to Clipboard" msgstr "" -#: src/slic3r/GUI/Tab.cpp:52 src/libslic3r/PrintConfig.cpp:230 +#: src/slic3r/GUI/Tab.cpp:52 src/libslic3r/PrintConfig.cpp:239 msgid "Compatible printers" msgstr "" @@ -3325,7 +3771,7 @@ msgstr "" msgid "Select the printers this profile is compatible with." msgstr "" -#: src/slic3r/GUI/Tab.cpp:58 src/libslic3r/PrintConfig.cpp:245 +#: src/slic3r/GUI/Tab.cpp:58 src/libslic3r/PrintConfig.cpp:254 msgid "Compatible print profiles" msgstr "" @@ -3349,204 +3795,206 @@ msgid "" "or click this button." msgstr "" -#: src/slic3r/GUI/Tab.cpp:920 -msgid "It's a default preset." -msgstr "" - #: src/slic3r/GUI/Tab.cpp:921 -msgid "It's a system preset." -msgstr "" - -#: src/slic3r/GUI/Tab.cpp:922 -#, possible-c-format -msgid "Current preset is inherited from %s" +msgid "This is a default preset." msgstr "" #: src/slic3r/GUI/Tab.cpp:923 -msgid "default preset" +msgid "This is a system preset." msgstr "" -#: src/slic3r/GUI/Tab.cpp:927 -msgid "It can't be deleted or modified." +#: src/slic3r/GUI/Tab.cpp:925 +msgid "Current preset is inherited from the default preset." msgstr "" #: src/slic3r/GUI/Tab.cpp:928 +#, possible-c-format +msgid "" +"Current preset is inherited from:\n" +"\t%s" +msgstr "" + +#: src/slic3r/GUI/Tab.cpp:932 +msgid "It can't be deleted or modified." +msgstr "" + +#: src/slic3r/GUI/Tab.cpp:933 msgid "" "Any modifications should be saved as a new preset inherited from this one." msgstr "" -#: src/slic3r/GUI/Tab.cpp:929 +#: src/slic3r/GUI/Tab.cpp:934 msgid "To do that please specify a new name for the preset." msgstr "" -#: src/slic3r/GUI/Tab.cpp:933 +#: src/slic3r/GUI/Tab.cpp:938 msgid "Additional information:" msgstr "" -#: src/slic3r/GUI/Tab.cpp:939 +#: src/slic3r/GUI/Tab.cpp:944 msgid "printer model" msgstr "" -#: src/slic3r/GUI/Tab.cpp:947 +#: src/slic3r/GUI/Tab.cpp:952 msgid "default print profile" msgstr "" -#: src/slic3r/GUI/Tab.cpp:950 +#: src/slic3r/GUI/Tab.cpp:955 msgid "default filament profile" msgstr "" -#: src/slic3r/GUI/Tab.cpp:964 +#: src/slic3r/GUI/Tab.cpp:969 msgid "default SLA material profile" msgstr "" -#: src/slic3r/GUI/Tab.cpp:968 +#: src/slic3r/GUI/Tab.cpp:973 msgid "default SLA print profile" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1003 src/slic3r/GUI/Tab.cpp:3419 +#: src/slic3r/GUI/Tab.cpp:1008 src/slic3r/GUI/Tab.cpp:3649 msgid "Layers and perimeters" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1004 src/slic3r/GUI/Tab.cpp:1253 -#: src/libslic3r/PrintConfig.cpp:56 +#: src/slic3r/GUI/Tab.cpp:1009 src/slic3r/GUI/Tab.cpp:1257 +#: src/libslic3r/PrintConfig.cpp:66 msgid "Layer height" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1008 +#: src/slic3r/GUI/Tab.cpp:1013 msgid "Vertical shells" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1019 +#: src/slic3r/GUI/Tab.cpp:1024 msgid "Horizontal shells" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1020 src/libslic3r/PrintConfig.cpp:1745 +#: src/slic3r/GUI/Tab.cpp:1025 src/libslic3r/PrintConfig.cpp:1759 msgid "Solid layers" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1025 +#: src/slic3r/GUI/Tab.cpp:1030 msgid "Quality (slower slicing)" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1043 +#: src/slic3r/GUI/Tab.cpp:1048 msgid "Reducing printing time" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1055 +#: src/slic3r/GUI/Tab.cpp:1060 msgid "Skirt and brim" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1072 +#: src/slic3r/GUI/Tab.cpp:1077 msgid "Raft" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1076 +#: src/slic3r/GUI/Tab.cpp:1081 msgid "Options for support material and raft" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1091 +#: src/slic3r/GUI/Tab.cpp:1096 msgid "Speed for print moves" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1103 +#: src/slic3r/GUI/Tab.cpp:1108 msgid "Speed for non-print moves" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1106 +#: src/slic3r/GUI/Tab.cpp:1111 msgid "Modifiers" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1109 +#: src/slic3r/GUI/Tab.cpp:1114 msgid "Acceleration control (advanced)" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1116 +#: src/slic3r/GUI/Tab.cpp:1121 msgid "Autospeed (advanced)" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1124 +#: src/slic3r/GUI/Tab.cpp:1129 msgid "Multiple Extruders" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1132 +#: src/slic3r/GUI/Tab.cpp:1137 msgid "Ooze prevention" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1149 +#: src/slic3r/GUI/Tab.cpp:1154 msgid "Extrusion width" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1159 +#: src/slic3r/GUI/Tab.cpp:1164 msgid "Overlap" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1162 +#: src/slic3r/GUI/Tab.cpp:1167 msgid "Flow" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1171 +#: src/slic3r/GUI/Tab.cpp:1176 msgid "Other" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1174 src/slic3r/GUI/Tab.cpp:3496 +#: src/slic3r/GUI/Tab.cpp:1179 src/slic3r/GUI/Tab.cpp:3703 msgid "Output options" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1175 +#: src/slic3r/GUI/Tab.cpp:1180 msgid "Sequential printing" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1177 +#: src/slic3r/GUI/Tab.cpp:1182 msgid "Extruder clearance (mm)" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1186 src/slic3r/GUI/Tab.cpp:3497 +#: src/slic3r/GUI/Tab.cpp:1191 src/slic3r/GUI/Tab.cpp:3704 msgid "Output file" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1193 src/libslic3r/PrintConfig.cpp:1418 +#: src/slic3r/GUI/Tab.cpp:1198 src/libslic3r/PrintConfig.cpp:1432 msgid "Post-processing scripts" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1199 src/slic3r/GUI/Tab.cpp:1200 -#: src/slic3r/GUI/Tab.cpp:1612 src/slic3r/GUI/Tab.cpp:1613 -#: src/slic3r/GUI/Tab.cpp:2022 src/slic3r/GUI/Tab.cpp:2023 -#: src/slic3r/GUI/Tab.cpp:2116 src/slic3r/GUI/Tab.cpp:2117 -#: src/slic3r/GUI/Tab.cpp:3385 src/slic3r/GUI/Tab.cpp:3386 +#: src/slic3r/GUI/Tab.cpp:1204 src/slic3r/GUI/Tab.cpp:1205 +#: src/slic3r/GUI/Tab.cpp:1716 src/slic3r/GUI/Tab.cpp:1717 +#: src/slic3r/GUI/Tab.cpp:2165 src/slic3r/GUI/Tab.cpp:2166 +#: src/slic3r/GUI/Tab.cpp:2273 src/slic3r/GUI/Tab.cpp:2274 +#: src/slic3r/GUI/Tab.cpp:3586 src/slic3r/GUI/Tab.cpp:3587 msgid "Notes" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1206 src/slic3r/GUI/Tab.cpp:1620 -#: src/slic3r/GUI/Tab.cpp:2029 src/slic3r/GUI/Tab.cpp:2123 -#: src/slic3r/GUI/Tab.cpp:3393 src/slic3r/GUI/Tab.cpp:3502 +#: src/slic3r/GUI/Tab.cpp:1211 src/slic3r/GUI/Tab.cpp:1724 +#: src/slic3r/GUI/Tab.cpp:2172 src/slic3r/GUI/Tab.cpp:2280 +#: src/slic3r/GUI/Tab.cpp:3594 src/slic3r/GUI/Tab.cpp:3709 msgid "Dependencies" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1207 src/slic3r/GUI/Tab.cpp:1621 -#: src/slic3r/GUI/Tab.cpp:2030 src/slic3r/GUI/Tab.cpp:2124 -#: src/slic3r/GUI/Tab.cpp:3394 src/slic3r/GUI/Tab.cpp:3503 +#: src/slic3r/GUI/Tab.cpp:1212 src/slic3r/GUI/Tab.cpp:1725 +#: src/slic3r/GUI/Tab.cpp:2173 src/slic3r/GUI/Tab.cpp:2281 +#: src/slic3r/GUI/Tab.cpp:3595 src/slic3r/GUI/Tab.cpp:3710 msgid "Profile dependencies" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1253 +#: src/slic3r/GUI/Tab.cpp:1256 msgid "" -"Layer height can't be equal to zero.\n" +"Zero layer height is not valid.\n" "\n" -"Shall I set its value to minimum (0.01)?" +"The layer height will be reset to 0.01." msgstr "" -#: src/slic3r/GUI/Tab.cpp:1266 +#: src/slic3r/GUI/Tab.cpp:1268 msgid "" -"First layer height can't be equal to zero.\n" +"Zero first layer height is not valid.\n" "\n" -"Shall I set its value to minimum (0.01)?" +"The first layer height will be reset to 0.01." msgstr "" -#: src/slic3r/GUI/Tab.cpp:1268 src/libslic3r/PrintConfig.cpp:852 +#: src/slic3r/GUI/Tab.cpp:1269 src/libslic3r/PrintConfig.cpp:866 msgid "First layer height" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1284 +#: src/slic3r/GUI/Tab.cpp:1283 #, possible-c-format msgid "" "The Spiral Vase mode requires:\n" @@ -3559,11 +4007,11 @@ msgid "" "Shall I adjust those settings in order to enable Spiral Vase?" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1291 +#: src/slic3r/GUI/Tab.cpp:1290 msgid "Spiral Vase" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1312 +#: src/slic3r/GUI/Tab.cpp:1311 msgid "" "The Wipe Tower currently supports the non-soluble supports only\n" "if they are printed with the current extruder without triggering a tool " @@ -3574,11 +4022,11 @@ msgid "" "Shall I adjust those settings in order to enable the Wipe Tower?" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1316 src/slic3r/GUI/Tab.cpp:1333 +#: src/slic3r/GUI/Tab.cpp:1315 src/slic3r/GUI/Tab.cpp:1332 msgid "Wipe Tower" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1330 +#: src/slic3r/GUI/Tab.cpp:1329 msgid "" "For the Wipe Tower to work with the soluble supports, the support layers\n" "need to be synchronized with the object layers.\n" @@ -3586,7 +4034,7 @@ msgid "" "Shall I synchronize support layers in order to enable the Wipe Tower?" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1348 +#: src/slic3r/GUI/Tab.cpp:1347 msgid "" "Supports work better, if the following feature is enabled:\n" "- Detect bridging perimeters\n" @@ -3594,103 +4042,116 @@ msgid "" "Shall I adjust those settings for supports?" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1351 +#: src/slic3r/GUI/Tab.cpp:1350 msgid "Support Generator" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1393 +#: src/slic3r/GUI/Tab.cpp:1392 msgid "" "The %1% infill pattern is not supposed to work at 100%% density.\n" "\n" "Shall I switch to rectilinear fill pattern?" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1514 src/libslic3r/PrintConfig.cpp:2015 +#: src/slic3r/GUI/Tab.cpp:1502 src/slic3r/GUI/Tab.cpp:1557 +msgid "Filament Overrides" +msgstr "" + +#: src/slic3r/GUI/Tab.cpp:1503 src/slic3r/GUI/Tab.cpp:1562 +#: src/slic3r/GUI/Tab.cpp:2514 +msgid "Retraction" +msgstr "" + +#: src/slic3r/GUI/Tab.cpp:1612 src/libslic3r/PrintConfig.cpp:2030 msgid "Temperature" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1520 +#: src/slic3r/GUI/Tab.cpp:1618 msgid "Bed" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1525 +#: src/slic3r/GUI/Tab.cpp:1623 msgid "Cooling" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1526 src/libslic3r/PrintConfig.cpp:1321 -#: src/libslic3r/PrintConfig.cpp:2134 +#: src/slic3r/GUI/Tab.cpp:1624 src/libslic3r/PrintConfig.cpp:1335 +#: src/libslic3r/PrintConfig.cpp:2150 msgid "Enable" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1537 +#: src/slic3r/GUI/Tab.cpp:1635 msgid "Fan settings" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1538 +#: src/slic3r/GUI/Tab.cpp:1636 msgid "Fan speed" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1546 +#: src/slic3r/GUI/Tab.cpp:1644 msgid "Cooling thresholds" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1552 +#: src/slic3r/GUI/Tab.cpp:1650 msgid "Filament properties" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1556 +#: src/slic3r/GUI/Tab.cpp:1654 msgid "Print speed override" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1566 +#: src/slic3r/GUI/Tab.cpp:1664 +msgid "Wipe tower parameters" +msgstr "" + +#: src/slic3r/GUI/Tab.cpp:1667 msgid "Toolchange parameters with single extruder MM printers" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1581 +#: src/slic3r/GUI/Tab.cpp:1681 msgid "Ramming settings" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1599 src/slic3r/GUI/Tab.cpp:1985 +#: src/slic3r/GUI/Tab.cpp:1703 src/slic3r/GUI/Tab.cpp:2128 msgid "Custom G-code" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1600 src/slic3r/GUI/Tab.cpp:1986 -#: src/libslic3r/PrintConfig.cpp:1771 src/libslic3r/PrintConfig.cpp:1786 +#: src/slic3r/GUI/Tab.cpp:1704 src/slic3r/GUI/Tab.cpp:2129 +#: src/libslic3r/PrintConfig.cpp:1785 src/libslic3r/PrintConfig.cpp:1800 msgid "Start G-code" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1606 src/slic3r/GUI/Tab.cpp:1992 -#: src/libslic3r/PrintConfig.cpp:360 src/libslic3r/PrintConfig.cpp:370 +#: src/slic3r/GUI/Tab.cpp:1710 src/slic3r/GUI/Tab.cpp:2135 +#: src/libslic3r/PrintConfig.cpp:369 src/libslic3r/PrintConfig.cpp:379 msgid "End G-code" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1737 src/slic3r/GUI/Tab.cpp:1925 +#: src/slic3r/GUI/Tab.cpp:1843 src/slic3r/GUI/Tab.cpp:2068 msgid "Test" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1747 +#: src/slic3r/GUI/Tab.cpp:1853 msgid "Could not get a valid Printer Host reference" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1753 src/slic3r/GUI/Tab.cpp:1938 +#: src/slic3r/GUI/Tab.cpp:1859 src/slic3r/GUI/Tab.cpp:2081 msgid "Success!" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1768 +#: src/slic3r/GUI/Tab.cpp:1874 msgid "" "HTTPS CA file is optional. It is only needed if you use HTTPS with a self-" "signed certificate." msgstr "" -#: src/slic3r/GUI/Tab.cpp:1781 +#: src/slic3r/GUI/Tab.cpp:1887 msgid "Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1782 +#: src/slic3r/GUI/Tab.cpp:1888 msgid "Open CA certificate file" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1810 +#: src/slic3r/GUI/Tab.cpp:1916 #, possible-c-format msgid "" "HTTPS CA File:\n" @@ -3700,278 +4161,283 @@ msgid "" "Store / Keychain." msgstr "" -#: src/slic3r/GUI/Tab.cpp:1850 src/slic3r/GUI/Tab.cpp:2051 +#: src/slic3r/GUI/Tab.cpp:1956 src/slic3r/GUI/Tab.cpp:2194 msgid "Size and coordinates" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1855 src/slic3r/GUI/Tab.cpp:2056 -#: src/slic3r/GUI/Tab.cpp:3040 +#: src/slic3r/GUI/Tab.cpp:1961 src/slic3r/GUI/Tab.cpp:2199 +#: src/slic3r/GUI/Tab.cpp:3256 msgid "Set" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1877 +#: src/slic3r/GUI/Tab.cpp:1993 msgid "Capabilities" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1882 +#: src/slic3r/GUI/Tab.cpp:1998 msgid "Number of extruders of the printer." msgstr "" -#: src/slic3r/GUI/Tab.cpp:1910 +#: src/slic3r/GUI/Tab.cpp:2023 +msgid "" +"Single Extruder Multi Material is selected, \n" +"and all extruders must have the same diameter.\n" +"Do you want to change the diameter for all extruders to first extruder " +"nozzle diameter value?" +msgstr "" + +#: src/slic3r/GUI/Tab.cpp:2026 src/slic3r/GUI/Tab.cpp:2484 +#: src/libslic3r/PrintConfig.cpp:1310 +msgid "Nozzle diameter" +msgstr "" + +#: src/slic3r/GUI/Tab.cpp:2053 msgid "USB/Serial connection" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1911 src/libslic3r/PrintConfig.cpp:1626 +#: src/slic3r/GUI/Tab.cpp:2054 src/libslic3r/PrintConfig.cpp:1640 msgid "Serial port" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1916 +#: src/slic3r/GUI/Tab.cpp:2059 msgid "Rescan serial ports" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1938 +#: src/slic3r/GUI/Tab.cpp:2081 msgid "Connection to printer works correctly." msgstr "" -#: src/slic3r/GUI/Tab.cpp:1941 +#: src/slic3r/GUI/Tab.cpp:2084 msgid "Connection failed." msgstr "" -#: src/slic3r/GUI/Tab.cpp:1954 src/slic3r/GUI/Tab.cpp:2111 +#: src/slic3r/GUI/Tab.cpp:2097 src/slic3r/GUI/Tab.cpp:2268 msgid "Print Host upload" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1998 src/libslic3r/PrintConfig.cpp:129 +#: src/slic3r/GUI/Tab.cpp:2141 src/libslic3r/PrintConfig.cpp:138 msgid "Before layer change G-code" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2004 src/libslic3r/PrintConfig.cpp:1042 +#: src/slic3r/GUI/Tab.cpp:2147 src/libslic3r/PrintConfig.cpp:1056 msgid "After layer change G-code" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2010 src/libslic3r/PrintConfig.cpp:2041 +#: src/slic3r/GUI/Tab.cpp:2153 src/libslic3r/PrintConfig.cpp:2056 msgid "Tool change G-code" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2016 +#: src/slic3r/GUI/Tab.cpp:2159 msgid "Between objects G-code (for sequential printing)" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2078 +#: src/slic3r/GUI/Tab.cpp:2231 msgid "Display" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2089 +#: src/slic3r/GUI/Tab.cpp:2246 msgid "Tilt" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2090 +#: src/slic3r/GUI/Tab.cpp:2247 msgid "Tilt time" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2096 src/slic3r/GUI/Tab.cpp:3367 +#: src/slic3r/GUI/Tab.cpp:2253 src/slic3r/GUI/Tab.cpp:3568 msgid "Corrections" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2173 src/slic3r/GUI/Tab.cpp:2246 -#: src/libslic3r/PrintConfig.cpp:1092 src/libslic3r/PrintConfig.cpp:1110 -#: src/libslic3r/PrintConfig.cpp:1128 src/libslic3r/PrintConfig.cpp:1145 -#: src/libslic3r/PrintConfig.cpp:1156 src/libslic3r/PrintConfig.cpp:1167 -#: src/libslic3r/PrintConfig.cpp:1178 +#: src/slic3r/GUI/Tab.cpp:2333 src/slic3r/GUI/Tab.cpp:2418 +#: src/libslic3r/PrintConfig.cpp:1106 src/libslic3r/PrintConfig.cpp:1124 +#: src/libslic3r/PrintConfig.cpp:1142 src/libslic3r/PrintConfig.cpp:1159 +#: src/libslic3r/PrintConfig.cpp:1170 src/libslic3r/PrintConfig.cpp:1181 +#: src/libslic3r/PrintConfig.cpp:1192 msgid "Machine limits" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2187 +#: src/slic3r/GUI/Tab.cpp:2347 msgid "Values in this column are for Normal mode" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2188 +#: src/slic3r/GUI/Tab.cpp:2348 msgid "Normal" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2193 +#: src/slic3r/GUI/Tab.cpp:2353 msgid "Values in this column are for Stealth mode" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2194 +#: src/slic3r/GUI/Tab.cpp:2354 msgid "Stealth" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2202 +#: src/slic3r/GUI/Tab.cpp:2362 msgid "Maximum feedrates" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2207 +#: src/slic3r/GUI/Tab.cpp:2367 msgid "Maximum accelerations" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2214 +#: src/slic3r/GUI/Tab.cpp:2374 msgid "Jerk limits" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2219 +#: src/slic3r/GUI/Tab.cpp:2379 msgid "Minimum feedrates" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2268 src/slic3r/GUI/Tab.cpp:2276 +#: src/slic3r/GUI/Tab.cpp:2443 src/slic3r/GUI/Tab.cpp:2451 msgid "Single extruder MM setup" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2277 +#: src/slic3r/GUI/Tab.cpp:2452 msgid "Single extruder multimaterial parameters" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2290 src/libslic3r/GCode/PreviewData.cpp:475 +#: src/slic3r/GUI/Tab.cpp:2465 src/libslic3r/GCode/PreviewData.cpp:477 #, possible-c-format msgid "Extruder %d" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2297 +#: src/slic3r/GUI/Tab.cpp:2483 +msgid "Do you want to change the diameter for all extruders?" +msgstr "" + +#: src/slic3r/GUI/Tab.cpp:2506 msgid "Layer height limits" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2302 +#: src/slic3r/GUI/Tab.cpp:2511 msgid "Position (for multi-extruder printers)" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2305 -msgid "Retraction" -msgstr "" - -#: src/slic3r/GUI/Tab.cpp:2308 +#: src/slic3r/GUI/Tab.cpp:2517 msgid "Only lift Z" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2321 +#: src/slic3r/GUI/Tab.cpp:2530 msgid "" "Retraction when tool is disabled (advanced settings for multi-extruder " "setups)" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2480 +#: src/slic3r/GUI/Tab.cpp:2693 msgid "" "The Wipe option is not available when using the Firmware Retraction mode.\n" "\n" "Shall I disable it in order to enable Firmware Retraction?" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2482 +#: src/slic3r/GUI/Tab.cpp:2695 msgid "Firmware Retraction" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2808 +#: src/slic3r/GUI/Tab.cpp:3024 #, possible-c-format msgid "Default preset (%s)" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2809 +#: src/slic3r/GUI/Tab.cpp:3025 #, possible-c-format msgid "Preset (%s)" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2826 +#: src/slic3r/GUI/Tab.cpp:3042 msgid "has the following unsaved changes:" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2829 +#: src/slic3r/GUI/Tab.cpp:3045 msgid "is not compatible with printer" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2830 +#: src/slic3r/GUI/Tab.cpp:3046 msgid "is not compatible with print profile" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2832 +#: src/slic3r/GUI/Tab.cpp:3048 msgid "and it has the following unsaved changes:" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2836 +#: src/slic3r/GUI/Tab.cpp:3052 msgid "Unsaved Changes" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2848 -msgid "Please check your object list before preset changing." -msgstr "" - -#: src/slic3r/GUI/Tab.cpp:2927 +#: src/slic3r/GUI/Tab.cpp:3143 msgid "%1% - Copy" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2950 +#: src/slic3r/GUI/Tab.cpp:3166 msgid "The supplied name is empty. It can't be saved." msgstr "" -#: src/slic3r/GUI/Tab.cpp:2955 +#: src/slic3r/GUI/Tab.cpp:3171 msgid "Cannot overwrite a system profile." msgstr "" -#: src/slic3r/GUI/Tab.cpp:2959 +#: src/slic3r/GUI/Tab.cpp:3175 msgid "Cannot overwrite an external profile." msgstr "" -#: src/slic3r/GUI/Tab.cpp:2985 +#: src/slic3r/GUI/Tab.cpp:3201 msgid "remove" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2985 +#: src/slic3r/GUI/Tab.cpp:3201 msgid "delete" msgstr "" #. TRN remove/delete -#: src/slic3r/GUI/Tab.cpp:2987 +#: src/slic3r/GUI/Tab.cpp:3203 msgid "Are you sure you want to %1% the selected preset?" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2988 -msgid "Remove" -msgstr "" - #. TRN Remove/Delete -#: src/slic3r/GUI/Tab.cpp:2990 +#: src/slic3r/GUI/Tab.cpp:3206 msgid "%1% Preset" msgstr "" -#: src/slic3r/GUI/Tab.cpp:3116 +#: src/slic3r/GUI/Tab.cpp:3332 msgid "LOCKED LOCK" msgstr "" #. TRN Description for "LOCKED LOCK" -#: src/slic3r/GUI/Tab.cpp:3118 +#: src/slic3r/GUI/Tab.cpp:3334 msgid "" -"indicates that the settings are the same as the system values for the " -"current option group" +"indicates that the settings are the same as the system (or default) values " +"for the current option group" msgstr "" -#: src/slic3r/GUI/Tab.cpp:3120 +#: src/slic3r/GUI/Tab.cpp:3336 msgid "UNLOCKED LOCK" msgstr "" #. TRN Description for "UNLOCKED LOCK" -#: src/slic3r/GUI/Tab.cpp:3122 +#: src/slic3r/GUI/Tab.cpp:3338 msgid "" "indicates that some settings were changed and are not equal to the system " -"values for the current option group.\n" +"(or default) values for the current option group.\n" "Click the UNLOCKED LOCK icon to reset all settings for current option group " -"to the system values." +"to the system (or default) values." msgstr "" -#: src/slic3r/GUI/Tab.cpp:3127 +#: src/slic3r/GUI/Tab.cpp:3343 msgid "WHITE BULLET" msgstr "" #. TRN Description for "WHITE BULLET" -#: src/slic3r/GUI/Tab.cpp:3129 +#: src/slic3r/GUI/Tab.cpp:3345 msgid "" -"for the left button: \tindicates a non-system preset,\n" +"for the left button: \tindicates a non-system (or non-default) preset,\n" "for the right button: \tindicates that the settings hasn't been modified." msgstr "" -#: src/slic3r/GUI/Tab.cpp:3103 +#: src/slic3r/GUI/Tab.cpp:3348 msgid "BACK ARROW" msgstr "" #. TRN Description for "BACK ARROW" -#: src/slic3r/GUI/Tab.cpp:3134 +#: src/slic3r/GUI/Tab.cpp:3350 msgid "" "indicates that the settings were changed and are not equal to the last saved " "preset for the current option group.\n" @@ -3979,30 +4445,31 @@ msgid "" "to the last saved preset." msgstr "" -#: src/slic3r/GUI/Tab.cpp:3159 +#: src/slic3r/GUI/Tab.cpp:3360 msgid "" -"LOCKED LOCK icon indicates that the settings are the same as the system " -"values for the current option group" +"LOCKED LOCK icon indicates that the settings are the same as the system (or " +"default) values for the current option group" msgstr "" -#: src/slic3r/GUI/Tab.cpp:3161 +#: src/slic3r/GUI/Tab.cpp:3362 msgid "" "UNLOCKED LOCK icon indicates that some settings were changed and are not " -"equal to the system values for the current option group.\n" -"Click to reset all settings for current option group to the system values." +"equal to the system (or default) values for the current option group.\n" +"Click to reset all settings for current option group to the system (or " +"default) values." msgstr "" -#: src/slic3r/GUI/Tab.cpp:3164 -msgid "WHITE BULLET icon indicates a non system preset." +#: src/slic3r/GUI/Tab.cpp:3365 +msgid "WHITE BULLET icon indicates a non system (or non default) preset." msgstr "" -#: src/slic3r/GUI/Tab.cpp:3167 +#: src/slic3r/GUI/Tab.cpp:3368 msgid "" "WHITE BULLET icon indicates that the settings are the same as in the last " "saved preset for the current option group." msgstr "" -#: src/slic3r/GUI/Tab.cpp:3169 +#: src/slic3r/GUI/Tab.cpp:3370 msgid "" "BACK ARROW icon indicates that the settings were changed and are not equal " "to the last saved preset for the current option group.\n" @@ -4010,25 +4477,26 @@ msgid "" "preset." msgstr "" -#: src/slic3r/GUI/Tab.cpp:3175 +#: src/slic3r/GUI/Tab.cpp:3376 msgid "" -"LOCKED LOCK icon indicates that the value is the same as the system value." +"LOCKED LOCK icon indicates that the value is the same as the system (or " +"default) value." msgstr "" -#: src/slic3r/GUI/Tab.cpp:3176 +#: src/slic3r/GUI/Tab.cpp:3377 msgid "" "UNLOCKED LOCK icon indicates that the value was changed and is not equal to " -"the system value.\n" -"Click to reset current value to the system value." +"the system (or default) value.\n" +"Click to reset current value to the system (or default) value." msgstr "" -#: src/slic3r/GUI/Tab.cpp:3182 +#: src/slic3r/GUI/Tab.cpp:3383 msgid "" "WHITE BULLET icon indicates that the value is the same as in the last saved " "preset." msgstr "" -#: src/slic3r/GUI/Tab.cpp:3183 +#: src/slic3r/GUI/Tab.cpp:3384 msgid "" "BACK ARROW icon indicates that the value was changed and is not equal to the " "last saved preset.\n" @@ -4036,80 +4504,81 @@ msgid "" msgstr "" #. TRN Preset -#: src/slic3r/GUI/Tab.cpp:3296 +#: src/slic3r/GUI/Tab.cpp:3497 #, possible-c-format msgid "Save %s as:" msgstr "" -#: src/slic3r/GUI/Tab.cpp:3340 +#: src/slic3r/GUI/Tab.cpp:3541 msgid "the following suffix is not allowed:" msgstr "" -#: src/slic3r/GUI/Tab.cpp:3344 +#: src/slic3r/GUI/Tab.cpp:3545 msgid "The supplied name is not available." msgstr "" -#: src/slic3r/GUI/Tab.cpp:3357 +#: src/slic3r/GUI/Tab.cpp:3558 msgid "Material" msgstr "" -#: src/slic3r/GUI/Tab.cpp:3359 src/slic3r/GUI/Tab.cpp:3450 +#: src/slic3r/GUI/Tab.cpp:3560 src/slic3r/GUI/Tab.cpp:3651 +#: src/slic3r/GUI/wxExtensions.cpp:454 msgid "Layers" msgstr "" -#: src/slic3r/GUI/Tab.cpp:3363 +#: src/slic3r/GUI/Tab.cpp:3564 msgid "Exposure" msgstr "" -#: src/slic3r/GUI/Tab.cpp:3458 +#: src/slic3r/GUI/Tab.cpp:3659 msgid "Support head" msgstr "" -#: src/slic3r/GUI/Tab.cpp:3463 +#: src/slic3r/GUI/Tab.cpp:3664 msgid "Support pillar" msgstr "" -#: src/slic3r/GUI/Tab.cpp:3473 +#: src/slic3r/GUI/Tab.cpp:3675 msgid "Connection of the support sticks and junctions" msgstr "" -#: src/slic3r/GUI/Tab.cpp:3478 +#: src/slic3r/GUI/Tab.cpp:3680 msgid "Automatic generation" msgstr "" -#: src/slic3r/GUI/Tab.cpp:3540 +#: src/slic3r/GUI/Tab.cpp:3747 msgid "Head penetration should not be greater than the head width." msgstr "" -#: src/slic3r/GUI/Tab.cpp:3541 +#: src/slic3r/GUI/Tab.cpp:3751 msgid "Invalid Head penetration" msgstr "" -#: src/slic3r/GUI/Tab.cpp:3553 +#: src/slic3r/GUI/Tab.cpp:3767 msgid "Pinhead diameter should be smaller than the pillar diameter." msgstr "" -#: src/slic3r/GUI/Tab.cpp:3554 +#: src/slic3r/GUI/Tab.cpp:3771 msgid "Invalid pinhead diameter" msgstr "" -#: src/slic3r/GUI/Tab.hpp:318 src/slic3r/GUI/Tab.hpp:411 +#: src/slic3r/GUI/Tab.hpp:324 src/slic3r/GUI/Tab.hpp:422 msgid "Print Settings" msgstr "" -#: src/slic3r/GUI/Tab.hpp:337 +#: src/slic3r/GUI/Tab.hpp:348 msgid "Filament Settings" msgstr "" -#: src/slic3r/GUI/Tab.hpp:372 +#: src/slic3r/GUI/Tab.hpp:383 msgid "Printer Settings" msgstr "" -#: src/slic3r/GUI/Tab.hpp:396 +#: src/slic3r/GUI/Tab.hpp:407 msgid "Material Settings" msgstr "" -#: src/slic3r/GUI/Tab.hpp:423 +#: src/slic3r/GUI/Tab.hpp:434 msgid "Save preset" msgstr "" @@ -4122,39 +4591,39 @@ msgstr "" msgid "New version of %s is available" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:46 +#: src/slic3r/GUI/UpdateDialogs.cpp:45 msgid "Current version:" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:48 +#: src/slic3r/GUI/UpdateDialogs.cpp:47 msgid "New version:" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:56 +#: src/slic3r/GUI/UpdateDialogs.cpp:55 msgid "Changelog && Download" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:63 src/slic3r/GUI/UpdateDialogs.cpp:126 +#: src/slic3r/GUI/UpdateDialogs.cpp:62 src/slic3r/GUI/UpdateDialogs.cpp:125 msgid "Open changelog page" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:68 +#: src/slic3r/GUI/UpdateDialogs.cpp:67 msgid "Open download page" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:74 +#: src/slic3r/GUI/UpdateDialogs.cpp:73 msgid "Don't notify about new releases any more" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:92 src/slic3r/GUI/UpdateDialogs.cpp:206 +#: src/slic3r/GUI/UpdateDialogs.cpp:91 src/slic3r/GUI/UpdateDialogs.cpp:205 msgid "Configuration update" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:92 +#: src/slic3r/GUI/UpdateDialogs.cpp:91 msgid "Configuration update is available" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:95 +#: src/slic3r/GUI/UpdateDialogs.cpp:94 msgid "" "Would you like to install it?\n" "\n" @@ -4164,21 +4633,21 @@ msgid "" "Updated configuration bundles:" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:116 +#: src/slic3r/GUI/UpdateDialogs.cpp:115 msgid "Comment:" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:150 +#: src/slic3r/GUI/UpdateDialogs.cpp:149 #, possible-c-format msgid "%s incompatibility" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:151 +#: src/slic3r/GUI/UpdateDialogs.cpp:150 #, possible-c-format msgid "%s configuration is incompatible" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:156 +#: src/slic3r/GUI/UpdateDialogs.cpp:155 #, possible-c-format msgid "" "This version of %s is not compatible with currently installed configuration " @@ -4191,25 +4660,25 @@ msgid "" "existing configuration before installing files compatible with this %s.\n" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:165 +#: src/slic3r/GUI/UpdateDialogs.cpp:164 #, possible-c-format msgid "This %s version: %s" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:170 +#: src/slic3r/GUI/UpdateDialogs.cpp:169 msgid "Incompatible bundles:" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:186 +#: src/slic3r/GUI/UpdateDialogs.cpp:185 #, possible-c-format msgid "Exit %s" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:189 +#: src/slic3r/GUI/UpdateDialogs.cpp:188 msgid "Re-configure" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:210 +#: src/slic3r/GUI/UpdateDialogs.cpp:209 #, possible-c-format msgid "" "%s now uses an updated configuration structure.\n" @@ -4225,7 +4694,7 @@ msgid "" "choose whether to enable automatic preset updates." msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:226 +#: src/slic3r/GUI/UpdateDialogs.cpp:225 msgid "For more information please visit our wiki page:" msgstr "" @@ -4318,21 +4787,37 @@ msgstr "" msgid "Show advanced settings" msgstr "" -#: src/slic3r/GUI/wxExtensions.cpp:444 +#: src/slic3r/GUI/wxExtensions.cpp:443 msgid "Instances" msgstr "" -#: src/slic3r/GUI/wxExtensions.cpp:451 src/slic3r/GUI/wxExtensions.cpp:518 +#: src/slic3r/GUI/wxExtensions.cpp:447 src/slic3r/GUI/wxExtensions.cpp:592 #, possible-c-format msgid "Instance %d" msgstr "" -#: src/slic3r/GUI/wxExtensions.cpp:2508 +#: src/slic3r/GUI/wxExtensions.cpp:486 +msgid "Range" +msgstr "" + +#: src/slic3r/GUI/wxExtensions.cpp:2570 +msgid "One layer mode" +msgstr "" + +#: src/slic3r/GUI/wxExtensions.cpp:2571 +msgid "Add/Del color change" +msgstr "" + +#: src/slic3r/GUI/wxExtensions.cpp:2572 +msgid "Discard all color changes" +msgstr "" + +#: src/slic3r/GUI/wxExtensions.cpp:2832 #, possible-c-format msgid "Switch to the %s mode" msgstr "" -#: src/slic3r/GUI/wxExtensions.cpp:2509 +#: src/slic3r/GUI/wxExtensions.cpp:2833 #, possible-c-format msgid "Current mode is %s" msgstr "" @@ -4382,17 +4867,17 @@ msgstr "" msgid "Could not connect to Prusa SLA" msgstr "" -#: src/slic3r/Utils/PresetUpdater.cpp:584 +#: src/slic3r/Utils/PresetUpdater.cpp:614 #, possible-c-format msgid "requires min. %s and max. %s" msgstr "" -#: src/slic3r/Utils/PresetUpdater.cpp:589 +#: src/slic3r/Utils/PresetUpdater.cpp:619 #, possible-c-format msgid "requires min. %s" msgstr "" -#: src/slic3r/Utils/PresetUpdater.cpp:591 +#: src/slic3r/Utils/PresetUpdater.cpp:621 #, possible-c-format msgid "requires max. %s" msgstr "" @@ -4478,215 +4963,219 @@ msgstr "" msgid "Model repair failed: \n" msgstr "" -#: src/libslic3r/Zipper.cpp:35 +#: src/libslic3r/Zipper.cpp:32 msgid "undefined error" msgstr "" -#: src/libslic3r/Zipper.cpp:37 +#: src/libslic3r/Zipper.cpp:34 msgid "too many files" msgstr "" -#: src/libslic3r/Zipper.cpp:39 +#: src/libslic3r/Zipper.cpp:36 msgid "file too large" msgstr "" -#: src/libslic3r/Zipper.cpp:41 +#: src/libslic3r/Zipper.cpp:38 msgid "unsupported method" msgstr "" -#: src/libslic3r/Zipper.cpp:43 +#: src/libslic3r/Zipper.cpp:40 msgid "unsupported encryption" msgstr "" -#: src/libslic3r/Zipper.cpp:45 +#: src/libslic3r/Zipper.cpp:42 msgid "unsupported feature" msgstr "" -#: src/libslic3r/Zipper.cpp:47 +#: src/libslic3r/Zipper.cpp:44 msgid "failed finding central directory" msgstr "" -#: src/libslic3r/Zipper.cpp:49 +#: src/libslic3r/Zipper.cpp:46 msgid "not a ZIP archive" msgstr "" -#: src/libslic3r/Zipper.cpp:51 +#: src/libslic3r/Zipper.cpp:48 msgid "invalid header or archive is corrupted" msgstr "" -#: src/libslic3r/Zipper.cpp:53 +#: src/libslic3r/Zipper.cpp:50 msgid "unsupported multidisk archive" msgstr "" -#: src/libslic3r/Zipper.cpp:55 +#: src/libslic3r/Zipper.cpp:52 msgid "decompression failed or archive is corrupted" msgstr "" -#: src/libslic3r/Zipper.cpp:57 +#: src/libslic3r/Zipper.cpp:54 msgid "compression failed" msgstr "" -#: src/libslic3r/Zipper.cpp:59 +#: src/libslic3r/Zipper.cpp:56 msgid "unexpected decompressed size" msgstr "" -#: src/libslic3r/Zipper.cpp:61 +#: src/libslic3r/Zipper.cpp:58 msgid "CRC-32 check failed" msgstr "" -#: src/libslic3r/Zipper.cpp:63 +#: src/libslic3r/Zipper.cpp:60 msgid "unsupported central directory size" msgstr "" -#: src/libslic3r/Zipper.cpp:65 +#: src/libslic3r/Zipper.cpp:62 msgid "allocation failed" msgstr "" -#: src/libslic3r/Zipper.cpp:67 +#: src/libslic3r/Zipper.cpp:64 msgid "file open failed" msgstr "" -#: src/libslic3r/Zipper.cpp:69 +#: src/libslic3r/Zipper.cpp:66 msgid "file create failed" msgstr "" -#: src/libslic3r/Zipper.cpp:71 +#: src/libslic3r/Zipper.cpp:68 msgid "file write failed" msgstr "" -#: src/libslic3r/Zipper.cpp:73 +#: src/libslic3r/Zipper.cpp:70 msgid "file read failed" msgstr "" -#: src/libslic3r/Zipper.cpp:75 +#: src/libslic3r/Zipper.cpp:72 msgid "file close failed" msgstr "" -#: src/libslic3r/Zipper.cpp:77 +#: src/libslic3r/Zipper.cpp:74 msgid "file seek failed" msgstr "" -#: src/libslic3r/Zipper.cpp:79 +#: src/libslic3r/Zipper.cpp:76 msgid "file stat failed" msgstr "" -#: src/libslic3r/Zipper.cpp:81 +#: src/libslic3r/Zipper.cpp:78 msgid "invalid parameter" msgstr "" -#: src/libslic3r/Zipper.cpp:83 +#: src/libslic3r/Zipper.cpp:80 msgid "invalid filename" msgstr "" -#: src/libslic3r/Zipper.cpp:85 +#: src/libslic3r/Zipper.cpp:82 msgid "buffer too small" msgstr "" -#: src/libslic3r/Zipper.cpp:87 +#: src/libslic3r/Zipper.cpp:84 msgid "internal error" msgstr "" -#: src/libslic3r/Zipper.cpp:89 +#: src/libslic3r/Zipper.cpp:86 msgid "file not found" msgstr "" -#: src/libslic3r/Zipper.cpp:91 +#: src/libslic3r/Zipper.cpp:88 msgid "archive is too large" msgstr "" -#: src/libslic3r/Zipper.cpp:93 +#: src/libslic3r/Zipper.cpp:90 msgid "validation failed" msgstr "" -#: src/libslic3r/Zipper.cpp:95 +#: src/libslic3r/Zipper.cpp:92 msgid "write calledback failed" msgstr "" -#: src/libslic3r/Zipper.cpp:105 +#: src/libslic3r/Zipper.cpp:102 msgid "Error with zip archive" msgstr "" -#: src/libslic3r/Print.cpp:1135 +#: src/libslic3r/Print.cpp:1093 msgid "All objects are outside of the print volume." msgstr "" -#: src/libslic3r/Print.cpp:1162 +#: src/libslic3r/Print.cpp:1120 msgid "Some objects are too close; your extruder will collide with them." msgstr "" -#: src/libslic3r/Print.cpp:1177 +#: src/libslic3r/Print.cpp:1135 msgid "" "Some objects are too tall and cannot be printed without extruder collisions." msgstr "" -#: src/libslic3r/Print.cpp:1187 +#: src/libslic3r/Print.cpp:1145 msgid "The Spiral Vase option can only be used when printing a single object." msgstr "" -#: src/libslic3r/Print.cpp:1189 +#: src/libslic3r/Print.cpp:1147 msgid "" "The Spiral Vase option can only be used when printing single material " "objects." msgstr "" -#: src/libslic3r/Print.cpp:1195 +#: src/libslic3r/Print.cpp:1155 msgid "" -"All extruders must have the same diameter for single extruder multimaterial " -"printer." +"The wipe tower is only supported if all extruders have the same nozzle " +"diameter and use filaments of the same diameter." msgstr "" -#: src/libslic3r/Print.cpp:1200 +#: src/libslic3r/Print.cpp:1159 msgid "" "The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter " "and Repetier G-code flavors." msgstr "" -#: src/libslic3r/Print.cpp:1202 +#: src/libslic3r/Print.cpp:1161 msgid "" "The Wipe Tower is currently only supported with the relative extruder " "addressing (use_relative_e_distances=1)." msgstr "" -#: src/libslic3r/Print.cpp:1223 +#: src/libslic3r/Print.cpp:1165 +msgid "All extruders must have the same diameter for the Wipe Tower." +msgstr "" + +#: src/libslic3r/Print.cpp:1186 msgid "" "The Wipe Tower is only supported for multiple objects if they have equal " "layer heights" msgstr "" -#: src/libslic3r/Print.cpp:1225 +#: src/libslic3r/Print.cpp:1188 msgid "" "The Wipe Tower is only supported for multiple objects if they are printed " "over an equal number of raft layers" msgstr "" -#: src/libslic3r/Print.cpp:1227 +#: src/libslic3r/Print.cpp:1190 msgid "" "The Wipe Tower is only supported for multiple objects if they are printed " "with the same support_material_contact_distance" msgstr "" -#: src/libslic3r/Print.cpp:1229 +#: src/libslic3r/Print.cpp:1192 msgid "" "The Wipe Tower is only supported for multiple objects if they are sliced " "equally." msgstr "" -#: src/libslic3r/Print.cpp:1258 +#: src/libslic3r/Print.cpp:1220 msgid "" "The Wipe tower is only supported if all objects have the same layer height " "profile" msgstr "" -#: src/libslic3r/Print.cpp:1268 +#: src/libslic3r/Print.cpp:1230 msgid "The supplied settings will cause an empty print." msgstr "" -#: src/libslic3r/Print.cpp:1285 +#: src/libslic3r/Print.cpp:1247 msgid "" "One or more object were assigned an extruder that the printer does not have." msgstr "" -#: src/libslic3r/Print.cpp:1294 +#: src/libslic3r/Print.cpp:1256 msgid "" "Printing with multiple extruders of differing nozzle diameters. If support " "is to be printed with the current extruder (support_material_extruder == 0 " @@ -4694,13 +5183,13 @@ msgid "" "same diameter." msgstr "" -#: src/libslic3r/Print.cpp:1302 +#: src/libslic3r/Print.cpp:1264 msgid "" "For the Wipe Tower to work with the soluble supports, the support layers " "need to be synchronized with the object layers." msgstr "" -#: src/libslic3r/Print.cpp:1306 +#: src/libslic3r/Print.cpp:1268 msgid "" "The Wipe Tower currently supports the non-soluble supports only if they are " "printed with the current extruder without triggering a tool change. (both " @@ -4708,83 +5197,90 @@ msgid "" "set to 0)." msgstr "" -#: src/libslic3r/Print.cpp:1328 +#: src/libslic3r/Print.cpp:1290 msgid "First layer height can't be greater than nozzle diameter" msgstr "" -#: src/libslic3r/Print.cpp:1332 +#: src/libslic3r/Print.cpp:1294 msgid "Layer height can't be greater than nozzle diameter" msgstr "" -#: src/libslic3r/Print.cpp:1476 +#: src/libslic3r/Print.cpp:1438 msgid "Infilling layers" msgstr "" -#: src/libslic3r/Print.cpp:1484 +#: src/libslic3r/Print.cpp:1446 msgid "Generating skirt" msgstr "" -#: src/libslic3r/Print.cpp:1492 +#: src/libslic3r/Print.cpp:1454 msgid "Generating brim" msgstr "" -#: src/libslic3r/Print.cpp:1520 +#: src/libslic3r/Print.cpp:1482 msgid "Exporting G-code" msgstr "" -#: src/libslic3r/Print.cpp:1524 +#: src/libslic3r/Print.cpp:1486 msgid "Generating G-code" msgstr "" -#: src/libslic3r/SLAPrint.cpp:57 +#: src/libslic3r/SLAPrint.cpp:58 msgid "Slicing model" msgstr "" -#: src/libslic3r/SLAPrint.cpp:58 src/libslic3r/SLAPrint.cpp:819 +#: src/libslic3r/SLAPrint.cpp:59 src/libslic3r/SLAPrint.cpp:871 msgid "Generating support points" msgstr "" -#: src/libslic3r/SLAPrint.cpp:59 +#: src/libslic3r/SLAPrint.cpp:60 msgid "Generating support tree" msgstr "" -#: src/libslic3r/SLAPrint.cpp:60 +#: src/libslic3r/SLAPrint.cpp:61 msgid "Generating pad" msgstr "" -#: src/libslic3r/SLAPrint.cpp:61 +#: src/libslic3r/SLAPrint.cpp:62 msgid "Slicing supports" msgstr "" -#: src/libslic3r/SLAPrint.cpp:78 +#: src/libslic3r/SLAPrint.cpp:79 msgid "Merging slices and calculating statistics" msgstr "" -#: src/libslic3r/SLAPrint.cpp:79 +#: src/libslic3r/SLAPrint.cpp:80 msgid "Rasterizing layers" msgstr "" -#: src/libslic3r/SLAPrint.cpp:622 +#: src/libslic3r/SLAPrint.cpp:650 msgid "" "Cannot proceed without support points! Add support points or disable support " "generation." msgstr "" -#: src/libslic3r/SLAPrint.cpp:634 +#: src/libslic3r/SLAPrint.cpp:664 msgid "Elevation is too low for object." msgstr "" -#. TRN To be shown at the status bar on SLA slicing error. -#: src/libslic3r/SLAPrint.cpp:719 -msgid "Slicing had to be stopped due to an internal error." +#: src/libslic3r/SLAPrint.cpp:670 +msgid "" +"The endings of the support pillars will be deployed on the gap between the " +"object and the pad. 'Support base safety distance' has to be greater than " +"the 'Pad object gap' parameter to avoid this." msgstr "" -#: src/libslic3r/SLAPrint.cpp:867 src/libslic3r/SLAPrint.cpp:877 -#: src/libslic3r/SLAPrint.cpp:925 +#: src/libslic3r/SLAPrint.cpp:759 +msgid "" +"Slicing had to be stopped due to an internal error: Inconsistent slice index." +msgstr "" + +#: src/libslic3r/SLAPrint.cpp:954 src/libslic3r/SLAPrint.cpp:964 +#: src/libslic3r/SLAPrint.cpp:1005 msgid "Visualizing supports" msgstr "" -#: src/libslic3r/SLAPrint.cpp:1463 +#: src/libslic3r/SLAPrint.cpp:1537 msgid "Slicing done" msgstr "" @@ -4800,101 +5296,109 @@ msgstr "" msgid "Bed shape" msgstr "" -#: src/libslic3r/PrintConfig.cpp:58 +#: src/libslic3r/PrintConfig.cpp:56 +msgid "Bed custom texture" +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:61 +msgid "Bed custom model" +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:68 msgid "" "This setting controls the height (and thus the total number) of the slices/" "layers. Thinner layers give better accuracy but take more time to print." msgstr "" -#: src/libslic3r/PrintConfig.cpp:65 +#: src/libslic3r/PrintConfig.cpp:75 msgid "Max print height" msgstr "" -#: src/libslic3r/PrintConfig.cpp:66 +#: src/libslic3r/PrintConfig.cpp:76 msgid "" "Set this to the maximum height that can be reached by your extruder while " "printing." msgstr "" -#: src/libslic3r/PrintConfig.cpp:72 +#: src/libslic3r/PrintConfig.cpp:82 msgid "Slice gap closing radius" msgstr "" -#: src/libslic3r/PrintConfig.cpp:74 +#: src/libslic3r/PrintConfig.cpp:84 msgid "" "Cracks smaller than 2x gap closing radius are being filled during the " "triangle mesh slicing. The gap closing operation may reduce the final print " "resolution, therefore it is advisable to keep the value reasonably low." msgstr "" -#: src/libslic3r/PrintConfig.cpp:82 +#: src/libslic3r/PrintConfig.cpp:92 msgid "Hostname, IP or URL" msgstr "" -#: src/libslic3r/PrintConfig.cpp:83 +#: src/libslic3r/PrintConfig.cpp:93 msgid "" "Slic3r can upload G-code files to a printer host. This field should contain " "the hostname, IP address or URL of the printer host instance." msgstr "" -#: src/libslic3r/PrintConfig.cpp:89 +#: src/libslic3r/PrintConfig.cpp:99 msgid "API Key / Password" msgstr "" -#: src/libslic3r/PrintConfig.cpp:90 +#: src/libslic3r/PrintConfig.cpp:100 msgid "" "Slic3r can upload G-code files to a printer host. This field should contain " "the API Key or the password required for authentication." msgstr "" -#: src/libslic3r/PrintConfig.cpp:96 +#: src/libslic3r/PrintConfig.cpp:106 msgid "HTTPS CA File" msgstr "" -#: src/libslic3r/PrintConfig.cpp:97 +#: src/libslic3r/PrintConfig.cpp:107 msgid "" "Custom CA certificate file can be specified for HTTPS OctoPrint connections, " "in crt/pem format. If left blank, the default OS CA certificate repository " "is used." msgstr "" -#: src/libslic3r/PrintConfig.cpp:112 +#: src/libslic3r/PrintConfig.cpp:121 msgid "Avoid crossing perimeters" msgstr "" -#: src/libslic3r/PrintConfig.cpp:113 +#: src/libslic3r/PrintConfig.cpp:122 msgid "" "Optimize travel moves in order to minimize the crossing of perimeters. This " "is mostly useful with Bowden extruders which suffer from oozing. This " "feature slows down both the print and the G-code generation." msgstr "" -#: src/libslic3r/PrintConfig.cpp:120 src/libslic3r/PrintConfig.cpp:2012 +#: src/libslic3r/PrintConfig.cpp:129 src/libslic3r/PrintConfig.cpp:2027 msgid "Other layers" msgstr "" -#: src/libslic3r/PrintConfig.cpp:121 +#: src/libslic3r/PrintConfig.cpp:130 msgid "" "Bed temperature for layers after the first one. Set this to zero to disable " "bed temperature control commands in the output." msgstr "" -#: src/libslic3r/PrintConfig.cpp:123 +#: src/libslic3r/PrintConfig.cpp:132 msgid "Bed temperature" msgstr "" -#: src/libslic3r/PrintConfig.cpp:130 +#: src/libslic3r/PrintConfig.cpp:139 msgid "" "This custom code is inserted at every layer change, right before the Z move. " "Note that you can use placeholder variables for all Slic3r settings as well " "as [layer_num] and [layer_z]." msgstr "" -#: src/libslic3r/PrintConfig.cpp:140 +#: src/libslic3r/PrintConfig.cpp:149 msgid "Between objects G-code" msgstr "" -#: src/libslic3r/PrintConfig.cpp:141 +#: src/libslic3r/PrintConfig.cpp:150 msgid "" "This code is inserted between objects when using sequential printing. By " "default extruder and bed temperature are reset using non-wait command; " @@ -4904,70 +5408,70 @@ msgid "" "S[first_layer_temperature]\" command wherever you want." msgstr "" -#: src/libslic3r/PrintConfig.cpp:152 +#: src/libslic3r/PrintConfig.cpp:161 msgid "Number of solid layers to generate on bottom surfaces." msgstr "" -#: src/libslic3r/PrintConfig.cpp:153 +#: src/libslic3r/PrintConfig.cpp:162 msgid "Bottom solid layers" msgstr "" -#: src/libslic3r/PrintConfig.cpp:158 +#: src/libslic3r/PrintConfig.cpp:167 msgid "Bridge" msgstr "" -#: src/libslic3r/PrintConfig.cpp:159 +#: src/libslic3r/PrintConfig.cpp:168 msgid "" "This is the acceleration your printer will use for bridges. Set zero to " "disable acceleration control for bridges." msgstr "" -#: src/libslic3r/PrintConfig.cpp:161 src/libslic3r/PrintConfig.cpp:304 -#: src/libslic3r/PrintConfig.cpp:826 src/libslic3r/PrintConfig.cpp:947 -#: src/libslic3r/PrintConfig.cpp:1116 src/libslic3r/PrintConfig.cpp:1169 -#: src/libslic3r/PrintConfig.cpp:1180 src/libslic3r/PrintConfig.cpp:1369 +#: src/libslic3r/PrintConfig.cpp:170 src/libslic3r/PrintConfig.cpp:313 +#: src/libslic3r/PrintConfig.cpp:840 src/libslic3r/PrintConfig.cpp:961 +#: src/libslic3r/PrintConfig.cpp:1130 src/libslic3r/PrintConfig.cpp:1183 +#: src/libslic3r/PrintConfig.cpp:1194 src/libslic3r/PrintConfig.cpp:1383 msgid "mm/s²" msgstr "" -#: src/libslic3r/PrintConfig.cpp:167 +#: src/libslic3r/PrintConfig.cpp:176 msgid "Bridging angle" msgstr "" -#: src/libslic3r/PrintConfig.cpp:169 +#: src/libslic3r/PrintConfig.cpp:178 msgid "" "Bridging angle override. If left to zero, the bridging angle will be " "calculated automatically. Otherwise the provided angle will be used for all " "bridges. Use 180° for zero angle." msgstr "" -#: src/libslic3r/PrintConfig.cpp:172 src/libslic3r/PrintConfig.cpp:744 -#: src/libslic3r/PrintConfig.cpp:1605 src/libslic3r/PrintConfig.cpp:1615 -#: src/libslic3r/PrintConfig.cpp:1843 src/libslic3r/PrintConfig.cpp:1997 -#: src/libslic3r/PrintConfig.cpp:2181 src/libslic3r/PrintConfig.cpp:2498 -#: src/libslic3r/PrintConfig.cpp:2607 +#: src/libslic3r/PrintConfig.cpp:181 src/libslic3r/PrintConfig.cpp:758 +#: src/libslic3r/PrintConfig.cpp:1619 src/libslic3r/PrintConfig.cpp:1629 +#: src/libslic3r/PrintConfig.cpp:1858 src/libslic3r/PrintConfig.cpp:2012 +#: src/libslic3r/PrintConfig.cpp:2197 src/libslic3r/PrintConfig.cpp:2582 +#: src/libslic3r/PrintConfig.cpp:2693 msgid "°" msgstr "" -#: src/libslic3r/PrintConfig.cpp:178 +#: src/libslic3r/PrintConfig.cpp:187 msgid "Bridges fan speed" msgstr "" -#: src/libslic3r/PrintConfig.cpp:179 +#: src/libslic3r/PrintConfig.cpp:188 msgid "This fan speed is enforced during all bridges and overhangs." msgstr "" -#: src/libslic3r/PrintConfig.cpp:180 src/libslic3r/PrintConfig.cpp:756 -#: src/libslic3r/PrintConfig.cpp:1189 src/libslic3r/PrintConfig.cpp:1252 -#: src/libslic3r/PrintConfig.cpp:1497 src/libslic3r/PrintConfig.cpp:2295 -#: src/libslic3r/PrintConfig.cpp:2537 +#: src/libslic3r/PrintConfig.cpp:189 src/libslic3r/PrintConfig.cpp:770 +#: src/libslic3r/PrintConfig.cpp:1203 src/libslic3r/PrintConfig.cpp:1266 +#: src/libslic3r/PrintConfig.cpp:1511 src/libslic3r/PrintConfig.cpp:2366 +#: src/libslic3r/PrintConfig.cpp:2623 msgid "%" msgstr "" -#: src/libslic3r/PrintConfig.cpp:187 +#: src/libslic3r/PrintConfig.cpp:196 msgid "Bridge flow ratio" msgstr "" -#: src/libslic3r/PrintConfig.cpp:189 +#: src/libslic3r/PrintConfig.cpp:198 msgid "" "This factor affects the amount of plastic for bridging. You can decrease it " "slightly to pull the extrudates and prevent sagging, although default " @@ -4975,83 +5479,83 @@ msgid "" "before tweaking this." msgstr "" -#: src/libslic3r/PrintConfig.cpp:199 +#: src/libslic3r/PrintConfig.cpp:208 msgid "Bridges" msgstr "" -#: src/libslic3r/PrintConfig.cpp:201 +#: src/libslic3r/PrintConfig.cpp:210 msgid "Speed for printing bridges." msgstr "" -#: src/libslic3r/PrintConfig.cpp:202 src/libslic3r/PrintConfig.cpp:578 -#: src/libslic3r/PrintConfig.cpp:586 src/libslic3r/PrintConfig.cpp:595 -#: src/libslic3r/PrintConfig.cpp:603 src/libslic3r/PrintConfig.cpp:630 -#: src/libslic3r/PrintConfig.cpp:649 src/libslic3r/PrintConfig.cpp:885 -#: src/libslic3r/PrintConfig.cpp:1012 src/libslic3r/PrintConfig.cpp:1098 -#: src/libslic3r/PrintConfig.cpp:1134 src/libslic3r/PrintConfig.cpp:1147 -#: src/libslic3r/PrintConfig.cpp:1158 src/libslic3r/PrintConfig.cpp:1211 -#: src/libslic3r/PrintConfig.cpp:1270 src/libslic3r/PrintConfig.cpp:1398 -#: src/libslic3r/PrintConfig.cpp:1572 src/libslic3r/PrintConfig.cpp:1581 -#: src/libslic3r/PrintConfig.cpp:1976 src/libslic3r/PrintConfig.cpp:2088 +#: src/libslic3r/PrintConfig.cpp:211 src/libslic3r/PrintConfig.cpp:592 +#: src/libslic3r/PrintConfig.cpp:600 src/libslic3r/PrintConfig.cpp:609 +#: src/libslic3r/PrintConfig.cpp:617 src/libslic3r/PrintConfig.cpp:644 +#: src/libslic3r/PrintConfig.cpp:663 src/libslic3r/PrintConfig.cpp:899 +#: src/libslic3r/PrintConfig.cpp:1026 src/libslic3r/PrintConfig.cpp:1112 +#: src/libslic3r/PrintConfig.cpp:1148 src/libslic3r/PrintConfig.cpp:1161 +#: src/libslic3r/PrintConfig.cpp:1172 src/libslic3r/PrintConfig.cpp:1225 +#: src/libslic3r/PrintConfig.cpp:1284 src/libslic3r/PrintConfig.cpp:1412 +#: src/libslic3r/PrintConfig.cpp:1586 src/libslic3r/PrintConfig.cpp:1595 +#: src/libslic3r/PrintConfig.cpp:1991 src/libslic3r/PrintConfig.cpp:2104 msgid "mm/s" msgstr "" -#: src/libslic3r/PrintConfig.cpp:209 +#: src/libslic3r/PrintConfig.cpp:218 msgid "Brim width" msgstr "" -#: src/libslic3r/PrintConfig.cpp:210 +#: src/libslic3r/PrintConfig.cpp:219 msgid "" "Horizontal width of the brim that will be printed around each object on the " "first layer." msgstr "" -#: src/libslic3r/PrintConfig.cpp:217 +#: src/libslic3r/PrintConfig.cpp:226 msgid "Clip multi-part objects" msgstr "" -#: src/libslic3r/PrintConfig.cpp:218 +#: src/libslic3r/PrintConfig.cpp:227 msgid "" "When printing multi-material objects, this settings will make Slic3r to clip " "the overlapping object parts one by the other (2nd part will be clipped by " "the 1st, 3rd part will be clipped by the 1st and 2nd etc)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:225 +#: src/libslic3r/PrintConfig.cpp:234 msgid "Colorprint height" msgstr "" -#: src/libslic3r/PrintConfig.cpp:226 +#: src/libslic3r/PrintConfig.cpp:235 msgid "Heights at which a filament change is to occur." msgstr "" -#: src/libslic3r/PrintConfig.cpp:236 +#: src/libslic3r/PrintConfig.cpp:245 msgid "Compatible printers condition" msgstr "" -#: src/libslic3r/PrintConfig.cpp:237 +#: src/libslic3r/PrintConfig.cpp:246 msgid "" "A boolean expression using the configuration values of an active printer " "profile. If this expression evaluates to true, this profile is considered " "compatible with the active printer profile." msgstr "" -#: src/libslic3r/PrintConfig.cpp:251 +#: src/libslic3r/PrintConfig.cpp:260 msgid "Compatible print profiles condition" msgstr "" -#: src/libslic3r/PrintConfig.cpp:252 +#: src/libslic3r/PrintConfig.cpp:261 msgid "" "A boolean expression using the configuration values of an active print " "profile. If this expression evaluates to true, this profile is considered " "compatible with the active print profile." msgstr "" -#: src/libslic3r/PrintConfig.cpp:269 +#: src/libslic3r/PrintConfig.cpp:278 msgid "Complete individual objects" msgstr "" -#: src/libslic3r/PrintConfig.cpp:270 +#: src/libslic3r/PrintConfig.cpp:279 msgid "" "When printing multiple objects or copies, this feature will complete each " "object before moving onto next one (and starting it from its bottom layer). " @@ -5059,177 +5563,178 @@ msgid "" "warn and prevent you from extruder collisions, but beware." msgstr "" -#: src/libslic3r/PrintConfig.cpp:278 +#: src/libslic3r/PrintConfig.cpp:287 msgid "Enable auto cooling" msgstr "" -#: src/libslic3r/PrintConfig.cpp:279 +#: src/libslic3r/PrintConfig.cpp:288 msgid "" "This flag enables the automatic cooling logic that adjusts print speed and " "fan speed according to layer printing time." msgstr "" -#: src/libslic3r/PrintConfig.cpp:284 +#: src/libslic3r/PrintConfig.cpp:293 msgid "Cooling tube position" msgstr "" -#: src/libslic3r/PrintConfig.cpp:285 +#: src/libslic3r/PrintConfig.cpp:294 msgid "Distance of the center-point of the cooling tube from the extruder tip." msgstr "" -#: src/libslic3r/PrintConfig.cpp:292 +#: src/libslic3r/PrintConfig.cpp:301 msgid "Cooling tube length" msgstr "" -#: src/libslic3r/PrintConfig.cpp:293 +#: src/libslic3r/PrintConfig.cpp:302 msgid "Length of the cooling tube to limit space for cooling moves inside it." msgstr "" -#: src/libslic3r/PrintConfig.cpp:301 +#: src/libslic3r/PrintConfig.cpp:310 msgid "" "This is the acceleration your printer will be reset to after the role-" "specific acceleration values are used (perimeter/infill). Set zero to " "prevent resetting acceleration at all." msgstr "" -#: src/libslic3r/PrintConfig.cpp:310 +#: src/libslic3r/PrintConfig.cpp:319 msgid "Default filament profile" msgstr "" -#: src/libslic3r/PrintConfig.cpp:311 +#: src/libslic3r/PrintConfig.cpp:320 msgid "" "Default filament profile associated with the current printer profile. On " "selection of the current printer profile, this filament profile will be " "activated." msgstr "" -#: src/libslic3r/PrintConfig.cpp:317 +#: src/libslic3r/PrintConfig.cpp:326 msgid "Default print profile" msgstr "" -#: src/libslic3r/PrintConfig.cpp:318 src/libslic3r/PrintConfig.cpp:2376 -#: src/libslic3r/PrintConfig.cpp:2387 +#: src/libslic3r/PrintConfig.cpp:327 src/libslic3r/PrintConfig.cpp:2447 +#: src/libslic3r/PrintConfig.cpp:2458 msgid "" "Default print profile associated with the current printer profile. On " "selection of the current printer profile, this print profile will be " "activated." msgstr "" -#: src/libslic3r/PrintConfig.cpp:324 +#: src/libslic3r/PrintConfig.cpp:333 msgid "Disable fan for the first" msgstr "" -#: src/libslic3r/PrintConfig.cpp:325 +#: src/libslic3r/PrintConfig.cpp:334 msgid "" "You can set this to a positive value to disable fan at all during the first " "layers, so that it does not make adhesion worse." msgstr "" -#: src/libslic3r/PrintConfig.cpp:327 src/libslic3r/PrintConfig.cpp:957 -#: src/libslic3r/PrintConfig.cpp:1470 src/libslic3r/PrintConfig.cpp:1655 -#: src/libslic3r/PrintConfig.cpp:1716 src/libslic3r/PrintConfig.cpp:1879 -#: src/libslic3r/PrintConfig.cpp:1924 +#: src/libslic3r/PrintConfig.cpp:336 src/libslic3r/PrintConfig.cpp:971 +#: src/libslic3r/PrintConfig.cpp:1484 src/libslic3r/PrintConfig.cpp:1669 +#: src/libslic3r/PrintConfig.cpp:1730 src/libslic3r/PrintConfig.cpp:1894 +#: src/libslic3r/PrintConfig.cpp:1939 msgid "layers" msgstr "" -#: src/libslic3r/PrintConfig.cpp:334 +#: src/libslic3r/PrintConfig.cpp:343 msgid "Don't support bridges" msgstr "" -#: src/libslic3r/PrintConfig.cpp:336 +#: src/libslic3r/PrintConfig.cpp:345 msgid "" "Experimental option for preventing support material from being generated " "under bridged areas." msgstr "" -#: src/libslic3r/PrintConfig.cpp:342 +#: src/libslic3r/PrintConfig.cpp:351 msgid "Distance between copies" msgstr "" -#: src/libslic3r/PrintConfig.cpp:343 +#: src/libslic3r/PrintConfig.cpp:352 msgid "Distance used for the auto-arrange feature of the plater." msgstr "" -#: src/libslic3r/PrintConfig.cpp:350 +#: src/libslic3r/PrintConfig.cpp:359 msgid "Elephant foot compensation" msgstr "" -#: src/libslic3r/PrintConfig.cpp:352 +#: src/libslic3r/PrintConfig.cpp:361 msgid "" "The first layer will be shrunk in the XY plane by the configured value to " "compensate for the 1st layer squish aka an Elephant Foot effect." msgstr "" -#: src/libslic3r/PrintConfig.cpp:361 +#: src/libslic3r/PrintConfig.cpp:370 msgid "" "This end procedure is inserted at the end of the output file. Note that you " "can use placeholder variables for all Slic3r settings." msgstr "" -#: src/libslic3r/PrintConfig.cpp:371 +#: src/libslic3r/PrintConfig.cpp:380 msgid "" "This end procedure is inserted at the end of the output file, before the " -"printer end gcode. Note that you can use placeholder variables for all " +"printer end gcode (and before any toolchange from this filament in case of " +"multimaterial printers). Note that you can use placeholder variables for all " "Slic3r settings. If you have multiple extruders, the gcode is processed in " "extruder order." msgstr "" -#: src/libslic3r/PrintConfig.cpp:381 +#: src/libslic3r/PrintConfig.cpp:391 msgid "Ensure vertical shell thickness" msgstr "" -#: src/libslic3r/PrintConfig.cpp:383 +#: src/libslic3r/PrintConfig.cpp:393 msgid "" "Add solid infill near sloping surfaces to guarantee the vertical shell " "thickness (top+bottom solid layers)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:389 +#: src/libslic3r/PrintConfig.cpp:399 msgid "Top fill pattern" msgstr "" -#: src/libslic3r/PrintConfig.cpp:391 +#: src/libslic3r/PrintConfig.cpp:401 msgid "" "Fill pattern for top infill. This only affects the top visible layer, and " "not its adjacent solid shells." msgstr "" -#: src/libslic3r/PrintConfig.cpp:399 src/libslic3r/PrintConfig.cpp:807 -#: src/libslic3r/PrintConfig.cpp:1957 +#: src/libslic3r/PrintConfig.cpp:409 src/libslic3r/PrintConfig.cpp:821 +#: src/libslic3r/PrintConfig.cpp:1972 msgid "Rectilinear" msgstr "" -#: src/libslic3r/PrintConfig.cpp:400 src/libslic3r/PrintConfig.cpp:813 +#: src/libslic3r/PrintConfig.cpp:410 src/libslic3r/PrintConfig.cpp:827 msgid "Concentric" msgstr "" -#: src/libslic3r/PrintConfig.cpp:401 src/libslic3r/PrintConfig.cpp:817 +#: src/libslic3r/PrintConfig.cpp:411 src/libslic3r/PrintConfig.cpp:831 msgid "Hilbert Curve" msgstr "" -#: src/libslic3r/PrintConfig.cpp:402 src/libslic3r/PrintConfig.cpp:818 +#: src/libslic3r/PrintConfig.cpp:412 src/libslic3r/PrintConfig.cpp:832 msgid "Archimedean Chords" msgstr "" -#: src/libslic3r/PrintConfig.cpp:403 src/libslic3r/PrintConfig.cpp:819 +#: src/libslic3r/PrintConfig.cpp:413 src/libslic3r/PrintConfig.cpp:833 msgid "Octagram Spiral" msgstr "" -#: src/libslic3r/PrintConfig.cpp:410 +#: src/libslic3r/PrintConfig.cpp:419 msgid "Bottom fill pattern" msgstr "" -#: src/libslic3r/PrintConfig.cpp:411 +#: src/libslic3r/PrintConfig.cpp:421 msgid "" "Fill pattern for bottom infill. This only affects the bottom external " "visible layer, and not its adjacent solid shells." msgstr "" -#: src/libslic3r/PrintConfig.cpp:416 src/libslic3r/PrintConfig.cpp:426 +#: src/libslic3r/PrintConfig.cpp:430 src/libslic3r/PrintConfig.cpp:440 msgid "External perimeters" msgstr "" -#: src/libslic3r/PrintConfig.cpp:418 +#: src/libslic3r/PrintConfig.cpp:432 msgid "" "Set this to a non-zero value to set a manual extrusion width for external " "perimeters. If left zero, default extrusion width will be used if set, " @@ -5237,43 +5742,43 @@ msgid "" "(for example 200%), it will be computed over layer height." msgstr "" -#: src/libslic3r/PrintConfig.cpp:421 src/libslic3r/PrintConfig.cpp:529 -#: src/libslic3r/PrintConfig.cpp:846 src/libslic3r/PrintConfig.cpp:858 -#: src/libslic3r/PrintConfig.cpp:978 src/libslic3r/PrintConfig.cpp:1003 -#: src/libslic3r/PrintConfig.cpp:1389 src/libslic3r/PrintConfig.cpp:1727 -#: src/libslic3r/PrintConfig.cpp:1832 src/libslic3r/PrintConfig.cpp:1900 -#: src/libslic3r/PrintConfig.cpp:2058 +#: src/libslic3r/PrintConfig.cpp:435 src/libslic3r/PrintConfig.cpp:543 +#: src/libslic3r/PrintConfig.cpp:860 src/libslic3r/PrintConfig.cpp:872 +#: src/libslic3r/PrintConfig.cpp:992 src/libslic3r/PrintConfig.cpp:1017 +#: src/libslic3r/PrintConfig.cpp:1403 src/libslic3r/PrintConfig.cpp:1741 +#: src/libslic3r/PrintConfig.cpp:1847 src/libslic3r/PrintConfig.cpp:1915 +#: src/libslic3r/PrintConfig.cpp:2074 msgid "mm or %" msgstr "" -#: src/libslic3r/PrintConfig.cpp:428 +#: src/libslic3r/PrintConfig.cpp:442 msgid "" "This separate setting will affect the speed of external perimeters (the " "visible ones). If expressed as percentage (for example: 80%) it will be " "calculated on the perimeters speed setting above. Set to zero for auto." msgstr "" -#: src/libslic3r/PrintConfig.cpp:431 src/libslic3r/PrintConfig.cpp:867 -#: src/libslic3r/PrintConfig.cpp:1686 src/libslic3r/PrintConfig.cpp:1737 -#: src/libslic3r/PrintConfig.cpp:1943 src/libslic3r/PrintConfig.cpp:2070 +#: src/libslic3r/PrintConfig.cpp:445 src/libslic3r/PrintConfig.cpp:881 +#: src/libslic3r/PrintConfig.cpp:1700 src/libslic3r/PrintConfig.cpp:1751 +#: src/libslic3r/PrintConfig.cpp:1958 src/libslic3r/PrintConfig.cpp:2086 msgid "mm/s or %" msgstr "" -#: src/libslic3r/PrintConfig.cpp:438 +#: src/libslic3r/PrintConfig.cpp:452 msgid "External perimeters first" msgstr "" -#: src/libslic3r/PrintConfig.cpp:440 +#: src/libslic3r/PrintConfig.cpp:454 msgid "" "Print contour perimeters from the outermost one to the innermost one instead " "of the default inverse order." msgstr "" -#: src/libslic3r/PrintConfig.cpp:446 +#: src/libslic3r/PrintConfig.cpp:460 msgid "Extra perimeters if needed" msgstr "" -#: src/libslic3r/PrintConfig.cpp:448 +#: src/libslic3r/PrintConfig.cpp:462 #, possible-c-format msgid "" "Add more perimeters when needed for avoiding gaps in sloping walls. Slic3r " @@ -5281,14 +5786,14 @@ msgid "" "is supported." msgstr "" -#: src/libslic3r/PrintConfig.cpp:458 +#: src/libslic3r/PrintConfig.cpp:472 msgid "" "The extruder to use (unless more specific extruder settings are specified). " "This value overrides perimeter and infill extruders, but not the support " "extruders." msgstr "" -#: src/libslic3r/PrintConfig.cpp:470 +#: src/libslic3r/PrintConfig.cpp:484 msgid "" "Set this to the vertical distance between your nozzle tip and (usually) the " "X carriage rods. In other words, this is the height of the clearance " @@ -5296,30 +5801,30 @@ msgid "" "extruder can peek before colliding with other printed objects." msgstr "" -#: src/libslic3r/PrintConfig.cpp:480 +#: src/libslic3r/PrintConfig.cpp:494 msgid "Radius" msgstr "" -#: src/libslic3r/PrintConfig.cpp:481 +#: src/libslic3r/PrintConfig.cpp:495 msgid "" "Set this to the clearance radius around your extruder. If the extruder is " "not centered, choose the largest value for safety. This setting is used to " "check for collisions and to display the graphical preview in the plater." msgstr "" -#: src/libslic3r/PrintConfig.cpp:491 +#: src/libslic3r/PrintConfig.cpp:505 msgid "Extruder Color" msgstr "" -#: src/libslic3r/PrintConfig.cpp:492 src/libslic3r/PrintConfig.cpp:552 +#: src/libslic3r/PrintConfig.cpp:506 src/libslic3r/PrintConfig.cpp:566 msgid "This is only used in the Slic3r interface as a visual help." msgstr "" -#: src/libslic3r/PrintConfig.cpp:498 +#: src/libslic3r/PrintConfig.cpp:512 msgid "Extruder offset" msgstr "" -#: src/libslic3r/PrintConfig.cpp:499 +#: src/libslic3r/PrintConfig.cpp:513 msgid "" "If your firmware doesn't handle the extruder displacement you need the G-" "code to take it into account. This option lets you specify the displacement " @@ -5327,21 +5832,21 @@ msgid "" "coordinates (they will be subtracted from the XY coordinate)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:508 +#: src/libslic3r/PrintConfig.cpp:522 msgid "Extrusion axis" msgstr "" -#: src/libslic3r/PrintConfig.cpp:509 +#: src/libslic3r/PrintConfig.cpp:523 msgid "" "Use this option to set the axis letter associated to your printer's extruder " "(usually E but some printers use A)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:514 +#: src/libslic3r/PrintConfig.cpp:528 msgid "Extrusion multiplier" msgstr "" -#: src/libslic3r/PrintConfig.cpp:515 +#: src/libslic3r/PrintConfig.cpp:529 msgid "" "This factor changes the amount of flow proportionally. You may need to tweak " "this setting to get nice surface finish and correct single wall widths. " @@ -5349,11 +5854,11 @@ msgid "" "more, check filament diameter and your firmware E steps." msgstr "" -#: src/libslic3r/PrintConfig.cpp:523 +#: src/libslic3r/PrintConfig.cpp:537 msgid "Default extrusion width" msgstr "" -#: src/libslic3r/PrintConfig.cpp:525 +#: src/libslic3r/PrintConfig.cpp:539 msgid "" "Set this to a non-zero value to allow a manual extrusion width. If left to " "zero, Slic3r derives extrusion widths from the nozzle diameter (see the " @@ -5362,123 +5867,123 @@ msgid "" "height." msgstr "" -#: src/libslic3r/PrintConfig.cpp:534 +#: src/libslic3r/PrintConfig.cpp:548 msgid "Keep fan always on" msgstr "" -#: src/libslic3r/PrintConfig.cpp:535 +#: src/libslic3r/PrintConfig.cpp:549 msgid "" "If this is enabled, fan will never be disabled and will be kept running at " "least at its minimum speed. Useful for PLA, harmful for ABS." msgstr "" -#: src/libslic3r/PrintConfig.cpp:540 +#: src/libslic3r/PrintConfig.cpp:554 msgid "Enable fan if layer print time is below" msgstr "" -#: src/libslic3r/PrintConfig.cpp:541 +#: src/libslic3r/PrintConfig.cpp:555 msgid "" "If layer print time is estimated below this number of seconds, fan will be " "enabled and its speed will be calculated by interpolating the minimum and " "maximum speeds." msgstr "" -#: src/libslic3r/PrintConfig.cpp:543 src/libslic3r/PrintConfig.cpp:1673 +#: src/libslic3r/PrintConfig.cpp:557 src/libslic3r/PrintConfig.cpp:1687 msgid "approximate seconds" msgstr "" -#: src/libslic3r/PrintConfig.cpp:551 +#: src/libslic3r/PrintConfig.cpp:565 msgid "Color" msgstr "" -#: src/libslic3r/PrintConfig.cpp:557 +#: src/libslic3r/PrintConfig.cpp:571 msgid "Filament notes" msgstr "" -#: src/libslic3r/PrintConfig.cpp:558 +#: src/libslic3r/PrintConfig.cpp:572 msgid "You can put your notes regarding the filament here." msgstr "" -#: src/libslic3r/PrintConfig.cpp:566 src/libslic3r/PrintConfig.cpp:1217 +#: src/libslic3r/PrintConfig.cpp:580 src/libslic3r/PrintConfig.cpp:1231 msgid "Max volumetric speed" msgstr "" -#: src/libslic3r/PrintConfig.cpp:567 +#: src/libslic3r/PrintConfig.cpp:581 msgid "" "Maximum volumetric speed allowed for this filament. Limits the maximum " "volumetric speed of a print to the minimum of print and filament volumetric " "speed. Set to zero for no limit." msgstr "" -#: src/libslic3r/PrintConfig.cpp:576 +#: src/libslic3r/PrintConfig.cpp:590 msgid "Loading speed" msgstr "" -#: src/libslic3r/PrintConfig.cpp:577 +#: src/libslic3r/PrintConfig.cpp:591 msgid "Speed used for loading the filament on the wipe tower." msgstr "" -#: src/libslic3r/PrintConfig.cpp:584 +#: src/libslic3r/PrintConfig.cpp:598 msgid "Loading speed at the start" msgstr "" -#: src/libslic3r/PrintConfig.cpp:585 +#: src/libslic3r/PrintConfig.cpp:599 msgid "Speed used at the very beginning of loading phase." msgstr "" -#: src/libslic3r/PrintConfig.cpp:592 +#: src/libslic3r/PrintConfig.cpp:606 msgid "Unloading speed" msgstr "" -#: src/libslic3r/PrintConfig.cpp:593 +#: src/libslic3r/PrintConfig.cpp:607 msgid "" "Speed used for unloading the filament on the wipe tower (does not affect " "initial part of unloading just after ramming)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:601 +#: src/libslic3r/PrintConfig.cpp:615 msgid "Unloading speed at the start" msgstr "" -#: src/libslic3r/PrintConfig.cpp:602 +#: src/libslic3r/PrintConfig.cpp:616 msgid "" "Speed used for unloading the tip of the filament immediately after ramming." msgstr "" -#: src/libslic3r/PrintConfig.cpp:609 +#: src/libslic3r/PrintConfig.cpp:623 msgid "Delay after unloading" msgstr "" -#: src/libslic3r/PrintConfig.cpp:610 +#: src/libslic3r/PrintConfig.cpp:624 msgid "" "Time to wait after the filament is unloaded. May help to get reliable " "toolchanges with flexible materials that may need more time to shrink to " "original dimensions." msgstr "" -#: src/libslic3r/PrintConfig.cpp:619 +#: src/libslic3r/PrintConfig.cpp:633 msgid "Number of cooling moves" msgstr "" -#: src/libslic3r/PrintConfig.cpp:620 +#: src/libslic3r/PrintConfig.cpp:634 msgid "" "Filament is cooled by being moved back and forth in the cooling tubes. " "Specify desired number of these moves." msgstr "" -#: src/libslic3r/PrintConfig.cpp:628 +#: src/libslic3r/PrintConfig.cpp:642 msgid "Speed of the first cooling move" msgstr "" -#: src/libslic3r/PrintConfig.cpp:629 +#: src/libslic3r/PrintConfig.cpp:643 msgid "Cooling moves are gradually accelerating beginning at this speed." msgstr "" -#: src/libslic3r/PrintConfig.cpp:636 +#: src/libslic3r/PrintConfig.cpp:650 msgid "Minimal purge on wipe tower" msgstr "" -#: src/libslic3r/PrintConfig.cpp:637 +#: src/libslic3r/PrintConfig.cpp:651 msgid "" "After a tool change, the exact position of the newly loaded filament inside " "the nozzle may not be known, and the filament pressure is likely not yet " @@ -5487,62 +5992,62 @@ msgid "" "to produce successive infill or sacrificial object extrusions reliably." msgstr "" -#: src/libslic3r/PrintConfig.cpp:641 +#: src/libslic3r/PrintConfig.cpp:655 msgid "mm³" msgstr "" -#: src/libslic3r/PrintConfig.cpp:647 +#: src/libslic3r/PrintConfig.cpp:661 msgid "Speed of the last cooling move" msgstr "" -#: src/libslic3r/PrintConfig.cpp:648 +#: src/libslic3r/PrintConfig.cpp:662 msgid "Cooling moves are gradually accelerating towards this speed." msgstr "" -#: src/libslic3r/PrintConfig.cpp:655 +#: src/libslic3r/PrintConfig.cpp:669 msgid "Filament load time" msgstr "" -#: src/libslic3r/PrintConfig.cpp:656 +#: src/libslic3r/PrintConfig.cpp:670 msgid "" "Time for the printer firmware (or the Multi Material Unit 2.0) to load a new " "filament during a tool change (when executing the T code). This time is " "added to the total print time by the G-code time estimator." msgstr "" -#: src/libslic3r/PrintConfig.cpp:663 +#: src/libslic3r/PrintConfig.cpp:677 msgid "Ramming parameters" msgstr "" -#: src/libslic3r/PrintConfig.cpp:664 +#: src/libslic3r/PrintConfig.cpp:678 msgid "" "This string is edited by RammingDialog and contains ramming specific " "parameters." msgstr "" -#: src/libslic3r/PrintConfig.cpp:670 +#: src/libslic3r/PrintConfig.cpp:684 msgid "Filament unload time" msgstr "" -#: src/libslic3r/PrintConfig.cpp:671 +#: src/libslic3r/PrintConfig.cpp:685 msgid "" "Time for the printer firmware (or the Multi Material Unit 2.0) to unload a " "filament during a tool change (when executing the T code). This time is " "added to the total print time by the G-code time estimator." msgstr "" -#: src/libslic3r/PrintConfig.cpp:679 +#: src/libslic3r/PrintConfig.cpp:693 msgid "" "Enter your filament diameter here. Good precision is required, so use a " "caliper and do multiple measurements along the filament, then compute the " "average." msgstr "" -#: src/libslic3r/PrintConfig.cpp:686 +#: src/libslic3r/PrintConfig.cpp:700 msgid "Density" msgstr "" -#: src/libslic3r/PrintConfig.cpp:687 +#: src/libslic3r/PrintConfig.cpp:701 msgid "" "Enter your filament density here. This is only for statistical information. " "A decent way is to weigh a known length of filament and compute the ratio of " @@ -5550,113 +6055,113 @@ msgid "" "displacement." msgstr "" -#: src/libslic3r/PrintConfig.cpp:690 +#: src/libslic3r/PrintConfig.cpp:704 msgid "g/cm³" msgstr "" -#: src/libslic3r/PrintConfig.cpp:695 +#: src/libslic3r/PrintConfig.cpp:709 msgid "Filament type" msgstr "" -#: src/libslic3r/PrintConfig.cpp:696 +#: src/libslic3r/PrintConfig.cpp:710 msgid "The filament material type for use in custom G-codes." msgstr "" -#: src/libslic3r/PrintConfig.cpp:722 +#: src/libslic3r/PrintConfig.cpp:736 msgid "Soluble material" msgstr "" -#: src/libslic3r/PrintConfig.cpp:723 +#: src/libslic3r/PrintConfig.cpp:737 msgid "Soluble material is most likely used for a soluble support." msgstr "" -#: src/libslic3r/PrintConfig.cpp:729 +#: src/libslic3r/PrintConfig.cpp:743 msgid "" "Enter your filament cost per kg here. This is only for statistical " "information." msgstr "" -#: src/libslic3r/PrintConfig.cpp:730 +#: src/libslic3r/PrintConfig.cpp:744 msgid "money/kg" msgstr "" -#: src/libslic3r/PrintConfig.cpp:739 +#: src/libslic3r/PrintConfig.cpp:753 msgid "Fill angle" msgstr "" -#: src/libslic3r/PrintConfig.cpp:741 +#: src/libslic3r/PrintConfig.cpp:755 msgid "" "Default base angle for infill orientation. Cross-hatching will be applied to " "this. Bridges will be infilled using the best direction Slic3r can detect, " "so this setting does not affect them." msgstr "" -#: src/libslic3r/PrintConfig.cpp:753 +#: src/libslic3r/PrintConfig.cpp:767 msgid "Fill density" msgstr "" -#: src/libslic3r/PrintConfig.cpp:755 +#: src/libslic3r/PrintConfig.cpp:769 msgid "Density of internal infill, expressed in the range 0% - 100%." msgstr "" -#: src/libslic3r/PrintConfig.cpp:790 +#: src/libslic3r/PrintConfig.cpp:804 msgid "Fill pattern" msgstr "" -#: src/libslic3r/PrintConfig.cpp:792 +#: src/libslic3r/PrintConfig.cpp:806 msgid "Fill pattern for general low-density infill." msgstr "" -#: src/libslic3r/PrintConfig.cpp:808 +#: src/libslic3r/PrintConfig.cpp:822 msgid "Grid" msgstr "" -#: src/libslic3r/PrintConfig.cpp:809 +#: src/libslic3r/PrintConfig.cpp:823 msgid "Triangles" msgstr "" -#: src/libslic3r/PrintConfig.cpp:810 +#: src/libslic3r/PrintConfig.cpp:824 msgid "Stars" msgstr "" -#: src/libslic3r/PrintConfig.cpp:811 +#: src/libslic3r/PrintConfig.cpp:825 msgid "Cubic" msgstr "" -#: src/libslic3r/PrintConfig.cpp:812 +#: src/libslic3r/PrintConfig.cpp:826 msgid "Line" msgstr "" -#: src/libslic3r/PrintConfig.cpp:814 src/libslic3r/PrintConfig.cpp:1959 +#: src/libslic3r/PrintConfig.cpp:828 src/libslic3r/PrintConfig.cpp:1974 msgid "Honeycomb" msgstr "" -#: src/libslic3r/PrintConfig.cpp:815 +#: src/libslic3r/PrintConfig.cpp:829 msgid "3D Honeycomb" msgstr "" -#: src/libslic3r/PrintConfig.cpp:816 +#: src/libslic3r/PrintConfig.cpp:830 msgid "Gyroid" msgstr "" -#: src/libslic3r/PrintConfig.cpp:823 src/libslic3r/PrintConfig.cpp:832 -#: src/libslic3r/PrintConfig.cpp:840 src/libslic3r/PrintConfig.cpp:873 +#: src/libslic3r/PrintConfig.cpp:837 src/libslic3r/PrintConfig.cpp:846 +#: src/libslic3r/PrintConfig.cpp:854 src/libslic3r/PrintConfig.cpp:887 msgid "First layer" msgstr "" -#: src/libslic3r/PrintConfig.cpp:824 +#: src/libslic3r/PrintConfig.cpp:838 msgid "" "This is the acceleration your printer will use for first layer. Set zero to " "disable acceleration control for first layer." msgstr "" -#: src/libslic3r/PrintConfig.cpp:833 +#: src/libslic3r/PrintConfig.cpp:847 msgid "" "Heated build plate temperature for the first layer. Set this to zero to " "disable bed temperature control commands in the output." msgstr "" -#: src/libslic3r/PrintConfig.cpp:842 +#: src/libslic3r/PrintConfig.cpp:856 msgid "" "Set this to a non-zero value to set a manual extrusion width for first " "layer. You can use this to force fatter extrudates for better adhesion. If " @@ -5664,7 +6169,7 @@ msgid "" "layer height. If set to zero, it will use the default extrusion width." msgstr "" -#: src/libslic3r/PrintConfig.cpp:854 +#: src/libslic3r/PrintConfig.cpp:868 msgid "" "When printing with very low layer heights, you might still want to print a " "thicker bottom layer to improve adhesion and tolerance for non perfect build " @@ -5672,47 +6177,47 @@ msgid "" "example: 150%) over the default layer height." msgstr "" -#: src/libslic3r/PrintConfig.cpp:863 +#: src/libslic3r/PrintConfig.cpp:877 msgid "First layer speed" msgstr "" -#: src/libslic3r/PrintConfig.cpp:864 +#: src/libslic3r/PrintConfig.cpp:878 msgid "" "If expressed as absolute value in mm/s, this speed will be applied to all " "the print moves of the first layer, regardless of their type. If expressed " "as a percentage (for example: 40%) it will scale the default speeds." msgstr "" -#: src/libslic3r/PrintConfig.cpp:874 +#: src/libslic3r/PrintConfig.cpp:888 msgid "" "Extruder temperature for first layer. If you want to control temperature " "manually during print, set this to zero to disable temperature control " "commands in the output file." msgstr "" -#: src/libslic3r/PrintConfig.cpp:883 +#: src/libslic3r/PrintConfig.cpp:897 msgid "" "Speed for filling small gaps using short zigzag moves. Keep this reasonably " "low to avoid too much shaking and resonance issues. Set zero to disable gaps " "filling." msgstr "" -#: src/libslic3r/PrintConfig.cpp:891 +#: src/libslic3r/PrintConfig.cpp:905 msgid "Verbose G-code" msgstr "" -#: src/libslic3r/PrintConfig.cpp:892 +#: src/libslic3r/PrintConfig.cpp:906 msgid "" "Enable this to get a commented G-code file, with each line explained by a " "descriptive text. If you print from SD card, the additional weight of the " "file could make your firmware slow down." msgstr "" -#: src/libslic3r/PrintConfig.cpp:899 +#: src/libslic3r/PrintConfig.cpp:913 msgid "G-code flavor" msgstr "" -#: src/libslic3r/PrintConfig.cpp:900 +#: src/libslic3r/PrintConfig.cpp:914 msgid "" "Some G/M-code commands, including temperature control and others, are not " "universal. Set this option to your printer's firmware to get a compatible " @@ -5720,15 +6225,15 @@ msgid "" "extrusion value at all." msgstr "" -#: src/libslic3r/PrintConfig.cpp:923 +#: src/libslic3r/PrintConfig.cpp:937 msgid "No extrusion" msgstr "" -#: src/libslic3r/PrintConfig.cpp:928 +#: src/libslic3r/PrintConfig.cpp:942 msgid "Label objects" msgstr "" -#: src/libslic3r/PrintConfig.cpp:929 +#: src/libslic3r/PrintConfig.cpp:943 msgid "" "Enable this to add comments into the G-Code labeling print moves with what " "object they belong to, which is useful for the Octoprint CancelObject " @@ -5736,46 +6241,46 @@ msgid "" "setup and Wipe into Object / Wipe into Infill." msgstr "" -#: src/libslic3r/PrintConfig.cpp:936 +#: src/libslic3r/PrintConfig.cpp:950 msgid "High extruder current on filament swap" msgstr "" -#: src/libslic3r/PrintConfig.cpp:937 +#: src/libslic3r/PrintConfig.cpp:951 msgid "" "It may be beneficial to increase the extruder motor current during the " "filament exchange sequence to allow for rapid ramming feed rates and to " "overcome resistance when loading a filament with an ugly shaped tip." msgstr "" -#: src/libslic3r/PrintConfig.cpp:945 +#: src/libslic3r/PrintConfig.cpp:959 msgid "" "This is the acceleration your printer will use for infill. Set zero to " "disable acceleration control for infill." msgstr "" -#: src/libslic3r/PrintConfig.cpp:953 +#: src/libslic3r/PrintConfig.cpp:967 msgid "Combine infill every" msgstr "" -#: src/libslic3r/PrintConfig.cpp:955 +#: src/libslic3r/PrintConfig.cpp:969 msgid "" "This feature allows to combine infill and speed up your print by extruding " "thicker infill layers while preserving thin perimeters, thus accuracy." msgstr "" -#: src/libslic3r/PrintConfig.cpp:958 +#: src/libslic3r/PrintConfig.cpp:972 msgid "Combine infill every n layers" msgstr "" -#: src/libslic3r/PrintConfig.cpp:964 +#: src/libslic3r/PrintConfig.cpp:978 msgid "Infill extruder" msgstr "" -#: src/libslic3r/PrintConfig.cpp:966 +#: src/libslic3r/PrintConfig.cpp:980 msgid "The extruder to use when printing infill." msgstr "" -#: src/libslic3r/PrintConfig.cpp:974 +#: src/libslic3r/PrintConfig.cpp:988 msgid "" "Set this to a non-zero value to set a manual extrusion width for infill. If " "left zero, default extrusion width will be used if set, otherwise 1.125 x " @@ -5784,32 +6289,32 @@ msgid "" "example 90%) it will be computed over layer height." msgstr "" -#: src/libslic3r/PrintConfig.cpp:983 +#: src/libslic3r/PrintConfig.cpp:997 msgid "Infill before perimeters" msgstr "" -#: src/libslic3r/PrintConfig.cpp:984 +#: src/libslic3r/PrintConfig.cpp:998 msgid "" "This option will switch the print order of perimeters and infill, making the " "latter first." msgstr "" -#: src/libslic3r/PrintConfig.cpp:989 +#: src/libslic3r/PrintConfig.cpp:1003 msgid "Only infill where needed" msgstr "" -#: src/libslic3r/PrintConfig.cpp:991 +#: src/libslic3r/PrintConfig.cpp:1005 msgid "" "This option will limit infill to the areas actually needed for supporting " "ceilings (it will act as internal support material). If enabled, slows down " "the G-code generation due to the multiple checks involved." msgstr "" -#: src/libslic3r/PrintConfig.cpp:998 +#: src/libslic3r/PrintConfig.cpp:1012 msgid "Infill/perimeters overlap" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1000 +#: src/libslic3r/PrintConfig.cpp:1014 msgid "" "This setting applies an additional overlap between infill and perimeters for " "better bonding. Theoretically this shouldn't be needed, but backlash might " @@ -5817,30 +6322,30 @@ msgid "" "perimeter extrusion width." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1011 +#: src/libslic3r/PrintConfig.cpp:1025 msgid "Speed for printing the internal fill. Set to zero for auto." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1019 +#: src/libslic3r/PrintConfig.cpp:1033 msgid "Inherits profile" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1020 +#: src/libslic3r/PrintConfig.cpp:1034 msgid "Name of the profile, from which this profile inherits." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1033 +#: src/libslic3r/PrintConfig.cpp:1047 msgid "Interface shells" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1034 +#: src/libslic3r/PrintConfig.cpp:1048 msgid "" "Force the generation of solid shells between adjacent materials/volumes. " "Useful for multi-extruder prints with translucent materials or manual " "soluble support material." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1043 +#: src/libslic3r/PrintConfig.cpp:1057 msgid "" "This custom code is inserted at every layer change, right after the Z move " "and before the extruder moves to the first layer point. Note that you can " @@ -5848,11 +6353,11 @@ msgid "" "[layer_z]." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1054 +#: src/libslic3r/PrintConfig.cpp:1068 msgid "Supports remaining times" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1055 +#: src/libslic3r/PrintConfig.cpp:1069 msgid "" "Emit M73 P[percent printed] R[remaining time in minutes] at 1 minute " "intervals into the G-code to let the firmware show accurate remaining time. " @@ -5860,151 +6365,151 @@ msgid "" "firmware supports M73 Qxx Sxx for the silent mode." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1063 +#: src/libslic3r/PrintConfig.cpp:1077 msgid "Supports stealth mode" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1064 +#: src/libslic3r/PrintConfig.cpp:1078 msgid "The firmware supports stealth mode" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1088 +#: src/libslic3r/PrintConfig.cpp:1102 msgid "Maximum feedrate X" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1089 +#: src/libslic3r/PrintConfig.cpp:1103 msgid "Maximum feedrate Y" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1090 +#: src/libslic3r/PrintConfig.cpp:1104 msgid "Maximum feedrate Z" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1091 +#: src/libslic3r/PrintConfig.cpp:1105 msgid "Maximum feedrate E" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1094 +#: src/libslic3r/PrintConfig.cpp:1108 msgid "Maximum feedrate of the X axis" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1095 +#: src/libslic3r/PrintConfig.cpp:1109 msgid "Maximum feedrate of the Y axis" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1096 +#: src/libslic3r/PrintConfig.cpp:1110 msgid "Maximum feedrate of the Z axis" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1097 +#: src/libslic3r/PrintConfig.cpp:1111 msgid "Maximum feedrate of the E axis" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1106 +#: src/libslic3r/PrintConfig.cpp:1120 msgid "Maximum acceleration X" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1107 +#: src/libslic3r/PrintConfig.cpp:1121 msgid "Maximum acceleration Y" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1108 +#: src/libslic3r/PrintConfig.cpp:1122 msgid "Maximum acceleration Z" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1109 +#: src/libslic3r/PrintConfig.cpp:1123 msgid "Maximum acceleration E" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1112 +#: src/libslic3r/PrintConfig.cpp:1126 msgid "Maximum acceleration of the X axis" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1113 +#: src/libslic3r/PrintConfig.cpp:1127 msgid "Maximum acceleration of the Y axis" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1114 +#: src/libslic3r/PrintConfig.cpp:1128 msgid "Maximum acceleration of the Z axis" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1115 +#: src/libslic3r/PrintConfig.cpp:1129 msgid "Maximum acceleration of the E axis" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1124 +#: src/libslic3r/PrintConfig.cpp:1138 msgid "Maximum jerk X" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1125 +#: src/libslic3r/PrintConfig.cpp:1139 msgid "Maximum jerk Y" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1126 +#: src/libslic3r/PrintConfig.cpp:1140 msgid "Maximum jerk Z" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1127 +#: src/libslic3r/PrintConfig.cpp:1141 msgid "Maximum jerk E" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1130 +#: src/libslic3r/PrintConfig.cpp:1144 msgid "Maximum jerk of the X axis" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1131 +#: src/libslic3r/PrintConfig.cpp:1145 msgid "Maximum jerk of the Y axis" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1132 +#: src/libslic3r/PrintConfig.cpp:1146 msgid "Maximum jerk of the Z axis" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1133 +#: src/libslic3r/PrintConfig.cpp:1147 msgid "Maximum jerk of the E axis" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1144 +#: src/libslic3r/PrintConfig.cpp:1158 msgid "Minimum feedrate when extruding" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1146 +#: src/libslic3r/PrintConfig.cpp:1160 msgid "Minimum feedrate when extruding (M205 S)" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1155 +#: src/libslic3r/PrintConfig.cpp:1169 msgid "Minimum travel feedrate" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1157 +#: src/libslic3r/PrintConfig.cpp:1171 msgid "Minimum travel feedrate (M205 T)" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1166 +#: src/libslic3r/PrintConfig.cpp:1180 msgid "Maximum acceleration when extruding" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1168 +#: src/libslic3r/PrintConfig.cpp:1182 msgid "Maximum acceleration when extruding (M204 S)" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1177 +#: src/libslic3r/PrintConfig.cpp:1191 msgid "Maximum acceleration when retracting" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1179 +#: src/libslic3r/PrintConfig.cpp:1193 msgid "Maximum acceleration when retracting (M204 T)" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1187 src/libslic3r/PrintConfig.cpp:1196 +#: src/libslic3r/PrintConfig.cpp:1201 src/libslic3r/PrintConfig.cpp:1210 msgid "Max" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1188 +#: src/libslic3r/PrintConfig.cpp:1202 msgid "This setting represents the maximum speed of your fan." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1197 +#: src/libslic3r/PrintConfig.cpp:1211 #, possible-c-format msgid "" "This is the highest printable layer height for this extruder, used to cap " @@ -6013,28 +6518,28 @@ msgid "" "adhesion. If set to 0, layer height is limited to 75% of the nozzle diameter." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1207 +#: src/libslic3r/PrintConfig.cpp:1221 msgid "Max print speed" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1208 +#: src/libslic3r/PrintConfig.cpp:1222 msgid "" "When setting other speed settings to 0 Slic3r will autocalculate the optimal " "speed in order to keep constant extruder pressure. This experimental setting " "is used to set the highest print speed you want to allow." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1218 +#: src/libslic3r/PrintConfig.cpp:1232 msgid "" "This experimental setting is used to set the maximum volumetric speed your " "extruder supports." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1227 +#: src/libslic3r/PrintConfig.cpp:1241 msgid "Max volumetric slope positive" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1228 src/libslic3r/PrintConfig.cpp:1239 +#: src/libslic3r/PrintConfig.cpp:1242 src/libslic3r/PrintConfig.cpp:1253 msgid "" "This experimental setting is used to limit the speed of change in extrusion " "rate. A value of 1.8 mm³/s² ensures, that a change from the extrusion rate " @@ -6042,99 +6547,95 @@ msgid "" "s) to 5.4 mm³/s (feedrate 60 mm/s) will take at least 2 seconds." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1232 src/libslic3r/PrintConfig.cpp:1243 +#: src/libslic3r/PrintConfig.cpp:1246 src/libslic3r/PrintConfig.cpp:1257 msgid "mm³/s²" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1238 +#: src/libslic3r/PrintConfig.cpp:1252 msgid "Max volumetric slope negative" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1250 src/libslic3r/PrintConfig.cpp:1259 +#: src/libslic3r/PrintConfig.cpp:1264 src/libslic3r/PrintConfig.cpp:1273 msgid "Min" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1251 +#: src/libslic3r/PrintConfig.cpp:1265 msgid "This setting represents the minimum PWM your fan needs to work." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1260 +#: src/libslic3r/PrintConfig.cpp:1274 msgid "" "This is the lowest printable layer height for this extruder and limits the " "resolution for variable layer height. Typical values are between 0.05 mm and " "0.1 mm." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1268 +#: src/libslic3r/PrintConfig.cpp:1282 msgid "Min print speed" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1269 +#: src/libslic3r/PrintConfig.cpp:1283 msgid "Slic3r will not scale speed down below this speed." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1276 +#: src/libslic3r/PrintConfig.cpp:1290 msgid "Minimal filament extrusion length" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1277 +#: src/libslic3r/PrintConfig.cpp:1291 msgid "" "Generate no less than the number of skirt loops required to consume the " "specified amount of filament on the bottom layer. For multi-extruder " "machines, this minimum applies to each extruder." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1286 +#: src/libslic3r/PrintConfig.cpp:1300 msgid "Configuration notes" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1287 +#: src/libslic3r/PrintConfig.cpp:1301 msgid "" "You can put here your personal notes. This text will be added to the G-code " "header comments." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1296 -msgid "Nozzle diameter" -msgstr "" - -#: src/libslic3r/PrintConfig.cpp:1297 +#: src/libslic3r/PrintConfig.cpp:1311 msgid "" "This is the diameter of your extruder nozzle (for example: 0.5, 0.35 etc.)" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1302 +#: src/libslic3r/PrintConfig.cpp:1316 msgid "Host Type" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1303 +#: src/libslic3r/PrintConfig.cpp:1317 msgid "" "Slic3r can upload G-code files to a printer host. This field must contain " "the kind of the host." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1314 +#: src/libslic3r/PrintConfig.cpp:1328 msgid "Only retract when crossing perimeters" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1315 +#: src/libslic3r/PrintConfig.cpp:1329 msgid "" "Disables retraction when the travel path does not exceed the upper layer's " "perimeters (and thus any ooze will be probably invisible)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1322 +#: src/libslic3r/PrintConfig.cpp:1336 msgid "" "This option will drop the temperature of the inactive extruders to prevent " "oozing. It will enable a tall skirt automatically and move extruders outside " "such skirt when changing temperatures." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1329 +#: src/libslic3r/PrintConfig.cpp:1343 msgid "Output filename format" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1330 +#: src/libslic3r/PrintConfig.cpp:1344 msgid "" "You can use all configuration options as variables inside this template. For " "example: [layer_height], [fill_density] etc. You can also use [timestamp], " @@ -6142,31 +6643,31 @@ msgid "" "[input_filename], [input_filename_base]." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1339 +#: src/libslic3r/PrintConfig.cpp:1353 msgid "Detect bridging perimeters" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1341 +#: src/libslic3r/PrintConfig.cpp:1355 msgid "" "Experimental option to adjust flow for overhangs (bridge flow will be used), " "to apply bridge speed to them and enable fan." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1347 +#: src/libslic3r/PrintConfig.cpp:1361 msgid "Filament parking position" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1348 +#: src/libslic3r/PrintConfig.cpp:1362 msgid "" "Distance of the extruder tip from the position where the filament is parked " "when unloaded. This should match the value in printer firmware." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1356 +#: src/libslic3r/PrintConfig.cpp:1370 msgid "Extra loading distance" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1357 +#: src/libslic3r/PrintConfig.cpp:1371 msgid "" "When set to zero, the distance the filament is moved from parking position " "during load is exactly the same as it was moved back during unload. When " @@ -6174,28 +6675,28 @@ msgid "" "than unloading." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1365 src/libslic3r/PrintConfig.cpp:1383 -#: src/libslic3r/PrintConfig.cpp:1395 src/libslic3r/PrintConfig.cpp:1405 +#: src/libslic3r/PrintConfig.cpp:1379 src/libslic3r/PrintConfig.cpp:1397 +#: src/libslic3r/PrintConfig.cpp:1409 src/libslic3r/PrintConfig.cpp:1419 msgid "Perimeters" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1366 +#: src/libslic3r/PrintConfig.cpp:1380 msgid "" "This is the acceleration your printer will use for perimeters. A high value " "like 9000 usually gives good results if your hardware is up to the job. Set " "zero to disable acceleration control for perimeters." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1374 +#: src/libslic3r/PrintConfig.cpp:1388 msgid "Perimeter extruder" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1376 +#: src/libslic3r/PrintConfig.cpp:1390 msgid "" "The extruder to use when printing perimeters and brim. First extruder is 1." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1385 +#: src/libslic3r/PrintConfig.cpp:1399 msgid "" "Set this to a non-zero value to set a manual extrusion width for perimeters. " "You may want to use thinner extrudates to get more accurate surfaces. If " @@ -6204,12 +6705,12 @@ msgid "" "it will be computed over layer height." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1397 +#: src/libslic3r/PrintConfig.cpp:1411 msgid "" "Speed for perimeters (contours, aka vertical shells). Set to zero for auto." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1407 +#: src/libslic3r/PrintConfig.cpp:1421 msgid "" "This option sets the number of perimeters to generate for each layer. Note " "that Slic3r may increase this number automatically when it detects sloping " @@ -6217,11 +6718,11 @@ msgid "" "Perimeters option is enabled." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1411 +#: src/libslic3r/PrintConfig.cpp:1425 msgid "(minimum)" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1419 +#: src/libslic3r/PrintConfig.cpp:1433 msgid "" "If you want to process the output G-code through custom scripts, just list " "their absolute paths here. Separate multiple scripts with a semicolon. " @@ -6230,55 +6731,55 @@ msgid "" "environment variables." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1431 +#: src/libslic3r/PrintConfig.cpp:1445 msgid "Printer type" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1432 +#: src/libslic3r/PrintConfig.cpp:1446 msgid "Type of the printer." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1437 +#: src/libslic3r/PrintConfig.cpp:1451 msgid "Printer notes" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1438 +#: src/libslic3r/PrintConfig.cpp:1452 msgid "You can put your notes regarding the printer here." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1446 +#: src/libslic3r/PrintConfig.cpp:1460 msgid "Printer vendor" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1447 +#: src/libslic3r/PrintConfig.cpp:1461 msgid "Name of the printer vendor." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1452 +#: src/libslic3r/PrintConfig.cpp:1466 msgid "Printer variant" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1453 +#: src/libslic3r/PrintConfig.cpp:1467 msgid "" "Name of the printer variant. For example, the printer variants may be " "differentiated by a nozzle diameter." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1466 +#: src/libslic3r/PrintConfig.cpp:1480 msgid "Raft layers" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1468 +#: src/libslic3r/PrintConfig.cpp:1482 msgid "" "The object will be raised by this number of layers, and support material " "will be generated under it." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1476 +#: src/libslic3r/PrintConfig.cpp:1490 msgid "Resolution" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1477 +#: src/libslic3r/PrintConfig.cpp:1491 msgid "" "Minimum detail resolution, used to simplify the input file for speeding up " "the slicing job and reducing memory usage. High-resolution models often " @@ -6286,278 +6787,278 @@ msgid "" "simplification and use full resolution from input." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1487 +#: src/libslic3r/PrintConfig.cpp:1501 msgid "Minimum travel after retraction" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1488 +#: src/libslic3r/PrintConfig.cpp:1502 msgid "" "Retraction is not triggered when travel moves are shorter than this length." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1494 +#: src/libslic3r/PrintConfig.cpp:1508 msgid "Retract amount before wipe" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1495 +#: src/libslic3r/PrintConfig.cpp:1509 msgid "" "With bowden extruders, it may be wise to do some amount of quick retract " "before doing the wipe movement." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1502 +#: src/libslic3r/PrintConfig.cpp:1516 msgid "Retract on layer change" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1503 +#: src/libslic3r/PrintConfig.cpp:1517 msgid "This flag enforces a retraction whenever a Z move is done." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1508 src/libslic3r/PrintConfig.cpp:1516 +#: src/libslic3r/PrintConfig.cpp:1522 src/libslic3r/PrintConfig.cpp:1530 msgid "Length" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1509 +#: src/libslic3r/PrintConfig.cpp:1523 msgid "Retraction Length" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1510 +#: src/libslic3r/PrintConfig.cpp:1524 msgid "" "When retraction is triggered, filament is pulled back by the specified " "amount (the length is measured on raw filament, before it enters the " "extruder)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1512 src/libslic3r/PrintConfig.cpp:1521 +#: src/libslic3r/PrintConfig.cpp:1526 src/libslic3r/PrintConfig.cpp:1535 msgid "mm (zero to disable)" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1517 +#: src/libslic3r/PrintConfig.cpp:1531 msgid "Retraction Length (Toolchange)" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1518 +#: src/libslic3r/PrintConfig.cpp:1532 msgid "" "When retraction is triggered before changing tool, filament is pulled back " "by the specified amount (the length is measured on raw filament, before it " "enters the extruder)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1526 +#: src/libslic3r/PrintConfig.cpp:1540 msgid "Lift Z" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1527 +#: src/libslic3r/PrintConfig.cpp:1541 msgid "" "If you set this to a positive value, Z is quickly raised every time a " "retraction is triggered. When using multiple extruders, only the setting for " "the first extruder will be considered." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1534 +#: src/libslic3r/PrintConfig.cpp:1548 msgid "Above Z" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1535 +#: src/libslic3r/PrintConfig.cpp:1549 msgid "Only lift Z above" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1536 +#: src/libslic3r/PrintConfig.cpp:1550 msgid "" "If you set this to a positive value, Z lift will only take place above the " "specified absolute Z. You can tune this setting for skipping lift on the " "first layers." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1543 +#: src/libslic3r/PrintConfig.cpp:1557 msgid "Below Z" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1544 +#: src/libslic3r/PrintConfig.cpp:1558 msgid "Only lift Z below" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1545 +#: src/libslic3r/PrintConfig.cpp:1559 msgid "" "If you set this to a positive value, Z lift will only take place below the " "specified absolute Z. You can tune this setting for limiting lift to the " "first layers." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1553 src/libslic3r/PrintConfig.cpp:1561 +#: src/libslic3r/PrintConfig.cpp:1567 src/libslic3r/PrintConfig.cpp:1575 msgid "Extra length on restart" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1554 +#: src/libslic3r/PrintConfig.cpp:1568 msgid "" "When the retraction is compensated after the travel move, the extruder will " "push this additional amount of filament. This setting is rarely needed." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1562 +#: src/libslic3r/PrintConfig.cpp:1576 msgid "" "When the retraction is compensated after changing tool, the extruder will " "push this additional amount of filament." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1569 src/libslic3r/PrintConfig.cpp:1570 +#: src/libslic3r/PrintConfig.cpp:1583 src/libslic3r/PrintConfig.cpp:1584 msgid "Retraction Speed" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1571 +#: src/libslic3r/PrintConfig.cpp:1585 msgid "The speed for retractions (it only applies to the extruder motor)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1577 src/libslic3r/PrintConfig.cpp:1578 +#: src/libslic3r/PrintConfig.cpp:1591 src/libslic3r/PrintConfig.cpp:1592 msgid "Deretraction Speed" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1579 +#: src/libslic3r/PrintConfig.cpp:1593 msgid "" "The speed for loading of a filament into extruder after retraction (it only " "applies to the extruder motor). If left to zero, the retraction speed is " "used." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1586 +#: src/libslic3r/PrintConfig.cpp:1600 msgid "Seam position" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1588 +#: src/libslic3r/PrintConfig.cpp:1602 msgid "Position of perimeters starting points." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1594 +#: src/libslic3r/PrintConfig.cpp:1608 msgid "Random" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1595 +#: src/libslic3r/PrintConfig.cpp:1609 msgid "Nearest" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1596 +#: src/libslic3r/PrintConfig.cpp:1610 msgid "Aligned" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1604 +#: src/libslic3r/PrintConfig.cpp:1618 msgid "Direction" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1606 +#: src/libslic3r/PrintConfig.cpp:1620 msgid "Preferred direction of the seam" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1607 +#: src/libslic3r/PrintConfig.cpp:1621 msgid "Seam preferred direction" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1614 +#: src/libslic3r/PrintConfig.cpp:1628 msgid "Jitter" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1616 +#: src/libslic3r/PrintConfig.cpp:1630 msgid "Seam preferred direction jitter" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1617 +#: src/libslic3r/PrintConfig.cpp:1631 msgid "Preferred direction of the seam - jitter" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1627 +#: src/libslic3r/PrintConfig.cpp:1641 msgid "USB/serial port for printer connection." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1634 +#: src/libslic3r/PrintConfig.cpp:1648 msgid "Serial port speed" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1635 +#: src/libslic3r/PrintConfig.cpp:1649 msgid "Speed (baud) of USB/serial port for printer connection." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1644 +#: src/libslic3r/PrintConfig.cpp:1658 msgid "Distance from object" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1645 +#: src/libslic3r/PrintConfig.cpp:1659 msgid "" "Distance between skirt and object(s). Set this to zero to attach the skirt " "to the object(s) and get a brim for better adhesion." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1652 +#: src/libslic3r/PrintConfig.cpp:1666 msgid "Skirt height" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1653 +#: src/libslic3r/PrintConfig.cpp:1667 msgid "" "Height of skirt expressed in layers. Set this to a tall value to use skirt " "as a shield against drafts." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1660 +#: src/libslic3r/PrintConfig.cpp:1674 msgid "Loops (minimum)" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1661 +#: src/libslic3r/PrintConfig.cpp:1675 msgid "Skirt Loops" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1662 +#: src/libslic3r/PrintConfig.cpp:1676 msgid "" "Number of loops for the skirt. If the Minimum Extrusion Length option is " "set, the number of loops might be greater than the one configured here. Set " "this to zero to disable skirt completely." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1670 +#: src/libslic3r/PrintConfig.cpp:1684 msgid "Slow down if layer print time is below" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1671 +#: src/libslic3r/PrintConfig.cpp:1685 msgid "" "If layer print time is estimated below this number of seconds, print moves " "speed will be scaled down to extend duration to this value." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1681 +#: src/libslic3r/PrintConfig.cpp:1695 msgid "Small perimeters" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1683 +#: src/libslic3r/PrintConfig.cpp:1697 msgid "" "This separate setting will affect the speed of perimeters having radius <= " "6.5mm (usually holes). If expressed as percentage (for example: 80%) it will " "be calculated on the perimeters speed setting above. Set to zero for auto." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1693 +#: src/libslic3r/PrintConfig.cpp:1707 msgid "Solid infill threshold area" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1695 +#: src/libslic3r/PrintConfig.cpp:1709 msgid "" "Force solid infill for regions having a smaller area than the specified " "threshold." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1696 +#: src/libslic3r/PrintConfig.cpp:1710 msgid "mm²" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1702 +#: src/libslic3r/PrintConfig.cpp:1716 msgid "Solid infill extruder" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1704 +#: src/libslic3r/PrintConfig.cpp:1718 msgid "The extruder to use when printing solid infill." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1710 +#: src/libslic3r/PrintConfig.cpp:1724 msgid "Solid infill every" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1712 +#: src/libslic3r/PrintConfig.cpp:1726 msgid "" "This feature allows to force a solid layer every given number of layers. " "Zero to disable. You can set this to any value (for example 9999); Slic3r " @@ -6565,7 +7066,7 @@ msgid "" "according to nozzle diameter and layer height." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1724 +#: src/libslic3r/PrintConfig.cpp:1738 msgid "" "Set this to a non-zero value to set a manual extrusion width for infill for " "solid surfaces. If left zero, default extrusion width will be used if set, " @@ -6573,22 +7074,22 @@ msgid "" "(for example 90%) it will be computed over layer height." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1734 +#: src/libslic3r/PrintConfig.cpp:1748 msgid "" "Speed for printing solid regions (top/bottom/internal horizontal shells). " "This can be expressed as a percentage (for example: 80%) over the default " "infill speed above. Set to zero for auto." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1746 +#: src/libslic3r/PrintConfig.cpp:1760 msgid "Number of solid layers to generate on top and bottom surfaces." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1752 +#: src/libslic3r/PrintConfig.cpp:1766 msgid "Spiral vase" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1753 +#: src/libslic3r/PrintConfig.cpp:1767 msgid "" "This feature will raise Z gradually while printing a single-walled object in " "order to remove any visible seam. This option requires a single perimeter, " @@ -6597,18 +7098,18 @@ msgid "" "when printing more than an object." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1761 +#: src/libslic3r/PrintConfig.cpp:1775 msgid "Temperature variation" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1762 +#: src/libslic3r/PrintConfig.cpp:1776 msgid "" "Temperature difference to be applied when an extruder is not active. Enables " "a full-height \"sacrificial\" skirt on which the nozzles are periodically " "wiped." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1772 +#: src/libslic3r/PrintConfig.cpp:1786 msgid "" "This start procedure is inserted at the beginning, after bed has reached the " "target temperature and extruder just started heating, and before extruder " @@ -6619,105 +7120,106 @@ msgid "" "\"M109 S[first_layer_temperature]\" command wherever you want." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1787 +#: src/libslic3r/PrintConfig.cpp:1801 msgid "" "This start procedure is inserted at the beginning, after any printer start " -"gcode. This is used to override settings for a specific filament. If Slic3r " -"detects M104, M109, M140 or M190 in your custom codes, such commands will " -"not be prepended automatically so you're free to customize the order of " +"gcode (and after any toolchange to this filament in case of multi-material " +"printers). This is used to override settings for a specific filament. If " +"Slic3r detects M104, M109, M140 or M190 in your custom codes, such commands " +"will not be prepended automatically so you're free to customize the order of " "heating commands and other custom actions. Note that you can use placeholder " "variables for all Slic3r settings, so you can put a \"M109 " "S[first_layer_temperature]\" command wherever you want. If you have multiple " "extruders, the gcode is processed in extruder order." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1802 +#: src/libslic3r/PrintConfig.cpp:1817 msgid "Single Extruder Multi Material" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1803 +#: src/libslic3r/PrintConfig.cpp:1818 msgid "The printer multiplexes filaments into a single hot end." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1808 +#: src/libslic3r/PrintConfig.cpp:1823 msgid "Prime all printing extruders" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1809 +#: src/libslic3r/PrintConfig.cpp:1824 msgid "" "If enabled, all printing extruders will be primed at the front edge of the " "print bed at the start of the print." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1814 +#: src/libslic3r/PrintConfig.cpp:1829 msgid "Generate support material" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1816 +#: src/libslic3r/PrintConfig.cpp:1831 msgid "Enable support material generation." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1820 +#: src/libslic3r/PrintConfig.cpp:1835 msgid "Auto generated supports" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1822 +#: src/libslic3r/PrintConfig.cpp:1837 msgid "" "If checked, supports will be generated automatically based on the overhang " "threshold value. If unchecked, supports will be generated inside the " "\"Support Enforcer\" volumes only." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1828 +#: src/libslic3r/PrintConfig.cpp:1843 msgid "XY separation between an object and its support" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1830 +#: src/libslic3r/PrintConfig.cpp:1845 msgid "" "XY separation between an object and its support. If expressed as percentage " "(for example 50%), it will be calculated over external perimeter width." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1840 +#: src/libslic3r/PrintConfig.cpp:1855 msgid "Pattern angle" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1842 +#: src/libslic3r/PrintConfig.cpp:1857 msgid "" "Use this setting to rotate the support material pattern on the horizontal " "plane." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1852 src/libslic3r/PrintConfig.cpp:2460 +#: src/libslic3r/PrintConfig.cpp:1867 src/libslic3r/PrintConfig.cpp:2531 msgid "" "Only create support if it lies on a build plate. Don't create support on a " "print." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1858 +#: src/libslic3r/PrintConfig.cpp:1873 msgid "Contact Z distance" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1860 +#: src/libslic3r/PrintConfig.cpp:1875 msgid "" "The vertical distance between object and support material interface. Setting " "this to 0 will also prevent Slic3r from using bridge flow and speed for the " "first object layer." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1867 +#: src/libslic3r/PrintConfig.cpp:1882 msgid "0 (soluble)" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1868 +#: src/libslic3r/PrintConfig.cpp:1883 msgid "0.2 (detachable)" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1873 +#: src/libslic3r/PrintConfig.cpp:1888 msgid "Enforce support for the first" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1875 +#: src/libslic3r/PrintConfig.cpp:1890 msgid "" "Generate support material for the specified number of layers counting from " "bottom, regardless of whether normal support material is enabled or not and " @@ -6725,21 +7227,21 @@ msgid "" "of objects having a very thin or poor footprint on the build plate." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1880 +#: src/libslic3r/PrintConfig.cpp:1895 msgid "Enforce support for the first n layers" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1886 +#: src/libslic3r/PrintConfig.cpp:1901 msgid "Support material/raft/skirt extruder" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1888 +#: src/libslic3r/PrintConfig.cpp:1903 msgid "" "The extruder to use when printing support material, raft and skirt (1+, 0 to " "use the current extruder to minimize tool changes)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1897 +#: src/libslic3r/PrintConfig.cpp:1912 msgid "" "Set this to a non-zero value to set a manual extrusion width for support " "material. If left zero, default extrusion width will be used if set, " @@ -6747,89 +7249,89 @@ msgid "" "example 90%) it will be computed over layer height." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1905 +#: src/libslic3r/PrintConfig.cpp:1920 msgid "Interface loops" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1907 +#: src/libslic3r/PrintConfig.cpp:1922 msgid "" "Cover the top contact layer of the supports with loops. Disabled by default." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1912 +#: src/libslic3r/PrintConfig.cpp:1927 msgid "Support material/raft interface extruder" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1914 +#: src/libslic3r/PrintConfig.cpp:1929 msgid "" "The extruder to use when printing support material interface (1+, 0 to use " "the current extruder to minimize tool changes). This affects raft too." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1921 +#: src/libslic3r/PrintConfig.cpp:1936 msgid "Interface layers" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1923 +#: src/libslic3r/PrintConfig.cpp:1938 msgid "" "Number of interface layers to insert between the object(s) and support " "material." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1930 +#: src/libslic3r/PrintConfig.cpp:1945 msgid "Interface pattern spacing" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1932 +#: src/libslic3r/PrintConfig.cpp:1947 msgid "Spacing between interface lines. Set zero to get a solid interface." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1941 +#: src/libslic3r/PrintConfig.cpp:1956 msgid "" "Speed for printing support material interface layers. If expressed as " "percentage (for example 50%) it will be calculated over support material " "speed." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1950 +#: src/libslic3r/PrintConfig.cpp:1965 msgid "Pattern" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1952 +#: src/libslic3r/PrintConfig.cpp:1967 msgid "Pattern used to generate support material." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1958 +#: src/libslic3r/PrintConfig.cpp:1973 msgid "Rectilinear grid" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1964 +#: src/libslic3r/PrintConfig.cpp:1979 msgid "Pattern spacing" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1966 +#: src/libslic3r/PrintConfig.cpp:1981 msgid "Spacing between support material lines." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1975 +#: src/libslic3r/PrintConfig.cpp:1990 msgid "Speed for printing support material." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1982 +#: src/libslic3r/PrintConfig.cpp:1997 msgid "Synchronize with object layers" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1984 +#: src/libslic3r/PrintConfig.cpp:1999 msgid "" "Synchronize support layers with the object print layers. This is useful with " "multi-material printers, where the extruder switch is expensive." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1990 +#: src/libslic3r/PrintConfig.cpp:2005 msgid "Overhang threshold" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1992 +#: src/libslic3r/PrintConfig.cpp:2007 msgid "" "Support material will not be generated for overhangs whose slope angle (90° " "= vertical) is above the given threshold. In other words, this value " @@ -6838,50 +7340,53 @@ msgid "" "detection (recommended)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2004 +#: src/libslic3r/PrintConfig.cpp:2019 msgid "With sheath around the support" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2006 +#: src/libslic3r/PrintConfig.cpp:2021 msgid "" "Add a sheath (a single perimeter line) around the base support. This makes " "the support more reliable, but also more difficult to remove." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2013 +#: src/libslic3r/PrintConfig.cpp:2028 msgid "" "Extruder temperature for layers after the first one. Set this to zero to " "disable temperature control commands in the output." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2021 +#: src/libslic3r/PrintConfig.cpp:2036 msgid "Detect thin walls" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2023 +#: src/libslic3r/PrintConfig.cpp:2038 msgid "" "Detect single-width walls (parts where two extrusions don't fit and we need " "to collapse them into a single trace)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2029 +#: src/libslic3r/PrintConfig.cpp:2044 msgid "Threads" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2030 +#: src/libslic3r/PrintConfig.cpp:2045 msgid "" "Threads are used to parallelize long-running tasks. Optimal threads number " "is slightly above the number of available cores/processors." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2042 +#: src/libslic3r/PrintConfig.cpp:2057 msgid "" -"This custom code is inserted right before every extruder change. Note that " -"you can use placeholder variables for all Slic3r settings as well as " -"[previous_extruder] and [next_extruder]." +"This custom code is inserted at every extruder change. If you don't leave " +"this empty, you are expected to take care of the toolchange yourself - " +"PrusaSlicer will not output any other G-code to change the filament. You can " +"use placeholder variables for all Slic3r settings as well as " +"[previous_extruder] and [next_extruder], so e.g. the standard toolchange " +"command can be scripted as T[next_extruder]." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2054 +#: src/libslic3r/PrintConfig.cpp:2070 msgid "" "Set this to a non-zero value to set a manual extrusion width for infill for " "top surfaces. You may want to use thinner extrudates to fill all narrow " @@ -6890,7 +7395,7 @@ msgid "" "percentage (for example 90%) it will be computed over layer height." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2065 +#: src/libslic3r/PrintConfig.cpp:2081 msgid "" "Speed for printing top solid layers (it only applies to the uppermost " "external layers and not to their internal solid layers). You may want to " @@ -6899,43 +7404,43 @@ msgid "" "for auto." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2080 +#: src/libslic3r/PrintConfig.cpp:2096 msgid "Number of solid layers to generate on top surfaces." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2081 +#: src/libslic3r/PrintConfig.cpp:2097 msgid "Top solid layers" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2087 +#: src/libslic3r/PrintConfig.cpp:2103 msgid "Speed for travel moves (jumps between distant extrusion points)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2095 +#: src/libslic3r/PrintConfig.cpp:2111 msgid "Use firmware retraction" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2096 +#: src/libslic3r/PrintConfig.cpp:2112 msgid "" "This experimental setting uses G10 and G11 commands to have the firmware " "handle the retraction. This is only supported in recent Marlin." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2102 +#: src/libslic3r/PrintConfig.cpp:2118 msgid "Use relative E distances" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2103 +#: src/libslic3r/PrintConfig.cpp:2119 msgid "" "If your firmware requires relative E values, check this, otherwise leave it " "unchecked. Most firmwares use absolute values." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2109 +#: src/libslic3r/PrintConfig.cpp:2125 msgid "Use volumetric E" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2110 +#: src/libslic3r/PrintConfig.cpp:2126 msgid "" "This experimental setting uses outputs the E values in cubic millimeters " "instead of linear millimeters. If your firmware doesn't already know " @@ -6945,127 +7450,127 @@ msgid "" "only supported in recent Marlin." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2120 +#: src/libslic3r/PrintConfig.cpp:2136 msgid "Enable variable layer height feature" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2121 +#: src/libslic3r/PrintConfig.cpp:2137 msgid "" "Some printers or printer setups may have difficulties printing with a " "variable layer height. Enabled by default." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2127 +#: src/libslic3r/PrintConfig.cpp:2143 msgid "Wipe while retracting" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2128 +#: src/libslic3r/PrintConfig.cpp:2144 msgid "" "This flag will move the nozzle while retracting to minimize the possible " "blob on leaky extruders." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2135 +#: src/libslic3r/PrintConfig.cpp:2151 msgid "" "Multi material printers may need to prime or purge extruders on tool " "changes. Extrude the excess material into the wipe tower." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2141 +#: src/libslic3r/PrintConfig.cpp:2157 msgid "Purging volumes - load/unload volumes" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2142 +#: src/libslic3r/PrintConfig.cpp:2158 msgid "" "This vector saves required volumes to change from/to each tool used on the " "wipe tower. These values are used to simplify creation of the full purging " "volumes below." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2148 +#: src/libslic3r/PrintConfig.cpp:2164 msgid "Purging volumes - matrix" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2149 +#: src/libslic3r/PrintConfig.cpp:2165 msgid "" "This matrix describes volumes (in cubic milimetres) required to purge the " "new filament on the wipe tower for any given pair of tools." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2158 +#: src/libslic3r/PrintConfig.cpp:2174 msgid "Position X" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2159 +#: src/libslic3r/PrintConfig.cpp:2175 msgid "X coordinate of the left front corner of a wipe tower" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2165 +#: src/libslic3r/PrintConfig.cpp:2181 msgid "Position Y" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2166 +#: src/libslic3r/PrintConfig.cpp:2182 msgid "Y coordinate of the left front corner of a wipe tower" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2173 +#: src/libslic3r/PrintConfig.cpp:2189 msgid "Width of a wipe tower" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2179 +#: src/libslic3r/PrintConfig.cpp:2195 msgid "Wipe tower rotation angle" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2180 +#: src/libslic3r/PrintConfig.cpp:2196 msgid "Wipe tower rotation angle with respect to x-axis." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2187 +#: src/libslic3r/PrintConfig.cpp:2203 msgid "Wipe into this object's infill" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2188 +#: src/libslic3r/PrintConfig.cpp:2204 msgid "" "Purging after toolchange will done inside this object's infills. This lowers " "the amount of waste but may result in longer print time due to additional " "travel moves." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2195 +#: src/libslic3r/PrintConfig.cpp:2211 msgid "Wipe into this object" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2196 +#: src/libslic3r/PrintConfig.cpp:2212 msgid "" "Object will be used to purge the nozzle after a toolchange to save material " "that would otherwise end up in the wipe tower and decrease print time. " "Colours of the objects will be mixed as a result." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2202 +#: src/libslic3r/PrintConfig.cpp:2218 msgid "Maximal bridging distance" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2203 +#: src/libslic3r/PrintConfig.cpp:2219 msgid "Maximal distance between supports on sparse infill sections." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2209 +#: src/libslic3r/PrintConfig.cpp:2225 msgid "XY Size Compensation" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2211 +#: src/libslic3r/PrintConfig.cpp:2227 msgid "" "The object will be grown/shrunk in the XY plane by the configured value " "(negative = inwards, positive = outwards). This might be useful for fine-" "tuning hole sizes." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2219 +#: src/libslic3r/PrintConfig.cpp:2235 msgid "Z offset" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2220 +#: src/libslic3r/PrintConfig.cpp:2236 msgid "" "This value will be added (or subtracted) from all the Z coordinates in the " "output G-code. It is used to compensate for bad Z endstop position: for " @@ -7073,308 +7578,345 @@ msgid "" "print bed, set this to -0.3 (or fix your endstop)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2237 +#: src/libslic3r/PrintConfig.cpp:2294 msgid "Display width" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2238 +#: src/libslic3r/PrintConfig.cpp:2295 msgid "Width of the display" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2243 +#: src/libslic3r/PrintConfig.cpp:2300 msgid "Display height" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2244 +#: src/libslic3r/PrintConfig.cpp:2301 msgid "Height of the display" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2249 +#: src/libslic3r/PrintConfig.cpp:2306 msgid "Number of pixels in" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2251 +#: src/libslic3r/PrintConfig.cpp:2308 msgid "Number of pixels in X" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2257 +#: src/libslic3r/PrintConfig.cpp:2314 msgid "Number of pixels in Y" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2262 +#: src/libslic3r/PrintConfig.cpp:2319 +msgid "Display horizontal mirroring" +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2320 +msgid "Mirror horizontally" +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2321 +msgid "Enable horizontal mirroring of output images" +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2326 +msgid "Display vertical mirroring" +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2327 +msgid "Mirror vertically" +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2328 +msgid "Enable vertical mirroring of output images" +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2333 msgid "Display orientation" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2263 +#: src/libslic3r/PrintConfig.cpp:2334 msgid "" "Set the actual LCD display orientation inside the SLA printer. Portrait mode " "will flip the meaning of display width and height parameters and the output " "images will be rotated by 90 degrees." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2269 +#: src/libslic3r/PrintConfig.cpp:2340 msgid "Landscape" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2270 +#: src/libslic3r/PrintConfig.cpp:2341 msgid "Portrait" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2275 +#: src/libslic3r/PrintConfig.cpp:2346 msgid "Fast" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2276 +#: src/libslic3r/PrintConfig.cpp:2347 msgid "Fast tilt" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2277 +#: src/libslic3r/PrintConfig.cpp:2348 msgid "Time of the fast tilt" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2284 +#: src/libslic3r/PrintConfig.cpp:2355 msgid "Slow" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2285 +#: src/libslic3r/PrintConfig.cpp:2356 msgid "Slow tilt" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2286 +#: src/libslic3r/PrintConfig.cpp:2357 msgid "Time of the slow tilt" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2293 +#: src/libslic3r/PrintConfig.cpp:2364 msgid "Area fill" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2294 +#: src/libslic3r/PrintConfig.cpp:2365 msgid "" "The percentage of the bed area. \n" "If the print area exceeds the specified value, \n" "then a slow tilt will be used, otherwise - a fast tilt" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2301 src/libslic3r/PrintConfig.cpp:2302 -#: src/libslic3r/PrintConfig.cpp:2303 +#: src/libslic3r/PrintConfig.cpp:2372 src/libslic3r/PrintConfig.cpp:2373 +#: src/libslic3r/PrintConfig.cpp:2374 msgid "Printer scaling correction" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2309 src/libslic3r/PrintConfig.cpp:2310 +#: src/libslic3r/PrintConfig.cpp:2380 src/libslic3r/PrintConfig.cpp:2381 msgid "Printer absolute correction" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2311 +#: src/libslic3r/PrintConfig.cpp:2382 msgid "" "Will inflate or deflate the sliced 2D polygons according to the sign of the " "correction." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2317 src/libslic3r/PrintConfig.cpp:2318 +#: src/libslic3r/PrintConfig.cpp:2388 src/libslic3r/PrintConfig.cpp:2389 msgid "Printer gamma correction" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2319 +#: src/libslic3r/PrintConfig.cpp:2390 msgid "" "This will apply a gamma correction to the rasterized 2D polygons. A gamma " "value of zero means thresholding with the threshold in the middle. This " "behaviour eliminates antialiasing without losing holes in polygons." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2330 src/libslic3r/PrintConfig.cpp:2331 +#: src/libslic3r/PrintConfig.cpp:2401 src/libslic3r/PrintConfig.cpp:2402 msgid "Initial layer height" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2337 +#: src/libslic3r/PrintConfig.cpp:2408 msgid "Faded layers" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2338 +#: src/libslic3r/PrintConfig.cpp:2409 msgid "" "Number of the layers needed for the exposure time fade from initial exposure " "time to the exposure time" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2345 src/libslic3r/PrintConfig.cpp:2346 +#: src/libslic3r/PrintConfig.cpp:2416 src/libslic3r/PrintConfig.cpp:2417 msgid "Exposure time" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2352 src/libslic3r/PrintConfig.cpp:2353 +#: src/libslic3r/PrintConfig.cpp:2423 src/libslic3r/PrintConfig.cpp:2424 msgid "Initial exposure time" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2359 src/libslic3r/PrintConfig.cpp:2360 +#: src/libslic3r/PrintConfig.cpp:2430 src/libslic3r/PrintConfig.cpp:2431 msgid "Correction for expansion" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2366 +#: src/libslic3r/PrintConfig.cpp:2437 msgid "SLA print material notes" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2367 +#: src/libslic3r/PrintConfig.cpp:2438 msgid "You can put your notes regarding the SLA print material here." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2375 src/libslic3r/PrintConfig.cpp:2386 +#: src/libslic3r/PrintConfig.cpp:2446 src/libslic3r/PrintConfig.cpp:2457 msgid "Default SLA material profile" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2397 +#: src/libslic3r/PrintConfig.cpp:2468 msgid "Generate supports" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2399 +#: src/libslic3r/PrintConfig.cpp:2470 msgid "Generate supports for the models" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2404 +#: src/libslic3r/PrintConfig.cpp:2475 msgid "Support head front diameter" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2406 +#: src/libslic3r/PrintConfig.cpp:2477 msgid "Diameter of the pointing side of the head" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2413 +#: src/libslic3r/PrintConfig.cpp:2484 msgid "Support head penetration" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2415 +#: src/libslic3r/PrintConfig.cpp:2486 msgid "How much the pinhead has to penetrate the model surface" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2422 +#: src/libslic3r/PrintConfig.cpp:2493 msgid "Support head width" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2424 +#: src/libslic3r/PrintConfig.cpp:2495 msgid "Width from the back sphere center to the front sphere center" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2432 +#: src/libslic3r/PrintConfig.cpp:2503 msgid "Support pillar diameter" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2434 +#: src/libslic3r/PrintConfig.cpp:2505 msgid "Diameter in mm of the support pillars" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2442 +#: src/libslic3r/PrintConfig.cpp:2513 msgid "Support pillar connection mode" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2443 +#: src/libslic3r/PrintConfig.cpp:2514 msgid "" "Controls the bridge type between two neighboring pillars. Can be zig-zag, " "cross (double zig-zag) or dynamic which will automatically switch between " "the first two depending on the distance of the two pillars." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2451 +#: src/libslic3r/PrintConfig.cpp:2522 msgid "Zig-Zag" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2452 +#: src/libslic3r/PrintConfig.cpp:2523 msgid "Cross" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2453 +#: src/libslic3r/PrintConfig.cpp:2524 msgid "Dynamic" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2465 +#: src/libslic3r/PrintConfig.cpp:2536 msgid "Pillar widening factor" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2467 +#: src/libslic3r/PrintConfig.cpp:2538 msgid "" "Merging bridges or pillars into another pillars can increase the radius. " "Zero means no increase, one means full increase." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2476 +#: src/libslic3r/PrintConfig.cpp:2547 msgid "Support base diameter" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2478 +#: src/libslic3r/PrintConfig.cpp:2549 msgid "Diameter in mm of the pillar base" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2486 +#: src/libslic3r/PrintConfig.cpp:2557 msgid "Support base height" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2488 +#: src/libslic3r/PrintConfig.cpp:2559 msgid "The height of the pillar base cone" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2495 +#: src/libslic3r/PrintConfig.cpp:2566 +msgid "Support base safety distance" +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2569 +msgid "" +"The minimum distance of the pillar base from the model in mm. Makes sense in " +"zero elevation mode where a gap according to this parameter is inserted " +"between the model and the pad." +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2579 msgid "Critical angle" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2497 +#: src/libslic3r/PrintConfig.cpp:2581 msgid "The default angle for connecting support sticks and junctions." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2505 +#: src/libslic3r/PrintConfig.cpp:2589 msgid "Max bridge length" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2507 +#: src/libslic3r/PrintConfig.cpp:2591 msgid "The max length of a bridge" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2514 +#: src/libslic3r/PrintConfig.cpp:2598 msgid "Max pillar linking distance" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2516 +#: src/libslic3r/PrintConfig.cpp:2600 msgid "" "The max distance of two pillars to get linked with each other. A zero value " "will prohibit pillar cascading." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2524 +#: src/libslic3r/PrintConfig.cpp:2608 msgid "Object elevation" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2526 -msgid "How much the supports should lift up the supported object." +#: src/libslic3r/PrintConfig.cpp:2610 +msgid "" +"How much the supports should lift up the supported object. If this value is " +"zero, the bottom of the model geometry will be considered as part of the pad." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2536 +#: src/libslic3r/PrintConfig.cpp:2622 msgid "This is a relative measure of support points density." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2542 +#: src/libslic3r/PrintConfig.cpp:2628 msgid "Minimal distance of the support points" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2544 +#: src/libslic3r/PrintConfig.cpp:2630 msgid "No support points will be placed closer than this threshold." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2550 +#: src/libslic3r/PrintConfig.cpp:2636 msgid "Use pad" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2552 +#: src/libslic3r/PrintConfig.cpp:2638 msgid "Add a pad underneath the supported model" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2557 +#: src/libslic3r/PrintConfig.cpp:2643 msgid "Pad wall thickness" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2559 +#: src/libslic3r/PrintConfig.cpp:2645 msgid "The thickness of the pad and its optional cavity walls." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2567 +#: src/libslic3r/PrintConfig.cpp:2653 msgid "Pad wall height" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2568 +#: src/libslic3r/PrintConfig.cpp:2654 msgid "" "Defines the pad cavity depth. Set to zero to disable the cavity. Be careful " "when enabling this feature, as some resins may produce an extreme suction " @@ -7382,279 +7924,317 @@ msgid "" "difficult." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2581 +#: src/libslic3r/PrintConfig.cpp:2667 msgid "Max merge distance" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2583 +#: src/libslic3r/PrintConfig.cpp:2669 msgid "" "Some objects can get along with a few smaller pads instead of a single big " "one. This parameter defines how far the center of two smaller pads should " "be. If theyare closer, they will get merged into one pad." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2594 +#: src/libslic3r/PrintConfig.cpp:2680 msgid "Pad edge radius" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2603 +#: src/libslic3r/PrintConfig.cpp:2689 msgid "Pad wall slope" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2605 +#: src/libslic3r/PrintConfig.cpp:2691 msgid "" "The slope of the pad wall relative to the bed plane. 90 degrees means " "straight walls." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2967 +#: src/libslic3r/PrintConfig.cpp:2700 +msgid "Pad object gap" +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2702 +msgid "" +"The gap between the object bottom and the generated pad in zero elevation " +"mode." +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2711 +msgid "Pad object connector stride" +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2713 +msgid "" +"Distance between two connector sticks between the object pad and the " +"generated pad." +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2721 +msgid "Pad object connector width" +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2723 +msgid "" +"The width of the connectors sticks which connect the object pad and the " +"generated pad." +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2731 +msgid "Pad object connector penetration" +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2734 +msgid "How much should the tiny connectors penetrate into the model body." +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:3094 msgid "Export OBJ" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2968 +#: src/libslic3r/PrintConfig.cpp:3095 msgid "Export the model(s) as OBJ." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2979 +#: src/libslic3r/PrintConfig.cpp:3106 msgid "Export SLA" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2980 +#: src/libslic3r/PrintConfig.cpp:3107 msgid "Slice the model and export SLA printing layers as PNG." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2985 +#: src/libslic3r/PrintConfig.cpp:3112 msgid "Export 3MF" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2986 +#: src/libslic3r/PrintConfig.cpp:3113 msgid "Export the model(s) as 3MF." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2990 +#: src/libslic3r/PrintConfig.cpp:3117 msgid "Export AMF" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2991 +#: src/libslic3r/PrintConfig.cpp:3118 msgid "Export the model(s) as AMF." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2995 +#: src/libslic3r/PrintConfig.cpp:3122 msgid "Export STL" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2996 +#: src/libslic3r/PrintConfig.cpp:3123 msgid "Export the model(s) as STL." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3001 +#: src/libslic3r/PrintConfig.cpp:3128 msgid "Slice the model and export toolpaths as G-code." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3006 +#: src/libslic3r/PrintConfig.cpp:3133 msgid "Slice" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3007 +#: src/libslic3r/PrintConfig.cpp:3134 msgid "" "Slice the model as FFF or SLA based on the printer_technology configuration " "value." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3012 +#: src/libslic3r/PrintConfig.cpp:3139 msgid "Help" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3013 +#: src/libslic3r/PrintConfig.cpp:3140 msgid "Show this help." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3018 +#: src/libslic3r/PrintConfig.cpp:3145 msgid "Help (FFF options)" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3019 +#: src/libslic3r/PrintConfig.cpp:3146 msgid "Show the full list of print/G-code configuration options." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3023 +#: src/libslic3r/PrintConfig.cpp:3150 msgid "Help (SLA options)" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3024 +#: src/libslic3r/PrintConfig.cpp:3151 msgid "Show the full list of SLA print configuration options." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3028 +#: src/libslic3r/PrintConfig.cpp:3155 msgid "Output Model Info" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3029 +#: src/libslic3r/PrintConfig.cpp:3156 msgid "Write information about the model to the console." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3033 +#: src/libslic3r/PrintConfig.cpp:3160 msgid "Save config file" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3034 +#: src/libslic3r/PrintConfig.cpp:3161 msgid "Save configuration to the specified file." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3044 +#: src/libslic3r/PrintConfig.cpp:3171 msgid "Align XY" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3045 +#: src/libslic3r/PrintConfig.cpp:3172 msgid "Align the model to the given point." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3050 +#: src/libslic3r/PrintConfig.cpp:3177 msgid "Cut model at the given Z." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3071 +#: src/libslic3r/PrintConfig.cpp:3198 msgid "Center" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3072 +#: src/libslic3r/PrintConfig.cpp:3199 msgid "Center the print around the given center." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3076 +#: src/libslic3r/PrintConfig.cpp:3203 msgid "Don't arrange" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3077 +#: src/libslic3r/PrintConfig.cpp:3204 msgid "" "Do not rearrange the given models before merging and keep their original XY " "coordinates." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3080 +#: src/libslic3r/PrintConfig.cpp:3207 msgid "Duplicate" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3081 +#: src/libslic3r/PrintConfig.cpp:3208 msgid "Multiply copies by this factor." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3085 +#: src/libslic3r/PrintConfig.cpp:3212 msgid "Duplicate by grid" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3086 +#: src/libslic3r/PrintConfig.cpp:3213 msgid "Multiply copies by creating a grid." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3089 +#: src/libslic3r/PrintConfig.cpp:3216 msgid "Merge" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3090 +#: src/libslic3r/PrintConfig.cpp:3217 msgid "" "Arrange the supplied models in a plate and merge them in a single model in " "order to perform actions once." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3095 +#: src/libslic3r/PrintConfig.cpp:3222 msgid "" "Try to repair any non-manifold meshes (this option is implicitly added " "whenever we need to slice the model to perform the requested action)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3099 +#: src/libslic3r/PrintConfig.cpp:3226 msgid "Rotation angle around the Z axis in degrees." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3103 +#: src/libslic3r/PrintConfig.cpp:3230 msgid "Rotate around X" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3104 +#: src/libslic3r/PrintConfig.cpp:3231 msgid "Rotation angle around the X axis in degrees." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3108 +#: src/libslic3r/PrintConfig.cpp:3235 msgid "Rotate around Y" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3109 +#: src/libslic3r/PrintConfig.cpp:3236 msgid "Rotation angle around the Y axis in degrees." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3114 +#: src/libslic3r/PrintConfig.cpp:3241 msgid "Scaling factor or percentage." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3119 +#: src/libslic3r/PrintConfig.cpp:3246 msgid "" "Detect unconnected parts in the given model(s) and split them into separate " "objects." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3122 +#: src/libslic3r/PrintConfig.cpp:3249 msgid "Scale to Fit" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3123 +#: src/libslic3r/PrintConfig.cpp:3250 msgid "Scale to fit the given volume." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3132 +#: src/libslic3r/PrintConfig.cpp:3259 msgid "Ignore non-existent config files" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3133 +#: src/libslic3r/PrintConfig.cpp:3260 msgid "Do not fail if a file supplied to --load does not exist." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3136 +#: src/libslic3r/PrintConfig.cpp:3263 msgid "Load config file" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3137 +#: src/libslic3r/PrintConfig.cpp:3264 msgid "" "Load configuration from the specified file. It can be used more than once to " "load options from multiple files." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3140 +#: src/libslic3r/PrintConfig.cpp:3267 msgid "Output File" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3141 +#: src/libslic3r/PrintConfig.cpp:3268 msgid "" "The file where the output will be written (if not specified, it will be " "based on the input file)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3151 +#: src/libslic3r/PrintConfig.cpp:3278 msgid "Data directory" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3152 +#: src/libslic3r/PrintConfig.cpp:3279 msgid "" "Load and store settings at the given directory. This is useful for " "maintaining different profiles or including configurations from a network " "storage." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3155 +#: src/libslic3r/PrintConfig.cpp:3282 msgid "Logging level" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3156 +#: src/libslic3r/PrintConfig.cpp:3283 msgid "" "Messages with severity lower or eqal to the loglevel will be printed out. 0:" "trace, 1:debug, 2:info, 3:warning, 4:error, 5:fatal" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3161 +#: src/libslic3r/PrintConfig.cpp:3288 msgid "Render with a software renderer" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3162 +#: src/libslic3r/PrintConfig.cpp:3289 msgid "" "Render with a software renderer. The bundled MESA software renderer is " "loaded instead of the default OpenGL driver." @@ -7696,21 +8276,21 @@ msgstr "" msgid "Volumetric flow rate (mm3/s)" msgstr "" -#: src/libslic3r/GCode/PreviewData.cpp:491 +#: src/libslic3r/GCode/PreviewData.cpp:493 msgid "Default print color" msgstr "" -#: src/libslic3r/GCode/PreviewData.cpp:495 +#: src/libslic3r/GCode/PreviewData.cpp:500 #, possible-c-format msgid "up to %.2f mm" msgstr "" -#: src/libslic3r/GCode/PreviewData.cpp:499 +#: src/libslic3r/GCode/PreviewData.cpp:504 #, possible-c-format msgid "above %.2f mm" msgstr "" -#: src/libslic3r/GCode/PreviewData.cpp:504 +#: src/libslic3r/GCode/PreviewData.cpp:509 #, possible-c-format msgid "%.2f - %.2f mm" msgstr "" diff --git a/resources/models/mk2_bed.stl b/resources/models/mk2_bed.stl index 07b7655ea6..8a74a23495 100644 Binary files a/resources/models/mk2_bed.stl and b/resources/models/mk2_bed.stl differ diff --git a/resources/models/mk3_bed.stl b/resources/models/mk3_bed.stl index 1bd1a8faac..6aff36f0bc 100644 Binary files a/resources/models/mk3_bed.stl and b/resources/models/mk3_bed.stl differ diff --git a/resources/models/sl1_bed.stl b/resources/models/sl1_bed.stl index b2cadde4bd..4a6980b09f 100644 Binary files a/resources/models/sl1_bed.stl and b/resources/models/sl1_bed.stl differ diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index de5e3d1060..a7fd819d41 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,4 +1,9 @@ +min_slic3r_version = 2.1.0-alpha0 +1.0.0-alpha0 Filament specific retract for PET and similar copolymers, and for FLEX min_slic3r_version = 1.42.0-alpha6 +0.8.3 FW version and SL1 materials update +0.8.2 FFF and SL1 settings update +0.8.1 Output settings and SLA materials update 0.8.0 Updated for the PrusaSlicer 2.0.0 final release 0.8.0-rc2 Updated firmware versions for MK2.5/S and MK3/S 0.8.0-rc1 Updated SLA profiles @@ -18,6 +23,8 @@ min_slic3r_version = 1.42.0-alpha 0.4.0-alpha3 Update of SLA profiles 0.4.0-alpha2 First SLA profiles min_slic3r_version = 1.41.3-alpha +0.4.8 MK2.5/3/S FW update +0.4.7 MK2/S/MMU FW update 0.4.6 Updated firmware versions for MK2.5/S and MK3/S 0.4.5 Enabled remaining time support for MK2/S/MMU1 0.4.4 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt @@ -26,6 +33,8 @@ min_slic3r_version = 1.41.3-alpha 0.4.1 New MK2.5S and MK3S FW versions 0.4.0 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt min_slic3r_version = 1.41.1 +0.3.8 MK2.5/3/S FW update +0.3.7 MK2/S/MMU FW update 0.3.6 Updated firmware versions for MK2.5 and MK3 0.3.5 New MK2.5 and MK3 FW versions 0.3.4 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt @@ -60,6 +69,8 @@ min_slic3r_version = 1.41.0-alpha 0.2.0-alpha1 added initial profiles for the i3 MK3 Multi Material Upgrade 2.0 0.2.0-alpha moved machine limits from the start G-code to the new print profile parameters min_slic3r_version = 1.40.0 +0.1.16 MK2.5/3/S FW update +0.1.15 MK2/S/MMU FW update 0.1.14 Updated firmware versions for MK2.5 and MK3 0.1.13 New MK2.5 and MK3 FW versions 0.1.12 New MK2.5 and MK3 FW versions diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 9db3a7a14e..b78eb7f540 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -4,8 +4,8 @@ # Vendor name will be shown by the Config Wizard. name = Prusa Research # Configuration version of this file. Config file will only be installed, if the config_version differs. -# This means, the server may force the Slic3r configuration to be downgraded. -config_version = 0.8.0 +# This means, the server may force the PrusaSlicer configuration to be downgraded. +config_version = 1.0.0-alpha0 # Where to get the updates from? config_update_url = http://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaResearch/ changelog_url = http://files.prusa3d.com/?latest=slicer-profiles&lng=%1% @@ -105,8 +105,8 @@ external_fill_pattern = rectilinear external_perimeters_first = 0 external_perimeter_extrusion_width = 0.45 extra_perimeters = 0 -extruder_clearance_height = 20 -extruder_clearance_radius = 20 +extruder_clearance_height = 25 +extruder_clearance_radius = 45 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% @@ -133,7 +133,7 @@ notes = overhangs = 0 only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 -output_filename_format = {input_filename_base}_{layer_height}mm_{filament_type[0]}_{printer_model}.gcode +output_filename_format = {input_filename_base}_{layer_height}mm_{filament_type[0]}_{printer_model}_{print_time}.gcode perimeters = 2 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 @@ -213,6 +213,7 @@ support_material_interface_layers = 0 support_material_interface_spacing = 0.15 support_material_spacing = 1 support_material_xy_spacing = 150% +output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{filament_type[0]}_{printer_model}_{print_time}.gcode [print:*0.25nozzleMK3*] external_perimeter_extrusion_width = 0.25 @@ -245,6 +246,7 @@ max_print_speed = 80 perimeters = 3 fill_pattern = grid fill_density = 20% +output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{filament_type[0]}_{printer_model}_{print_time}.gcode # Print parameters common to a 0.6mm diameter nozzle. [print:*0.6nozzle*] @@ -256,6 +258,7 @@ perimeter_extrusion_width = 0.65 solid_infill_extrusion_width = 0.65 top_infill_extrusion_width = 0.6 support_material_extrusion_width = 0.55 +output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{filament_type[0]}_{printer_model}_{print_time}.gcode [print:*0.6nozzleMK3*] external_perimeter_extrusion_width = 0.65 @@ -268,6 +271,7 @@ top_infill_extrusion_width = 0.6 support_material_extrusion_width = 0.55 bridge_flow_ratio = 0.95 bridge_speed = 25 +output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{filament_type[0]}_{printer_model}_{print_time}.gcode [print:*soluble_support*] overhangs = 1 @@ -807,7 +811,7 @@ compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and # MK2 MMU # [print:0.35mm FAST sol full 0.6 nozzle] inherits = *0.35mm*; *0.6nozzle*; *soluble_support* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 and num_extruders>1 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_model=="MK2SMM" and nozzle_diameter[0]==0.6 and num_extruders>1 external_perimeter_extrusion_width = 0.6 external_perimeter_speed = 30 notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder @@ -872,8 +876,8 @@ solid_infill_speed = 70 top_solid_infill_speed = 45 external_perimeter_extrusion_width = 0.7 perimeter_extrusion_width = 0.7 -infill_extrusion_width = 0.72 -solid_infill_extrusion_width = 0.72 +infill_extrusion_width = 0.7 +solid_infill_extrusion_width = 0.7 # XXXXXXXXXXXXXXXXXXXXXX # XXX----- MK2.5 ----XXX @@ -987,7 +991,7 @@ first_layer_temperature = 215 max_fan_speed = 100 min_fan_speed = 100 temperature = 210 -start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{elsif nozzle_diameter[0]==0.6}15{else}30{endif} ; Filament gcode" +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{elsif nozzle_diameter[0]==0.6}18{else}30{endif} ; Filament gcode" [filament:*PET*] inherits = *common* @@ -1003,14 +1007,24 @@ first_layer_bed_temperature = 85 first_layer_temperature = 230 max_fan_speed = 50 min_fan_speed = 30 -start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{elsif nozzle_diameter[0]==0.6}22{else}45{endif} ; Filament gcode" +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{elsif nozzle_diameter[0]==0.6}24{else}45{endif} ; Filament gcode" temperature = 240 +filament_retract_length = 1.4 +filament_retract_lift = 0.2 +compatible_printers_condition = printer_model!="MK2SMM" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) [filament:*PET06*] inherits = *PET* -compatible_printers_condition = nozzle_diameter[0]==0.6 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) +compatible_printers_condition = nozzle_diameter[0]==0.6 and printer_model!="MK2SMM" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) filament_max_volumetric_speed = 15 +[filament:*PETMMU1*] +inherits = *PET* +filament_retract_length = nil +filament_retract_speed = nil +filament_retract_lift = 0.2 +compatible_printers_condition = printer_model=="MK2SMM" + [filament:*ABS*] inherits = *common* bed_temperature = 110 @@ -1028,14 +1042,14 @@ first_layer_temperature = 255 max_fan_speed = 30 min_fan_speed = 20 temperature = 255 -start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{elsif nozzle_diameter[0]==0.6}15{else}30{endif} ; Filament gcode" +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{elsif nozzle_diameter[0]==0.6}18{else}30{endif} ; Filament gcode" [filament:*FLEX*] inherits = *common* bed_temperature = 50 bridge_fan_speed = 100 # For now, all but selected filaments are disabled for the MMU 2.0 -compatible_printers_condition = nozzle_diameter[0]>0.35 and num_extruders==1 && ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material) +compatible_printers_condition = nozzle_diameter[0]>0.35 and printer_model!="MK2SMM" and num_extruders==1 && ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material) cooling = 0 disable_fan_first_layers = 1 extrusion_multiplier = 1.2 @@ -1048,8 +1062,10 @@ first_layer_bed_temperature = 50 first_layer_temperature = 240 max_fan_speed = 90 min_fan_speed = 70 -start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +start_filament_gcode = "M900 K0"; Filament gcode" temperature = 240 +filament_retract_length = 0.4 +filament_retract_lift = 0 [filament:ColorFabb Brass Bronze] inherits = *PLA* @@ -1120,7 +1136,7 @@ temperature = 270 [filament:ColorFabb XT-CF20] inherits = *PET* -compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) +compatible_printers_condition = nozzle_diameter[0]>0.35 and printer_model!="MK2SMM" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) extrusion_multiplier = 1.2 filament_cost = 80.65 filament_density = 1.35 @@ -1130,6 +1146,8 @@ first_layer_bed_temperature = 90 first_layer_temperature = 260 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 260 +filament_retract_length = nil +filament_retract_lift = 0.2 [filament:ColorFabb nGen] inherits = *PET* @@ -1159,16 +1177,20 @@ first_layer_temperature = 260 max_fan_speed = 35 min_fan_speed = 20 temperature = 260 +filament_retract_length = nil +filament_retract_lift = 0 +compatible_printers_condition = nozzle_diameter[0]>0.35 and num_extruders==1 && ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material) [filament:E3D Edge] inherits = *PET* filament_cost = 56.9 filament_density = 1.26 -filament_notes = "List of manufacturers tested with standard PET print settings:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladec PETG" +filament_type = EDGE [filament:E3D PC-ABS] inherits = *ABS* filament_cost = 0 +filament_type = PC filament_density = 1.05 first_layer_temperature = 270 temperature = 270 @@ -1187,12 +1209,13 @@ filament_density = 1.04 fan_always_on = 1 first_layer_temperature = 265 temperature = 265 +filament_type = ASA -[filament:Fillamentum CPE HG100 HM100] +[filament:Fillamentum CPE] inherits = *PET* filament_cost = 54.1 filament_density = 1.25 -filament_notes = "CPE HG100 , CPE HM100" +filament_type = CPE first_layer_bed_temperature = 90 first_layer_temperature = 275 max_fan_speed = 50 @@ -1230,10 +1253,20 @@ filament_cost = 25.4 filament_density = 1.24 filament_notes = "List of materials tested with standard PLA print settings:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" +[filament:Generic FLEX] +inherits = *FLEX* +filament_cost = 82 +filament_density = 1.22 +filament_max_volumetric_speed = 1.2 +filament_retract_length = 0 +filament_retract_speed = nil +filament_retract_lift = nil + [filament:Polymaker PC-Max] inherits = *ABS* filament_cost = 77.3 filament_density = 1.20 +filament_type = PC bed_temperature = 115 filament_colour = #3A80CA first_layer_bed_temperature = 100 @@ -1304,7 +1337,7 @@ inherits = *PET* filament_cost = 27.82 filament_density = 1.27 filament_notes = "List of manufacturers tested with standard PET print settings:\n\nE3D Edge\nPlasty Mladec PETG" -compatible_printers_condition = nozzle_diameter[0]!=0.6 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) +compatible_printers_condition = nozzle_diameter[0]!=0.6 and printer_model!="MK2SMM" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) [filament:Prusament PETG] inherits = *PET* @@ -1312,7 +1345,8 @@ first_layer_temperature = 240 temperature = 250 filament_cost = 24.99 filament_density = 1.27 -compatible_printers_condition = nozzle_diameter[0]!=0.6 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) +filament_type = PETG +compatible_printers_condition = nozzle_diameter[0]!=0.6 and printer_model!="MK2SMM" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) [filament:Prusa PET 0.6 nozzle] inherits = *PET06* @@ -1326,10 +1360,11 @@ first_layer_temperature = 240 temperature = 250 filament_cost = 24.99 filament_density = 1.27 +filament_type = PETG [filament:*PET MMU2*] inherits = Prusa PET -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material +compatible_printers_condition = nozzle_diameter[0]!=0.6 and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material temperature = 230 first_layer_temperature = 230 filament_cooling_final_speed = 1 @@ -1343,6 +1378,13 @@ filament_unload_time = 12 filament_unloading_speed = 20 filament_unloading_speed_start = 120 filament_loading_speed_start = 19 +filament_retract_length = 1.4 +filament_retract_lift = 0.2 + +[filament:*PET MMU2 06*] +inherits = *PET MMU2* +compatible_printers_condition = nozzle_diameter[0]==0.6 and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material +filament_max_volumetric_speed = 13 [filament:Generic PET MMU2] inherits = *PET MMU2* @@ -1352,12 +1394,23 @@ inherits = *PET MMU2* [filament:Prusament PETG MMU2] inherits = *PET MMU2* +filament_type = PETG + +[filament:Generic PET MMU2 0.6 nozzle] +inherits = *PET MMU2 06* + +[filament:Prusa PET MMU2 0.6 nozzle] +inherits = *PET MMU2 06* + +[filament:Prusament PETG MMU2 0.6 nozzle] +inherits = *PET MMU2 06* +filament_type = PETG [filament:Prusa PLA] inherits = *PLA* filament_cost = 25.4 filament_density = 1.24 -filament_notes = "List of materials tested with standard PLA print settings:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" +filament_notes = "List of materials tested with standard PLA print settings:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFiberlogy PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nAmazonBasics PLA" [filament:Prusament PLA] inherits = *PLA* @@ -1395,6 +1448,7 @@ inherits = *PLA MMU2* inherits = *FLEX* filament_cost = 82 filament_density = 1.22 +filament_max_volumetric_speed = 1.35 [filament:Taulman Bridge] inherits = *common* @@ -1409,7 +1463,7 @@ fan_below_layer_time = 20 filament_colour = #DEE0E6 filament_max_volumetric_speed = 10 filament_soluble = 0 -filament_type = PET +filament_type = NYLON first_layer_bed_temperature = 60 first_layer_temperature = 240 max_fan_speed = 5 @@ -1524,7 +1578,7 @@ fan_below_layer_time = 100 filament_colour = #DEE0E6 filament_max_volumetric_speed = 5 filament_notes = "List of materials tested with standard PLA print settings:\n\nEsun PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nEUMAKERS PLA" -filament_type = PLA +filament_type = PP first_layer_bed_temperature = 100 first_layer_temperature = 220 max_fan_speed = 100 @@ -1532,6 +1586,130 @@ min_fan_speed = 100 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 220 +## Filaments MMU1 + +[filament:ColorFabb HT MMU1] +inherits = *PETMMU1* +bed_temperature = 110 +bridge_fan_speed = 30 +cooling = 1 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 10 +filament_cost = 58.66 +filament_density = 1.18 +first_layer_bed_temperature = 105 +first_layer_temperature = 270 +max_fan_speed = 20 +min_fan_speed = 10 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" +temperature = 270 + +[filament:ColorFabb XT MMU1] +inherits = *PETMMU1* +filament_type = PET +filament_cost = 62.9 +filament_density = 1.27 +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +temperature = 270 + +[filament:ColorFabb XT-CF20 MMU1] +inherits = *PETMMU1* +compatible_printers_condition = nozzle_diameter[0]>0.35 and printer_model=="MK2SMM" +extrusion_multiplier = 1.2 +filament_cost = 80.65 +filament_density = 1.35 +filament_colour = #804040 +filament_max_volumetric_speed = 1 +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" +temperature = 260 + +[filament:ColorFabb nGen MMU1] +inherits = *PETMMU1* +filament_cost = 21.2 +filament_density = 1.2 +bridge_fan_speed = 40 +fan_always_on = 0 +fan_below_layer_time = 10 +filament_type = NGEN +first_layer_temperature = 240 +max_fan_speed = 35 +min_fan_speed = 20 + +[filament:E3D Edge MMU1] +inherits = *PETMMU1* +filament_cost = 56.9 +filament_density = 1.26 +filament_type = EDGE +filament_notes = "List of manufacturers tested with standard PET print settings:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladec PETG" + +[filament:Fillamentum CPE MMU1] +inherits = *PETMMU1* +filament_cost = 54.1 +filament_density = 1.25 +filament_type = CPE +first_layer_bed_temperature = 90 +first_layer_temperature = 275 +max_fan_speed = 50 +min_fan_speed = 50 +temperature = 275 + +[filament:Generic PET MMU1] +inherits = *PETMMU1* +filament_cost = 27.82 +filament_density = 1.27 +filament_notes = "List of manufacturers tested with standard PET print settings:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladec PETG" + +[filament:Prusa PET MMU1] +inherits = *PETMMU1* +filament_cost = 27.82 +filament_density = 1.27 +filament_notes = "List of manufacturers tested with standard PET print settings:\n\nE3D Edge\nPlasty Mladec PETG" + +[filament:Prusament PETG MMU1] +inherits = *PETMMU1* +first_layer_temperature = 240 +temperature = 250 +filament_cost = 24.99 +filament_density = 1.27 +filament_type = PETG + +[filament:Taulman T-Glase MMU1] +inherits = *PETMMU1* +filament_cost = 40 +filament_density = 1.27 +bridge_fan_speed = 40 +cooling = 0 +fan_always_on = 0 +first_layer_bed_temperature = 90 +first_layer_temperature = 240 +max_fan_speed = 5 +min_fan_speed = 0 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" + +[filament:SemiFlex or Flexfill 98A MMU1] +inherits = *FLEX* +filament_cost = 82 +filament_density = 1.22 +filament_max_volumetric_speed = 1.35 +filament_retract_length = nil +filament_retract_speed = nil +filament_retract_lift = nil +compatible_printers_condition = printer_model=="MK2SMM" + +[filament:Generic FLEX MMU1] +inherits = *FLEX* +filament_cost = 82 +filament_density = 1.22 +filament_max_volumetric_speed = 1.2 +filament_retract_length = 0 +filament_retract_speed = nil +filament_retract_lift = nil +compatible_printers_condition = printer_model=="MK2SMM" + [sla_print:*common*] compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_SL1.*/ layer_height = 0.05 @@ -1575,6 +1753,9 @@ layer_height = 0.05 [sla_print:0.1 Fast] inherits = *common* layer_height = 0.1 +support_head_front_diameter = 0.5 +support_head_penetration = 0.5 +support_pillar_diameter = 1.3 ########### Materials 0.025 @@ -1605,82 +1786,117 @@ initial_layer_height = 0.035 inherits = *common 0.05* compatible_prints_condition = layer_height == 0.1 exposure_time = 20 -initial_exposure_time = 90 +initial_exposure_time = 45 initial_layer_height = 0.1 ########### Materials 0.025 -[sla_material:Bluecast Phrozen Wax 0.025] +[sla_material:BlueCast Phrozen Wax 0.025] inherits = *common 0.025* -exposure_time = 8 +exposure_time = 15 +initial_exposure_time = 50 + +[sla_material:BlueCast EcoGray 0.025] +inherits = *common 0.025* +exposure_time = 6 +initial_exposure_time = 40 + +[sla_material:BlueCast Keramaster Dental 0.025] +inherits = *common 0.025* +exposure_time = 6 initial_exposure_time = 45 +[sla_material:BlueCast X10 0.025] +inherits = *common 0.025* +exposure_time = 4 +initial_exposure_time = 100 + [sla_material:Prusa Orange Tough 0.025] inherits = *common 0.025* exposure_time = 6 initial_exposure_time = 30 +[sla_material:Prusa Grey Tough 0.025] +inherits = *common 0.025* +exposure_time = 7 +initial_exposure_time = 30 + +[sla_material:Prusa Azure Blue Tough 0.025] +inherits = *common 0.025* +exposure_time = 7 +initial_exposure_time = 30 + +[sla_material:Prusa Maroon Tough 0.025] +inherits = *common 0.025* +exposure_time = 6 +initial_exposure_time = 30 + +[sla_material:Prusa Beige Tough 0.025] +inherits = *common 0.025* +exposure_time = 6 +initial_exposure_time = 30 + +[sla_material:Prusa Pink Tough 0.025] +inherits = *common 0.025* +exposure_time = 7 +initial_exposure_time = 30 + +[sla_material:Prusa White Tough 0.025] +inherits = *common 0.025* +exposure_time = 6.5 +initial_exposure_time = 30 + +[sla_material:Prusa Transparent Tough 0.025] +inherits = *common 0.025* +exposure_time = 6 +initial_exposure_time = 15 + +[sla_material:Prusa Green Casting 0.025] +inherits = *common 0.025* +exposure_time = 12 +initial_exposure_time = 35 + +## [sla_material:Prusa Transparent Green Tough 0.025] +## inherits = *common 0.025* +## exposure_time = 5 +## initial_exposure_time = 30 + ########### Materials 0.05 -[sla_material:3DM-HTR140 (high temperature) 0.05] -inherits = *common 0.05* -exposure_time = 12 -initial_exposure_time = 45 - -[sla_material:Bluecast Ecogray 0.05] -inherits = *common 0.05* -exposure_time = 8 -initial_exposure_time = 45 - -[sla_material:Bluecast Keramaster 0.05] -inherits = *common 0.05* -exposure_time = 8 -initial_exposure_time = 45 - -[sla_material:Bluecast Keramaster Dental 0.05] +[sla_material:BlueCast EcoGray 0.05] inherits = *common 0.05* exposure_time = 7 +initial_exposure_time = 35 + +[sla_material:BlueCast Keramaster 0.05] +inherits = *common 0.05* +exposure_time = 8 initial_exposure_time = 45 -[sla_material:Bluecast LCD-DLP Original 0.05] +[sla_material:BlueCast Keramaster Dental 0.05] +inherits = *common 0.05* +exposure_time = 7 +initial_exposure_time = 50 + +[sla_material:BlueCast LCD-DLP Original 0.05] inherits = *common 0.05* exposure_time = 10 initial_exposure_time = 60 -[sla_material:Bluecast Phrozen Wax 0.05] +[sla_material:BlueCast Phrozen Wax 0.05] inherits = *common 0.05* -exposure_time = 10 -initial_exposure_time = 55 +exposure_time = 16 +initial_exposure_time = 50 -[sla_material:Bluecast S+ 0.05] +[sla_material:BlueCast S+ 0.05] inherits = *common 0.05* exposure_time = 9 initial_exposure_time = 45 -[sla_material:Bluecast X2 0.05] -inherits = *common 0.05* -exposure_time = 10 -initial_exposure_time = 60 - -[sla_material:Prusa Skin Tough 0.05] +[sla_material:BlueCast X10 0.05] inherits = *common 0.05* exposure_time = 6 -initial_exposure_time = 30 - -[sla_material:Prusa Orange Tough 0.05] -inherits = *common 0.05* -exposure_time = 7.5 -initial_exposure_time = 30 - -[sla_material:Prusa Grey Tough 0.05] -inherits = *common 0.05* -exposure_time = 8.5 -initial_exposure_time = 30 - -[sla_material:Prusa Black Tough 0.05] -inherits = *common 0.05* -exposure_time = 6 -initial_exposure_time = 30 +initial_exposure_time = 100 [sla_material:Monocure 3D Black Rapid Resin 0.05] inherits = *common 0.05* @@ -1697,20 +1913,30 @@ inherits = *common 0.05* exposure_time = 8 initial_exposure_time = 40 -[sla_material:Monocure 3D Gray Rapid Resin 0.05] +[sla_material:Monocure 3D Grey Rapid Resin 0.05] inherits = *common 0.05* -exposure_time = 7 -initial_exposure_time = 40 +exposure_time = 10 +initial_exposure_time = 30 [sla_material:Monocure 3D White Rapid Resin 0.05] inherits = *common 0.05* exposure_time = 7 initial_exposure_time = 40 +[sla_material:3DM-HTR140 (high temperature) 0.05] +inherits = *common 0.05* +exposure_time = 12 +initial_exposure_time = 45 + [sla_material:3DM-ABS 0.05] inherits = *common 0.05* -exposure_time = 9 -initial_exposure_time = 35 +exposure_time = 13 +initial_exposure_time = 25 + +[sla_material:3DM-BLACK 0.05] +inherits = *common 0.05* +exposure_time = 20 +initial_exposure_time = 40 [sla_material:3DM-DENT 0.05] inherits = *common 0.05* @@ -1737,7 +1963,39 @@ inherits = *common 0.05* exposure_time = 9 initial_exposure_time = 40 -## [sla_material:Prusa Skin Super Low Odor 0.05] +[sla_material:Harz Labs Model Resin Cherry 0.05] +inherits = *common 0.05* +exposure_time = 8 +initial_exposure_time = 45 + +[sla_material:Photocentric Hard Grey 0.05] +inherits = *common 0.05* +exposure_time = 15 +initial_exposure_time = 30 + +## Prusa + +[sla_material:Prusa Beige Tough 0.05] +inherits = *common 0.05* +exposure_time = 7 +initial_exposure_time = 30 + +[sla_material:Prusa Orange Tough 0.05] +inherits = *common 0.05* +exposure_time = 7.5 +initial_exposure_time = 30 + +[sla_material:Prusa Grey Tough 0.05] +inherits = *common 0.05* +exposure_time = 8.5 +initial_exposure_time = 30 + +[sla_material:Prusa Black Tough 0.05] +inherits = *common 0.05* +exposure_time = 6 +initial_exposure_time = 30 + +## [sla_material:Prusa Beige Super Low Odor 0.05] ## inherits = *common 0.05* ## exposure_time = 7.5 ## initial_exposure_time = 30 @@ -1752,11 +2010,6 @@ initial_exposure_time = 40 ## exposure_time = 6.5 ## initial_exposure_time = 30 -[sla_material:Harz Labs Model Resin Cherry 0.05] -inherits = *common 0.05* -exposure_time = 8 -initial_exposure_time = 45 - ## [sla_material:Prusa Black High Tenacity 0.05] ## inherits = *common 0.05* ## exposure_time = 7 @@ -1765,7 +2018,7 @@ initial_exposure_time = 45 [sla_material:Prusa Green Casting 0.05] inherits = *common 0.05* exposure_time = 13 -initial_exposure_time = 30 +initial_exposure_time = 40 ## [sla_material:Prusa Yellow Solid 0.05] ## inherits = *common 0.05* @@ -1774,10 +2027,10 @@ initial_exposure_time = 30 [sla_material:Prusa White Tough 0.05] inherits = *common 0.05* -exposure_time = 7 +exposure_time = 7.5 initial_exposure_time = 30 -## [sla_material:Prusa Green Transparent 0.05] +## [sla_material:Prusa Transparent Green Tough 0.05] ## inherits = *common 0.05* ## exposure_time = 6 ## initial_exposure_time = 30 @@ -1789,12 +2042,12 @@ initial_exposure_time = 30 [sla_material:Prusa Maroon Tough 0.05] inherits = *common 0.05* -exposure_time = 9 +exposure_time = 7.5 initial_exposure_time = 30 [sla_material:Prusa Pink Tough 0.05] inherits = *common 0.05* -exposure_time = 7 +exposure_time = 8 initial_exposure_time = 30 [sla_material:Prusa Azure Blue Tough 0.05] @@ -1802,6 +2055,11 @@ inherits = *common 0.05* exposure_time = 8 initial_exposure_time = 30 +[sla_material:Prusa Transparent Tough 0.05] +inherits = *common 0.05* +exposure_time = 7 +initial_exposure_time = 15 + ## [sla_material:Prusa Yellow Flexible 0.05] ## inherits = *common 0.05* ## exposure_time = 9 @@ -1809,8 +2067,8 @@ initial_exposure_time = 30 ## [sla_material:Prusa Clear Flexible 0.05] ## inherits = *common 0.05* -## exposure_time = 9 -## initial_exposure_time = 30 +## exposure_time = 5 +## initial_exposure_time = 15 ## [sla_material:Prusa White Flexible 0.05] ## inherits = *common 0.05* @@ -1843,9 +2101,49 @@ initial_exposure_time = 30 [sla_material:Prusa Orange Tough 0.1] inherits = *common 0.1* -exposure_time = 10 +exposure_time = 13 +initial_exposure_time = 45 + +[sla_material:Prusa Beige Tough 0.1] +inherits = *common 0.1* +exposure_time = 13 +initial_exposure_time = 45 + +[sla_material:Prusa Pink Tough 0.1] +inherits = *common 0.1* +exposure_time = 13 +initial_exposure_time = 45 + +[sla_material:Prusa Azure Blue Tough 0.1] +inherits = *common 0.1* +exposure_time = 13 +initial_exposure_time = 45 + +[sla_material:Prusa Maroon Tough 0.1] +inherits = *common 0.1* +exposure_time = 13 +initial_exposure_time = 45 + +[sla_material:Prusa White Tough 0.1] +inherits = *common 0.1* +exposure_time = 13 +initial_exposure_time = 45 + +[sla_material:Prusa Black Tough 0.1] +inherits = *common 0.1* +exposure_time = 13 +initial_exposure_time = 55 + +[sla_material:Prusa Transparent Tough 0.1] +inherits = *common 0.1* +exposure_time = 8 initial_exposure_time = 30 +[sla_material:Prusa Green Casting 0.1] +inherits = *common 0.1* +exposure_time = 15 +initial_exposure_time = 50 + [printer:*common*] printer_technology = FFF bed_shape = 0x0,250x0,250x210,0x210 @@ -1897,7 +2195,7 @@ retract_speed = 35 serial_port = serial_speed = 250000 single_extruder_multi_material = 0 -start_gcode = M115 U3.1.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.2.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 toolchange_gcode = use_firmware_retraction = 0 use_relative_e_distances = 1 @@ -1934,7 +2232,7 @@ printer_model = MK2SMM inherits = *multimaterial* end_gcode = G1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\nM107 ; turn off fan\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors\n\n printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN -start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\n; Start G-Code sequence START\nT?\nM104 S[first_layer_temperature]\nM140 S[first_layer_bed_temperature]\nM109 S[first_layer_temperature]\nM190 S[first_layer_bed_temperature]\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100\nM92 E140\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG92 E0.0 +start_gcode = M115 U3.2.3 ; tell printer latest fw version\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\n; Start G-Code sequence START\nT?\nM104 S[first_layer_temperature]\nM140 S[first_layer_bed_temperature]\nM109 S[first_layer_temperature]\nM190 S[first_layer_bed_temperature]\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100\nM92 E140\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG92 E0.0 default_print_profile = 0.15mm OPTIMAL [printer:*mm-multi*] @@ -1944,7 +2242,7 @@ end_gcode = {if not has_wipe_tower}\n; Pull the filament into the cooling tubes. extruder_colour = #FFAA55;#E37BA0;#4ECDD3;#FB7259 nozzle_diameter = 0.4,0.4,0.4,0.4 printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN -start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\n; Start G-Code sequence START\nT[initial_tool]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100 ; set max feedrate\nM92 E140 ; E-steps per filament milimeter\n{if not has_single_extruder_multi_material_priming}\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\n{endif}\nG92 E0.0 +start_gcode = M115 U3.2.3 ; tell printer latest fw version\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\n; Start G-Code sequence START\nT[initial_tool]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100 ; set max feedrate\nM92 E140 ; E-steps per filament milimeter\n{if not has_single_extruder_multi_material_priming}\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\n{endif}\nG92 E0.0 default_print_profile = 0.15mm OPTIMAL # XXXXXXXXXXXXXXXXX @@ -2012,19 +2310,19 @@ min_layer_height = 0.1 inherits = Original Prusa i3 MK2S printer_model = MK2.5 remaining_times = 1 -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 [printer:Original Prusa i3 MK2.5 0.25 nozzle] inherits = Original Prusa i3 MK2S 0.25 nozzle printer_model = MK2.5 remaining_times = 1 -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 [printer:Original Prusa i3 MK2.5 0.6 nozzle] inherits = Original Prusa i3 MK2S 0.6 nozzle printer_model = MK2.5 remaining_times = 1 -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 [printer:Original Prusa i3 MK2.5 MMU2 Single] inherits = Original Prusa i3 MK2.5; *mm2* @@ -2053,7 +2351,7 @@ machine_min_travel_rate = 0 default_print_profile = 0.15mm OPTIMAL MK2.5 default_filament_profile = Prusament PLA printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2.5\n -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\n; select extruder\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; load to nozzle\nTc\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\n; select extruder\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; load to nozzle\nTc\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200 F3000 ; home X axis\nM84 ; disable motors [printer:Original Prusa i3 MK2.5 MMU2 Single 0.6 nozzle] @@ -2095,23 +2393,23 @@ single_extruder_multi_material = 1 # to be defined explicitely. nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#DB5182;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\nG92 E0.0\n +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\nG92 E0.0\n end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200 F3000 ; home X axis\nM84 ; disable motors\n [printer:Original Prusa i3 MK2.5S] inherits = Original Prusa i3 MK2.5 printer_model = MK2.5S -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 [printer:Original Prusa i3 MK2.5S 0.25 nozzle] inherits = Original Prusa i3 MK2.5 0.25 nozzle printer_model = MK2.5S -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 [printer:Original Prusa i3 MK2.5S 0.6 nozzle] inherits = Original Prusa i3 MK2.5 0.6 nozzle printer_model = MK2.5S -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 [printer:Original Prusa i3 MK2.5S MMU2S Single] inherits = Original Prusa i3 MK2.5; *mm2s* @@ -2140,7 +2438,7 @@ machine_min_travel_rate = 0 default_print_profile = 0.15mm OPTIMAL MK2.5 default_filament_profile = Prusament PLA printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2.5\n -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200 F3000 ; home X axis\nM84 ; disable motors [printer:Original Prusa i3 MK2.5S MMU2S Single 0.6 nozzle] @@ -2160,7 +2458,7 @@ min_layer_height = 0.05 nozzle_diameter = 0.25 printer_variant = 0.25 default_print_profile = 0.10mm DETAIL 0.25 nozzle -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F1400.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F1400.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n [printer:Original Prusa i3 MK2.5S MMU2S] inherits = Original Prusa i3 MK2.5; *mm2s* @@ -2193,9 +2491,24 @@ single_extruder_multi_material = 1 # to be defined explicitely. nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#DB5182;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E29.0 F1073.0\nG1 X5.0 E29.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\nG92 E0.0\n +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E29.0 F1073.0\nG1 X5.0 E29.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\nG92 E0.0\n end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200 F3000 ; home X axis\nM84 ; disable motors\n +[printer:Original Prusa i3 MK2.5S MMU2S 0.6 nozzle] +inherits = Original Prusa i3 MK2.5S MMU2S +nozzle_diameter = 0.6,0.6,0.6,0.6,0.6 +max_layer_height = 0.40 +min_layer_height = 0.15 +printer_variant = 0.6 +default_print_profile = 0.20mm NORMAL 0.6 nozzle + +[printer:Original Prusa i3 MK2.5 MMU2 0.6 nozzle] +inherits = Original Prusa i3 MK2.5 MMU2 +nozzle_diameter = 0.6,0.6,0.6,0.6,0.6 +max_layer_height = 0.40 +min_layer_height = 0.15 +printer_variant = 0.6 +default_print_profile = 0.20mm NORMAL 0.6 nozzle # XXXXXXXXXXXXXXXXX # XXX--- MK3 ---XXX @@ -2225,7 +2538,7 @@ remaining_times = 1 printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 max_print_height = 210 -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif} +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif} printer_model = MK3 default_print_profile = 0.15mm QUALITY MK3 @@ -2235,7 +2548,7 @@ nozzle_diameter = 0.25 max_layer_height = 0.15 min_layer_height = 0.05 printer_variant = 0.25 -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E8.0 F700.0 ; intro line\nG1 X100.0 E12.5 F700.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif} +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E8.0 F700.0 ; intro line\nG1 X100.0 E12.5 F700.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif} default_print_profile = 0.10mm DETAIL 0.25 nozzle MK3 [printer:Original Prusa i3 MK3 0.6 nozzle] @@ -2249,17 +2562,17 @@ default_print_profile = 0.30mm QUALITY 0.6 nozzle MK3 [printer:Original Prusa i3 MK3S] inherits = Original Prusa i3 MK3 printer_model = MK3S -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif} +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif} [printer:Original Prusa i3 MK3S 0.25 nozzle] inherits = Original Prusa i3 MK3 0.25 nozzle printer_model = MK3S -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E8.0 F700.0 ; intro line\nG1 X100.0 E12.5 F700.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif} +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E8.0 F700.0 ; intro line\nG1 X100.0 E12.5 F700.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif} [printer:Original Prusa i3 MK3S 0.6 nozzle] inherits = Original Prusa i3 MK3 0.6 nozzle printer_model = MK3S -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif} +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif} [printer:*mm2*] inherits = Original Prusa i3 MK3 @@ -2289,7 +2602,7 @@ default_filament_profile = Prusament PLA MMU2 inherits = *mm2* single_extruder_multi_material = 0 default_filament_profile = Prusament PLA -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200 F3000 ; home X axis\nM84 ; disable motors [printer:Original Prusa i3 MK3 MMU2 Single 0.6 nozzle] @@ -2308,7 +2621,7 @@ nozzle_diameter = 0.25 max_layer_height = 0.15 min_layer_height = 0.05 printer_variant = 0.25 -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 E8.0 F1000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F1400.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 E8.0 F1000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F1400.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n default_print_profile = 0.10mm DETAIL 0.25 nozzle MK3 [printer:Original Prusa i3 MK3 MMU2] @@ -2319,14 +2632,14 @@ inherits = *mm2* machine_max_acceleration_e = 8000,8000 nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#DB5182;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200 F3000 ; home X axis\nM84 ; disable motors\n [printer:Original Prusa i3 MK3S MMU2S Single] inherits = *mm2s* single_extruder_multi_material = 0 default_filament_profile = Prusament PLA -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200 F3000 ; home X axis\nM84 ; disable motors [printer:Original Prusa i3 MK3S MMU2S Single 0.6 nozzle] @@ -2345,7 +2658,7 @@ nozzle_diameter = 0.25 max_layer_height = 0.15 min_layer_height = 0.05 printer_variant = 0.25 -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F1400.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F1400.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n default_print_profile = 0.10mm DETAIL 0.25 nozzle MK3 [printer:Original Prusa i3 MK3S MMU2S] @@ -2353,18 +2666,26 @@ inherits = *mm2s* machine_max_acceleration_e = 8000,8000 nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#DB5182;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E29.0 F1073.0\nG1 X5.0 E29.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E29.0 F1073.0\nG1 X5.0 E29.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200 F3000 ; home X axis\nM84 ; disable motors\n -# 0.6 nozzle MMU printer profile - only for single mode for now +## 0.6mm nozzle MMU2/S printer profiles -# [printer:Original Prusa i3 MK3S MMU2S 0.6 nozzle] -# inherits = Original Prusa i3 MK3S MMU2S -# nozzle_diameter = 0.6,0.6,0.6,0.6,0.6 -# max_layer_height = 0.40 -# min_layer_height = 0.15 -# printer_variant = 0.6 -# default_print_profile = 0.30mm QUALITY 0.6 nozzle MK3 +[printer:Original Prusa i3 MK3S MMU2S 0.6 nozzle] +inherits = Original Prusa i3 MK3S MMU2S +nozzle_diameter = 0.6,0.6,0.6,0.6,0.6 +max_layer_height = 0.40 +min_layer_height = 0.15 +printer_variant = 0.6 +default_print_profile = 0.30mm QUALITY 0.6 nozzle MK3 + +[printer:Original Prusa i3 MK3 MMU2 0.6 nozzle] +inherits = Original Prusa i3 MK3 MMU2 +nozzle_diameter = 0.6,0.6,0.6,0.6,0.6 +max_layer_height = 0.40 +min_layer_height = 0.15 +printer_variant = 0.6 +default_print_profile = 0.30mm QUALITY 0.6 nozzle MK3 [printer:Original Prusa SL1] printer_technology = SLA diff --git a/resources/shaders/printbed.fs b/resources/shaders/printbed.fs index be14347c21..d1316ca2fe 100644 --- a/resources/shaders/printbed.fs +++ b/resources/shaders/printbed.fs @@ -5,16 +5,30 @@ const vec3 back_color_light = vec3(0.365, 0.365, 0.365); uniform sampler2D texture; uniform bool transparent_background; +uniform bool svg_source; varying vec2 tex_coords; -void main() +vec4 svg_color() { + // takes foreground from texture + vec4 fore_color = texture2D(texture, tex_coords); + // calculates radial gradient vec3 back_color = vec3(mix(back_color_light, back_color_dark, smoothstep(0.0, 0.5, length(abs(tex_coords.xy) - vec2(0.5))))); - vec4 fore_color = texture2D(texture, tex_coords); - // blends foreground with background - gl_FragColor = vec4(mix(back_color, fore_color.rgb, fore_color.a), transparent_background ? fore_color.a : 1.0); + return vec4(mix(back_color, fore_color.rgb, fore_color.a), transparent_background ? fore_color.a : 1.0); +} + +vec4 non_svg_color() +{ + // takes foreground from texture + vec4 color = texture2D(texture, tex_coords); + return vec4(color.rgb, transparent_background ? color.a * 0.25 : color.a); +} + +void main() +{ + gl_FragColor = svg_source ? svg_color() : non_svg_color(); } \ No newline at end of file diff --git a/resources/shaders/variable_layer_height.vs b/resources/shaders/variable_layer_height.vs index 9763859d04..4f98dfa56e 100644 --- a/resources/shaders/variable_layer_height.vs +++ b/resources/shaders/variable_layer_height.vs @@ -15,6 +15,7 @@ const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); #define INTENSITY_AMBIENT 0.3 uniform mat4 volume_world_matrix; +uniform float object_max_z; // x = tainted, y = specular; varying vec2 intensity; @@ -42,6 +43,12 @@ void main() intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; // Scaled to widths of the Z texture. - object_z = (volume_world_matrix * gl_Vertex).z; + if (object_max_z > 0.0) + // when rendering the overlay + object_z = object_max_z * gl_MultiTexCoord0.y; + else + // when rendering the volumes + object_z = (volume_world_matrix * gl_Vertex).z; + gl_Position = ftransform(); } diff --git a/sandboxes/CMakeLists.txt b/sandboxes/CMakeLists.txt index 0e1f52d6cf..5905c438e9 100644 --- a/sandboxes/CMakeLists.txt +++ b/sandboxes/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(slabasebed) +add_subdirectory(slasupporttree) diff --git a/sandboxes/slabasebed/slabasebed.cpp b/sandboxes/slabasebed/slabasebed.cpp index 0c34eb9f52..b8b94d86f3 100644 --- a/sandboxes/slabasebed/slabasebed.cpp +++ b/sandboxes/slabasebed/slabasebed.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -15,7 +16,8 @@ const std::string USAGE_STR = { namespace Slic3r { namespace sla { -Contour3D create_base_pool(const ExPolygons &ground_layer, +Contour3D create_base_pool(const Polygons &ground_layer, + const ExPolygons &holes = {}, const PoolConfig& cfg = PoolConfig()); Contour3D walls(const Polygon& floor_plate, const Polygon& ceiling, @@ -43,36 +45,27 @@ int main(const int argc, const char *argv[]) { model.align_to_origin(); ExPolygons ground_slice; - sla::Contour3D mesh; -// TriangleMesh basepool; - sla::base_plate(model, ground_slice, 0.1f); - if(ground_slice.empty()) return EXIT_FAILURE; -// ExPolygon bottom_plate = ground_slice.front(); -// ExPolygon top_plate = bottom_plate; -// sla::offset(top_plate, coord_t(3.0/SCALING_FACTOR)); -// sla::offset(bottom_plate, coord_t(1.0/SCALING_FACTOR)); + ground_slice = offset_ex(ground_slice, 0.5); + ExPolygon gndfirst; gndfirst = ground_slice.front(); + sla::breakstick_holes(gndfirst, 0.5, 10, 0.3); + + sla::Contour3D mesh; bench.start(); -// TriangleMesh pool; sla::PoolConfig cfg; cfg.min_wall_height_mm = 0; - cfg.edge_radius_mm = 0.2; - mesh = sla::create_base_pool(ground_slice, cfg); - -// mesh.merge(triangulate_expolygon_3d(top_plate, 3.0, false)); -// mesh.merge(triangulate_expolygon_3d(bottom_plate, 0.0, true)); -// mesh = sla::walls(bottom_plate.contour, top_plate.contour, 0, 3, 2.0, [](){}); - + cfg.edge_radius_mm = 0; + mesh = sla::create_base_pool(to_polygons(ground_slice), {}, cfg); + bench.stop(); cout << "Base pool creation time: " << std::setprecision(10) << bench.getElapsedSec() << " seconds." << endl; - -// auto point = []() + for(auto& trind : mesh.indices) { Vec3d p0 = mesh.points[size_t(trind[0])]; Vec3d p1 = mesh.points[size_t(trind[1])]; @@ -83,7 +76,7 @@ int main(const int argc, const char *argv[]) { if(std::abs(a) < 1e-6) std::cout << "degenerate triangle" << std::endl; } -// basepool.write_ascii("out.stl"); + // basepool.write_ascii("out.stl"); std::fstream outstream("out.obj", std::fstream::out); mesh.to_obj(outstream); diff --git a/sandboxes/slasupporttree/CMakeLists.txt b/sandboxes/slasupporttree/CMakeLists.txt new file mode 100644 index 0000000000..79adb842b9 --- /dev/null +++ b/sandboxes/slasupporttree/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(slasupporttree slasupporttree.cpp) +target_link_libraries(slasupporttree libslic3r ${Boost_LIBRARIES} ${TBB_LIBRARIES} ${Boost_LIBRARIES} ${CMAKE_DL_LIBS}) diff --git a/sandboxes/slasupporttree/slasupporttree.cpp b/sandboxes/slasupporttree/slasupporttree.cpp new file mode 100644 index 0000000000..dcaddf6d3f --- /dev/null +++ b/sandboxes/slasupporttree/slasupporttree.cpp @@ -0,0 +1,42 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +const std::string USAGE_STR = { + "Usage: slasupporttree stlfilename.stl" +}; + +int main(const int argc, const char *argv[]) { + using namespace Slic3r; + using std::cout; using std::endl; + + if(argc < 2) { + cout << USAGE_STR << endl; + return EXIT_SUCCESS; + } + + DynamicPrintConfig config; + + Model model = Model::read_from_file(argv[1], &config); + + SLAPrint print; + + print.apply(model, config); + print.process(); + + + return EXIT_SUCCESS; +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4f6959623b..31cb24f24a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,5 +1,6 @@ project(PrusaSlicer-native) +add_subdirectory(build-utils) add_subdirectory(admesh) add_subdirectory(avrdude) # boost/nowide @@ -12,14 +13,12 @@ add_subdirectory(poly2tri) add_subdirectory(qhull) add_subdirectory(Shiny) add_subdirectory(semver) +add_subdirectory(libigl) # Adding libnest2d project for bin packing... set(LIBNEST2D_UNITTESTS ON CACHE BOOL "Force generating unittests for libnest2d") add_subdirectory(libnest2d) -include_directories(${LIBDIR}/qhull/src) -#message(STATUS ${LIBDIR}/qhull/src) - add_subdirectory(libslic3r) if (SLIC3R_GUI) @@ -49,7 +48,7 @@ if (SLIC3R_GUI) endif () endif () else () - find_package(wxWidgets 3.1 REQUIRED COMPONENTS base core adv html gl) + find_package(wxWidgets 3.1 REQUIRED COMPONENTS html adv gl core base) endif () if(UNIX) @@ -58,6 +57,9 @@ if (SLIC3R_GUI) include(${wxWidgets_USE_FILE}) +# list(REMOVE_ITEM wxWidgets_LIBRARIES oleacc) + message(STATUS "wx libs: ${wxWidgets_LIBRARIES}") + add_subdirectory(slic3r) endif() @@ -67,17 +69,23 @@ endif() configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/msw/PrusaSlicer.rc.in ${CMAKE_CURRENT_BINARY_DIR}/PrusaSlicer.rc @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/msw/PrusaSlicer.manifest.in ${CMAKE_CURRENT_BINARY_DIR}/PrusaSlicer.manifest @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/osx/Info.plist.in ${CMAKE_CURRENT_BINARY_DIR}/Info.plist @ONLY) -if (MSVC) +if (WIN32) add_library(PrusaSlicer SHARED PrusaSlicer.cpp PrusaSlicer.hpp) else () add_executable(PrusaSlicer PrusaSlicer.cpp PrusaSlicer.hpp) endif () -if (NOT MSVC) + +if (MINGW) + target_link_options(PrusaSlicer PUBLIC "-Wl,-allow-multiple-definition") + set_target_properties(PrusaSlicer PROPERTIES PREFIX "") +endif (MINGW) + +if (NOT WIN32) # Binary name on unix like systems (OSX, Linux) set_target_properties(PrusaSlicer PROPERTIES OUTPUT_NAME "prusa-slicer") endif () -target_link_libraries(PrusaSlicer libslic3r) +target_link_libraries(PrusaSlicer libslic3r cereal) if (APPLE) # add_compile_options(-stdlib=libc++) # add_definitions(-DBOOST_THREAD_DONT_USE_CHRONO -DBOOST_NO_CXX11_RVALUE_REFERENCES -DBOOST_THREAD_USES_MOVE) @@ -93,11 +101,12 @@ endif () # Add the Slic3r GUI library, libcurl, OpenGL and GLU libraries. if (SLIC3R_GUI) - target_link_libraries(PrusaSlicer libslic3r_gui ${wxWidgets_LIBRARIES}) +# target_link_libraries(PrusaSlicer ws2_32 uxtheme setupapi libslic3r_gui ${wxWidgets_LIBRARIES}) +target_link_libraries(PrusaSlicer libslic3r_gui ${wxWidgets_LIBRARIES}) # Configure libcurl and its dependencies OpenSSL & zlib find_package(CURL REQUIRED) - if (NOT MSVC) + if (NOT WIN32) # Required by libcurl find_package(ZLIB REQUIRED) endif() @@ -125,7 +134,7 @@ if (SLIC3R_GUI) target_link_options(PrusaSlicer PUBLIC "$<$:/DEBUG>") target_link_libraries(PrusaSlicer user32.lib Setupapi.lib OpenGL32.Lib GlU32.Lib) elseif (MINGW) - target_link_libraries(PrusaSlicer -lopengl32) + target_link_libraries(PrusaSlicer opengl32 ws2_32 uxtheme setupapi) elseif (APPLE) target_link_libraries(PrusaSlicer "-framework OpenGL") else () @@ -135,26 +144,34 @@ endif () # On Windows, a shim application is required to produce a console / non console version of the Slic3r application. # Also the shim may load the Mesa software OpenGL renderer if the default renderer does not support OpenGL 2.0 and higher. -if (MSVC) +if (WIN32) + if (MINGW) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -municode") + endif() + add_executable(PrusaSlicer_app_gui WIN32 PrusaSlicer_app_msvc.cpp ${CMAKE_CURRENT_BINARY_DIR}/PrusaSlicer.rc) # Generate debug symbols even in release mode. - target_link_options(PrusaSlicer_app_gui PUBLIC "$<$:/DEBUG>") + if(MSVC) + target_link_options(PrusaSlicer_app_gui PUBLIC "$<$:/DEBUG>") + endif() target_compile_definitions(PrusaSlicer_app_gui PRIVATE -DSLIC3R_WRAPPER_NOCONSOLE) add_dependencies(PrusaSlicer_app_gui PrusaSlicer) set_target_properties(PrusaSlicer_app_gui PROPERTIES OUTPUT_NAME "prusa-slicer") - target_include_directories(PrusaSlicer_app_gui SYSTEM PUBLIC ${Boost_INCLUDE_DIRS}) + target_link_libraries(PrusaSlicer_app_gui PRIVATE boost_headeronly) add_executable(PrusaSlicer_app_console PrusaSlicer_app_msvc.cpp ${CMAKE_CURRENT_BINARY_DIR}/PrusaSlicer.rc) # Generate debug symbols even in release mode. + if (MSVC) target_link_options(PrusaSlicer_app_console PUBLIC "$<$:/DEBUG>") + endif () target_compile_definitions(PrusaSlicer_app_console PRIVATE -DSLIC3R_WRAPPER_CONSOLE) add_dependencies(PrusaSlicer_app_console PrusaSlicer) set_target_properties(PrusaSlicer_app_console PROPERTIES OUTPUT_NAME "prusa-slicer-console") - target_include_directories(PrusaSlicer_app_console SYSTEM PUBLIC ${Boost_INCLUDE_DIRS}) + target_link_libraries(PrusaSlicer_app_console PRIVATE boost_headeronly) endif () # Link the resources dir to where Slic3r GUI expects it -if (MSVC) +if (WIN32) if (CMAKE_CONFIGURATION_TYPES) foreach (CONF ${CMAKE_CONFIGURATION_TYPES}) file(TO_NATIVE_PATH "${CMAKE_CURRENT_BINARY_DIR}/${CONF}" WIN_CONF_OUTPUT_DIR) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index fef1f6e7f0..6c6f9584f0 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -7,10 +7,13 @@ #include #include #ifdef SLIC3R_GUI + extern "C" + { // Let the NVIDIA and AMD know we want to use their graphics card // on a dual graphics card system. __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; + } #endif /* SLIC3R_GUI */ #endif /* WIN32 */ @@ -23,6 +26,7 @@ #include #include #include +#include #include "unix/fhs.hpp" // Generated by CMake from ../platform/unix/fhs.hpp.in @@ -44,18 +48,39 @@ #ifdef SLIC3R_GUI #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI_App.hpp" + #include "slic3r/GUI/3DScene.hpp" #endif /* SLIC3R_GUI */ using namespace Slic3r; PrinterTechnology get_printer_technology(const DynamicConfig &config) { - const ConfigOptionEnum *opt = config.option>("printer_technology"); + const ConfigOptionEnum *opt = config.option>("printer_technology"); return (opt == nullptr) ? ptUnknown : opt->value; } -int CLI::run(int argc, char **argv) +int CLI::run(int argc, char **argv) { + // Switch boost::filesystem to utf8. + try { + boost::nowide::nowide_filesystem(); + } catch (const std::runtime_error& ex) { + std::string caption = std::string(SLIC3R_APP_NAME) + " Error"; + std::string text = std::string("An error occured while setting up locale.\n") + ( +#if !defined(_WIN32) && !defined(__APPLE__) + // likely some linux system + "You may need to reconfigure the missing locales, likely by running the \"locale-gen\" and \"dpkg-reconfigure locales\" commands.\n" +#endif + SLIC3R_APP_NAME " will now terminate.\n\n") + ex.what(); + #if defined(_WIN32) && defined(SLIC3R_GUI) + if (m_actions.empty()) + // Empty actions means Slicer is executed in the GUI mode. Show a GUI message. + MessageBoxA(NULL, text.c_str(), caption.c_str(), MB_OK | MB_ICONERROR); + #endif + boost::nowide::cerr << text.c_str() << std::endl; + return 1; + } + if (! this->setup(argc, argv)) return 1; @@ -64,14 +89,14 @@ int CLI::run(int argc, char **argv) bool start_gui = m_actions.empty() && // cutting transformations are setting an "export" action. - std::find(m_transforms.begin(), m_transforms.end(), "cut") == m_transforms.end() && - std::find(m_transforms.begin(), m_transforms.end(), "cut_x") == m_transforms.end() && - std::find(m_transforms.begin(), m_transforms.end(), "cut_y") == m_transforms.end(); + std::find(m_transforms.begin(), m_transforms.end(), "cut") == m_transforms.end() && + std::find(m_transforms.begin(), m_transforms.end(), "cut_x") == m_transforms.end() && + std::find(m_transforms.begin(), m_transforms.end(), "cut_y") == m_transforms.end(); PrinterTechnology printer_technology = get_printer_technology(m_extra_config); - const std::vector &load_configs = m_config.option("load", true)->values; - + const std::vector &load_configs = m_config.option("load", true)->values; + // load config files supplied via --load - for (auto const &file : load_configs) { + for (auto const &file : load_configs) { if (! boost::filesystem::exists(file)) { if (m_config.opt_bool("ignore_nonexistent_config")) { continue; @@ -91,13 +116,13 @@ int CLI::run(int argc, char **argv) PrinterTechnology other_printer_technology = get_printer_technology(config); if (printer_technology == ptUnknown) { printer_technology = other_printer_technology; - } else if (printer_technology != other_printer_technology) { + } else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) { boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl; return 1; } m_print_config.apply(config); } - + // Read input file(s) if any. for (const std::string &file : m_input_files) { if (! boost::filesystem::exists(file)) { @@ -111,7 +136,7 @@ int CLI::run(int argc, char **argv) PrinterTechnology other_printer_technology = get_printer_technology(m_print_config); if (printer_technology == ptUnknown) { printer_technology = other_printer_technology; - } else if (printer_technology != other_printer_technology) { + } else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) { boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl; return 1; } @@ -133,21 +158,21 @@ int CLI::run(int argc, char **argv) m_print_config.normalize(); if (printer_technology == ptUnknown) - printer_technology = std::find(m_actions.begin(), m_actions.end(), "export_sla") == m_actions.end() ? ptFFF : ptSLA; + printer_technology = std::find(m_actions.begin(), m_actions.end(), "export_sla") == m_actions.end() ? ptFFF : ptSLA; // Initialize full print configs for both the FFF and SLA technologies. FullPrintConfig fff_print_config; // SLAFullPrintConfig sla_print_config; fff_print_config.apply(m_print_config, true); // sla_print_config.apply(m_print_config, true); - + // Loop through transform options. for (auto const &opt_key : m_transforms) { if (opt_key == "merge") { Model m; for (auto &model : m_models) - for (ModelObject *o : model.objects) - m.add_object(*o); + for (ModelObject *o : model.objects) + m.add_object(*o); // Rearrange instances unless --dont-arrange is supplied if (! m_config.opt_bool("dont_arrange")) { m.add_default_instances(); @@ -159,8 +184,8 @@ int CLI::run(int argc, char **argv) this->has_print_action() ? &bb : nullptr ); } - m_models.clear(); - m_models.emplace_back(std::move(m)); + m_models.clear(); + m_models.emplace_back(std::move(m)); } else if (opt_key == "duplicate") { const BoundingBoxf &bb = fff_print_config.bed_shape.values; for (auto &model : m_models) { @@ -189,11 +214,11 @@ int CLI::run(int argc, char **argv) // this affects instances: model.center_instances_around_point(m_config.option("center")->value); // this affects volumes: - //FIXME Vojtech: Who knows why the complete model should be aligned with Z as a single rigid body? + //FIXME Vojtech: Who knows why the complete model should be aligned with Z as a single rigid body? //model.align_to_ground(); BoundingBoxf3 bbox; for (ModelObject *model_object : model.objects) - // We are interested into the Z span only, therefore it is sufficient to measure the bounding box of the 1st instance only. + // We are interested into the Z span only, therefore it is sufficient to measure the bounding box of the 1st instance only. bbox.merge(model_object->instance_bounding_box(0, false)); for (ModelObject *model_object : model.objects) for (ModelInstance *model_instance : model_object->instances) @@ -204,7 +229,7 @@ int CLI::run(int argc, char **argv) for (auto &model : m_models) { BoundingBoxf3 bb = model.bounding_box(); // this affects volumes: - model.translate(-(bb.min.x() - p.x()), -(bb.min.y() - p.y()), -bb.min.z()); + model.translate(-(bb.min.x() - p.x()), -(bb.min.y() - p.y()), -bb.min.z()); } } else if (opt_key == "dont_arrange") { // do nothing - this option alters other transform options @@ -241,10 +266,9 @@ int CLI::run(int argc, char **argv) } else if (opt_key == "cut" || opt_key == "cut_x" || opt_key == "cut_y") { std::vector new_models; for (auto &model : m_models) { - model.repair(); - model.translate(0, 0, -model.bounding_box().min.z()); // align to z = 0 - size_t num_objects = model.objects.size(); - for (size_t i = 0; i < num_objects; ++ i) { + model.translate(0, 0, -model.bounding_box().min.z()); // align to z = 0 + size_t num_objects = model.objects.size(); + for (size_t i = 0; i < num_objects; ++ i) { #if 0 if (opt_key == "cut_x") { @@ -255,15 +279,15 @@ int CLI::run(int argc, char **argv) o->cut(Z, m_config.opt_float("cut"), &out); } #else - model.objects.front()->cut(0, m_config.opt_float("cut"), true, true, true); + model.objects.front()->cut(0, m_config.opt_float("cut"), true, true, true); #endif - model.delete_object(size_t(0)); + model.delete_object(size_t(0)); } } - + // TODO: copy less stuff around using pointers m_models = new_models; - + if (m_actions.empty()) m_actions.push_back("export_stl"); } @@ -273,7 +297,7 @@ int CLI::run(int argc, char **argv) for (auto &model : m_models) { TriangleMesh mesh = model.mesh(); mesh.repair(); - + TriangleMeshPtrs meshes = mesh.cut_by_grid(m_config.option("cut_grid")->value); size_t i = 0; for (TriangleMesh* m : meshes) { @@ -284,10 +308,10 @@ int CLI::run(int argc, char **argv) delete m; } } - + // TODO: copy less stuff around using pointers m_models = new_models; - + if (m_actions.empty()) m_actions.push_back("export_stl"); } @@ -301,14 +325,15 @@ int CLI::run(int argc, char **argv) } } } else if (opt_key == "repair") { - for (auto &model : m_models) - model.repair(); + // Models are repaired by default. + //for (auto &model : m_models) + // model.repair(); } else { boost::nowide::cerr << "error: option not implemented yet: " << opt_key << std::endl; return 1; } } - + // loop through action options for (auto const &opt_key : m_actions) { if (opt_key == "help") { @@ -351,14 +376,14 @@ int CLI::run(int argc, char **argv) boost::nowide::cerr << "error: cannot export SLA slices for a SLA configuration" << std::endl; return 1; } - // Make a copy of the model if the current action is not the last action, as the model may be - // modified by the centering and such. - Model model_copy; - bool make_copy = &opt_key != &m_actions.back(); + // Make a copy of the model if the current action is not the last action, as the model may be + // modified by the centering and such. + Model model_copy; + bool make_copy = &opt_key != &m_actions.back(); for (Model &model_in : m_models) { - if (make_copy) - model_copy = model_in; - Model &model = make_copy ? model_copy : model_in; + if (make_copy) + model_copy = model_in; + Model &model = make_copy ? model_copy : model_in; // If all objects have defined instances, their relative positions will be // honored when printing (they will be only centered, unless --dont-arrange // is supplied); if any object has no instances, it will get a default one @@ -378,7 +403,7 @@ int CLI::run(int argc, char **argv) if (! m_config.opt_bool("dont_arrange")) { //FIXME make the min_object_distance configurable. model.arrange_objects(fff_print.config().min_object_distance()); - model.center_instances_around_point(m_config.option("center")->value); + model.center_instances_around_point(m_config.option("center")->value); } if (printer_technology == ptFFF) { for (auto* mo : model.objects) @@ -392,40 +417,40 @@ int CLI::run(int argc, char **argv) } if (print->empty()) boost::nowide::cout << "Nothing to print for " << outfile << " . Either the print is empty or no object is fully inside the print volume." << std::endl; - else + else try { std::string outfile_final; - print->process(); + print->process(); if (printer_technology == ptFFF) { // The outfile is processed by a PlaceholderParser. outfile = fff_print.export_gcode(outfile, nullptr); outfile_final = fff_print.print_statistics().finalize_output_path(outfile); } else { - outfile = sla_print.output_filepath(outfile); + outfile = sla_print.output_filepath(outfile); // We need to finalize the filename beforehand because the export function sets the filename inside the zip metadata outfile_final = sla_print.print_statistics().finalize_output_path(outfile); - sla_print.export_raster(outfile_final); + sla_print.export_raster(outfile_final); } - if (outfile != outfile_final && Slic3r::rename_file(outfile, outfile_final) != 0) { - boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl; + if (outfile != outfile_final && Slic3r::rename_file(outfile, outfile_final)) { + boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl; return 1; } boost::nowide::cout << "Slicing result exported to " << outfile << std::endl; } catch (const std::exception &ex) { - boost::nowide::cerr << ex.what() << std::endl; - return 1; + boost::nowide::cerr << ex.what() << std::endl; + return 1; } /* print.center = ! m_config.has("center") && ! m_config.has("align_xy") && ! m_config.opt_bool("dont_arrange"); print.set_model(model); - + // start chronometer typedef std::chrono::high_resolution_clock clock_; typedef std::chrono::duration > second_; std::chrono::time_point t0{ clock_::now() }; - + const std::string outfile = this->output_filepath(model, IO::Gcode); try { print.export_gcode(outfile); @@ -434,7 +459,7 @@ int CLI::run(int argc, char **argv) return 1; } boost::nowide::cout << "G-code exported to " << outfile << std::endl; - + // output some statistics double duration { std::chrono::duration_cast(clock_::now() - t0).count() }; boost::nowide::cout << std::fixed << std::setprecision(0) @@ -451,51 +476,55 @@ int CLI::run(int argc, char **argv) return 1; } } - - if (start_gui) { + + if (start_gui) { #ifdef SLIC3R_GUI // #ifdef USE_WX - GUI::GUI_App *gui = new GUI::GUI_App(); + GUI::GUI_App *gui = new GUI::GUI_App(); // gui->autosave = m_config.opt_string("autosave"); - GUI::GUI_App::SetInstance(gui); - gui->CallAfter([gui, this, &load_configs] { - if (!gui->initialized()) { - return; - } + GUI::GUI_App::SetInstance(gui); + gui->CallAfter([gui, this, &load_configs] { + if (!gui->initialized()) { + return; + } #if 0 - // Load the cummulative config over the currently active profiles. - //FIXME if multiple configs are loaded, only the last one will have an effect. - // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc). - // As of now only the full configs are supported here. - if (!m_print_config.empty()) - gui->mainframe->load_config(m_print_config); + // Load the cummulative config over the currently active profiles. + //FIXME if multiple configs are loaded, only the last one will have an effect. + // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc). + // As of now only the full configs are supported here. + if (!m_print_config.empty()) + gui->mainframe->load_config(m_print_config); #endif - if (! load_configs.empty()) - // Load the last config to give it a name at the UI. The name of the preset may be later - // changed by loading an AMF or 3MF. - //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config. - gui->mainframe->load_config_file(load_configs.back()); - // If loading a 3MF file, the config is loaded from the last one. - if (! m_input_files.empty()) - gui->plater()->load_files(m_input_files, true, true); - if (! m_extra_config.empty()) - gui->mainframe->load_config(m_extra_config); - }); - return wxEntry(argc, argv); + if (! load_configs.empty()) + // Load the last config to give it a name at the UI. The name of the preset may be later + // changed by loading an AMF or 3MF. + //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config. + gui->mainframe->load_config_file(load_configs.back()); + // If loading a 3MF file, the config is loaded from the last one. + if (! m_input_files.empty()) + gui->plater()->load_files(m_input_files, true, true); + if (! m_extra_config.empty()) + gui->mainframe->load_config(m_extra_config); + }); + int result = wxEntry(argc, argv); + //FIXME this is a workaround for the PrusaSlicer 2.1 release. + _3DScene::destroy(); + return result; #else /* SLIC3R_GUI */ - // No GUI support. Just print out a help. - this->print_help(false); - // If started without a parameter, consider it to be OK, otherwise report an error code (no action etc). - return (argc == 0) ? 0 : 1; + // No GUI support. Just print out a help. + this->print_help(false); + // If started without a parameter, consider it to be OK, otherwise report an error code (no action etc). + return (argc == 0) ? 0 : 1; #endif /* SLIC3R_GUI */ } - + return 0; } bool CLI::setup(int argc, char **argv) { { + Slic3r::set_logging_level(1); const char *loglevel = boost::nowide::getenv("SLIC3R_LOGLEVEL"); if (loglevel != nullptr) { if (loglevel[0] >= '0' && loglevel[0] <= '9' && loglevel[1] == 0) @@ -536,18 +565,18 @@ bool CLI::setup(int argc, char **argv) // If any option is unsupported, print usage and abort immediately. t_config_option_keys opt_order; if (! m_config.read_cli(argc, argv, &m_input_files, &opt_order)) { - // Separate error message reported by the CLI parser from the help. - boost::nowide::cerr << std::endl; + // Separate error message reported by the CLI parser from the help. + boost::nowide::cerr << std::endl; this->print_help(); - return false; + return false; + } + // Parse actions and transform options. + for (auto const &opt_key : opt_order) { + if (cli_actions_config_def.has(opt_key)) + m_actions.emplace_back(opt_key); + else if (cli_transform_config_def.has(opt_key)) + m_transforms.emplace_back(opt_key); } - // Parse actions and transform options. - for (auto const &opt_key : opt_order) { - if (cli_actions_config_def.has(opt_key)) - m_actions.emplace_back(opt_key); - else if (cli_transform_config_def.has(opt_key)) - m_transforms.emplace_back(opt_key); - } { const ConfigOptionInt *opt_loglevel = m_config.opt("loglevel"); @@ -560,15 +589,15 @@ bool CLI::setup(int argc, char **argv) for (const std::pair &optdef : *options) m_config.optptr(optdef.first, true); - set_data_dir(m_config.opt_string("datadir")); + set_data_dir(m_config.opt_string("datadir")); - return true; + return true; } -void CLI::print_help(bool include_print_options, PrinterTechnology printer_technology) const +void CLI::print_help(bool include_print_options, PrinterTechnology printer_technology) const { boost::nowide::cout - << SLIC3R_BUILD_ID << " " << "based on Slic3r" + << SLIC3R_BUILD_ID << " " << "based on Slic3r" #ifdef SLIC3R_GUI << " (with GUI support)" #else /* SLIC3R_GUI */ @@ -576,24 +605,24 @@ void CLI::print_help(bool include_print_options, PrinterTechnology printer_techn #endif /* SLIC3R_GUI */ << std::endl << "https://github.com/prusa3d/PrusaSlicer" << std::endl << std::endl - << "Usage: slic3r [ ACTIONS ] [ TRANSFORM ] [ OPTIONS ] [ file.stl ... ]" << std::endl + << "Usage: prusa-slicer [ ACTIONS ] [ TRANSFORM ] [ OPTIONS ] [ file.stl ... ]" << std::endl << std::endl << "Actions:" << std::endl; cli_actions_config_def.print_cli_help(boost::nowide::cout, false); - + boost::nowide::cout << std::endl << "Transform options:" << std::endl; cli_transform_config_def.print_cli_help(boost::nowide::cout, false); - + boost::nowide::cout << std::endl << "Other options:" << std::endl; cli_misc_config_def.print_cli_help(boost::nowide::cout, false); - + if (include_print_options) { boost::nowide::cout << std::endl; - print_config_def.print_cli_help(boost::nowide::cout, true, [printer_technology](const ConfigOptionDef &def) + print_config_def.print_cli_help(boost::nowide::cout, true, [printer_technology](const ConfigOptionDef &def) { return printer_technology == ptAny || def.printer_technology == ptAny || printer_technology == def.printer_technology; }); } else { boost::nowide::cout @@ -610,14 +639,14 @@ bool CLI::export_models(IO::ExportFormat format) switch (format) { case IO::AMF: success = Slic3r::store_amf(path.c_str(), &model, nullptr); break; case IO::OBJ: success = Slic3r::store_obj(path.c_str(), &model); break; - case IO::STL: success = Slic3r::store_stl(path.c_str(), &model, true); break; - case IO::TMF: success = Slic3r::store_3mf(path.c_str(), &model, nullptr); break; + case IO::STL: success = Slic3r::store_stl(path.c_str(), &model, true); break; + case IO::TMF: success = Slic3r::store_3mf(path.c_str(), &model, nullptr); break; default: assert(false); break; } if (success) - std::cout << "File exported to " << path << std::endl; + std::cout << "File exported to " << path << std::endl; else { - std::cerr << "File export to " << path << " failed" << std::endl; + std::cerr << "File export to " << path << " failed" << std::endl; return false; } } @@ -631,12 +660,12 @@ std::string CLI::output_filepath(const Model &model, IO::ExportFormat format) co case IO::AMF: ext = ".zip.amf"; break; case IO::OBJ: ext = ".obj"; break; case IO::STL: ext = ".stl"; break; - case IO::TMF: ext = ".3mf"; break; + case IO::TMF: ext = ".3mf"; break; default: assert(false); break; }; auto proposed_path = boost::filesystem::path(model.propose_export_file_name_and_path(ext)); // use --output when available - std::string cmdline_param = m_config.opt_string("output"); + std::string cmdline_param = m_config.opt_string("output"); if (! cmdline_param.empty()) { // if we were supplied a directory, use it and append our automatically generated filename boost::filesystem::path cmdline_path(cmdline_param); @@ -648,20 +677,20 @@ std::string CLI::output_filepath(const Model &model, IO::ExportFormat format) co return proposed_path.string(); } -#ifdef _MSC_VER +#if defined(_MSC_VER) || defined(__MINGW32__) extern "C" { - __declspec(dllexport) int __stdcall slic3r_main(int argc, wchar_t **argv) - { - // Convert wchar_t arguments to UTF8. - std::vector argv_narrow; - std::vector argv_ptrs(argc + 1, nullptr); - for (size_t i = 0; i < argc; ++ i) - argv_narrow.emplace_back(boost::nowide::narrow(argv[i])); - for (size_t i = 0; i < argc; ++ i) - argv_ptrs[i] = const_cast(argv_narrow[i].data()); - // Call the UTF8 main. - return CLI().run(argc, argv_ptrs.data()); - } + __declspec(dllexport) int __stdcall slic3r_main(int argc, wchar_t **argv) + { + // Convert wchar_t arguments to UTF8. + std::vector argv_narrow; + std::vector argv_ptrs(argc + 1, nullptr); + for (size_t i = 0; i < argc; ++ i) + argv_narrow.emplace_back(boost::nowide::narrow(argv[i])); + for (size_t i = 0; i < argc; ++ i) + argv_ptrs[i] = const_cast(argv_narrow[i].data()); + // Call the UTF8 main. + return CLI().run(argc, argv_ptrs.data()); + } } #else /* _MSC_VER */ int main(int argc, char **argv) diff --git a/src/PrusaSlicer_app_msvc.cpp b/src/PrusaSlicer_app_msvc.cpp index 5b01751b9d..b3d1e8bb47 100644 --- a/src/PrusaSlicer_app_msvc.cpp +++ b/src/PrusaSlicer_app_msvc.cpp @@ -8,17 +8,20 @@ #include #ifdef SLIC3R_GUI - // Let the NVIDIA and AMD know we want to use their graphics card - // on a dual graphics card system. - __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; - __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; +extern "C" +{ + // Let the NVIDIA and AMD know we want to use their graphics card + // on a dual graphics card system. + __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; + __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; +} #endif /* SLIC3R_GUI */ #include #include #ifdef SLIC3R_GUI - #include + #include #endif /* SLIC3R_GUI */ #include @@ -33,97 +36,97 @@ class OpenGLVersionCheck { public: - std::string version; - std::string glsl_version; - std::string vendor; - std::string renderer; + std::string version; + std::string glsl_version; + std::string vendor; + std::string renderer; - HINSTANCE hOpenGL = nullptr; - bool success = false; + HINSTANCE hOpenGL = nullptr; + bool success = false; - bool load_opengl_dll() - { - MSG msg = {0}; - WNDCLASS wc = {0}; - wc.lpfnWndProc = OpenGLVersionCheck::supports_opengl2_wndproc; - wc.hInstance = (HINSTANCE)GetModuleHandle(nullptr); - wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND); - wc.lpszClassName = L"PrusaSlicer_opengl_version_check"; - wc.style = CS_OWNDC; - if (RegisterClass(&wc)) { - HWND hwnd = CreateWindowW(wc.lpszClassName, L"PrusaSlicer_opengl_version_check", WS_OVERLAPPEDWINDOW, 0, 0, 640, 480, 0, 0, wc.hInstance, (LPVOID)this); - if (hwnd) { - message_pump_exit = false; - while (GetMessage(&msg, NULL, 0, 0 ) > 0 && ! message_pump_exit) - DispatchMessage(&msg); - } - } - return this->success; - } + bool load_opengl_dll() + { + MSG msg = {0}; + WNDCLASS wc = {0}; + wc.lpfnWndProc = OpenGLVersionCheck::supports_opengl2_wndproc; + wc.hInstance = (HINSTANCE)GetModuleHandle(nullptr); + wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND); + wc.lpszClassName = L"PrusaSlicer_opengl_version_check"; + wc.style = CS_OWNDC; + if (RegisterClass(&wc)) { + HWND hwnd = CreateWindowW(wc.lpszClassName, L"PrusaSlicer_opengl_version_check", WS_OVERLAPPEDWINDOW, 0, 0, 640, 480, 0, 0, wc.hInstance, (LPVOID)this); + if (hwnd) { + message_pump_exit = false; + while (GetMessage(&msg, NULL, 0, 0 ) > 0 && ! message_pump_exit) + DispatchMessage(&msg); + } + } + return this->success; + } - void unload_opengl_dll() - { - if (this->hOpenGL) { - BOOL released = FreeLibrary(this->hOpenGL); - if (released) - printf("System OpenGL library released\n"); - else - printf("System OpenGL library NOT released\n"); - this->hOpenGL = nullptr; - } - } + void unload_opengl_dll() + { + if (this->hOpenGL) { + BOOL released = FreeLibrary(this->hOpenGL); + if (released) + printf("System OpenGL library released\n"); + else + printf("System OpenGL library NOT released\n"); + this->hOpenGL = nullptr; + } + } - bool is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const - { - // printf("is_version_greater_or_equal_to, version: %s\n", version.c_str()); - std::vector tokens; - boost::split(tokens, version, boost::is_any_of(" "), boost::token_compress_on); - if (tokens.empty()) - return false; + bool is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const + { + // printf("is_version_greater_or_equal_to, version: %s\n", version.c_str()); + std::vector tokens; + boost::split(tokens, version, boost::is_any_of(" "), boost::token_compress_on); + if (tokens.empty()) + return false; - std::vector numbers; - boost::split(numbers, tokens[0], boost::is_any_of("."), boost::token_compress_on); + std::vector numbers; + boost::split(numbers, tokens[0], boost::is_any_of("."), boost::token_compress_on); - unsigned int gl_major = 0; - unsigned int gl_minor = 0; - if (numbers.size() > 0) - gl_major = ::atoi(numbers[0].c_str()); - if (numbers.size() > 1) - gl_minor = ::atoi(numbers[1].c_str()); - // printf("Major: %d, minor: %d\n", gl_major, gl_minor); - if (gl_major < major) - return false; - else if (gl_major > major) - return true; - else - return gl_minor >= minor; - } + unsigned int gl_major = 0; + unsigned int gl_minor = 0; + if (numbers.size() > 0) + gl_major = ::atoi(numbers[0].c_str()); + if (numbers.size() > 1) + gl_minor = ::atoi(numbers[1].c_str()); + // printf("Major: %d, minor: %d\n", gl_major, gl_minor); + if (gl_major < major) + return false; + else if (gl_major > major) + return true; + else + return gl_minor >= minor; + } protected: - static bool message_pump_exit; + static bool message_pump_exit; - void check(HWND hWnd) - { - hOpenGL = LoadLibraryExW(L"opengl32.dll", nullptr, 0); - if (hOpenGL == nullptr) { - printf("Failed loading the system opengl32.dll\n"); - return; - } + void check(HWND hWnd) + { + hOpenGL = LoadLibraryExW(L"opengl32.dll", nullptr, 0); + if (hOpenGL == nullptr) { + printf("Failed loading the system opengl32.dll\n"); + return; + } - typedef HGLRC (WINAPI *Func_wglCreateContext)(HDC); - typedef BOOL (WINAPI *Func_wglMakeCurrent )(HDC, HGLRC); - typedef BOOL (WINAPI *Func_wglDeleteContext)(HGLRC); - typedef GLubyte* (WINAPI *Func_glGetString )(GLenum); + typedef HGLRC (WINAPI *Func_wglCreateContext)(HDC); + typedef BOOL (WINAPI *Func_wglMakeCurrent )(HDC, HGLRC); + typedef BOOL (WINAPI *Func_wglDeleteContext)(HGLRC); + typedef GLubyte* (WINAPI *Func_glGetString )(GLenum); - Func_wglCreateContext wglCreateContext = (Func_wglCreateContext)GetProcAddress(hOpenGL, "wglCreateContext"); - Func_wglMakeCurrent wglMakeCurrent = (Func_wglMakeCurrent) GetProcAddress(hOpenGL, "wglMakeCurrent"); - Func_wglDeleteContext wglDeleteContext = (Func_wglDeleteContext)GetProcAddress(hOpenGL, "wglDeleteContext"); - Func_glGetString glGetString = (Func_glGetString) GetProcAddress(hOpenGL, "glGetString"); + Func_wglCreateContext wglCreateContext = (Func_wglCreateContext)GetProcAddress(hOpenGL, "wglCreateContext"); + Func_wglMakeCurrent wglMakeCurrent = (Func_wglMakeCurrent) GetProcAddress(hOpenGL, "wglMakeCurrent"); + Func_wglDeleteContext wglDeleteContext = (Func_wglDeleteContext)GetProcAddress(hOpenGL, "wglDeleteContext"); + Func_glGetString glGetString = (Func_glGetString) GetProcAddress(hOpenGL, "glGetString"); - if (wglCreateContext == nullptr || wglMakeCurrent == nullptr || wglDeleteContext == nullptr || glGetString == nullptr) { - printf("Failed loading the system opengl32.dll: The library is invalid.\n"); - return; - } + if (wglCreateContext == nullptr || wglMakeCurrent == nullptr || wglDeleteContext == nullptr || glGetString == nullptr) { + printf("Failed loading the system opengl32.dll: The library is invalid.\n"); + return; + } PIXELFORMATDESCRIPTOR pfd = { @@ -146,152 +149,155 @@ protected: }; HDC ourWindowHandleToDeviceContext = ::GetDC(hWnd); - // Gdi32.dll - int letWindowsChooseThisPixelFormat = ::ChoosePixelFormat(ourWindowHandleToDeviceContext, &pfd); - // Gdi32.dll + // Gdi32.dll + int letWindowsChooseThisPixelFormat = ::ChoosePixelFormat(ourWindowHandleToDeviceContext, &pfd); + // Gdi32.dll SetPixelFormat(ourWindowHandleToDeviceContext, letWindowsChooseThisPixelFormat, &pfd); - // Opengl32.dll + // Opengl32.dll HGLRC glcontext = wglCreateContext(ourWindowHandleToDeviceContext); wglMakeCurrent(ourWindowHandleToDeviceContext, glcontext); // Opengl32.dll - const char *data = (const char*)glGetString(GL_VERSION); - if (data != nullptr) - this->version = data; - // printf("check -version: %s\n", version.c_str()); - data = (const char*)glGetString(0x8B8C); // GL_SHADING_LANGUAGE_VERSION - if (data != nullptr) - this->glsl_version = data; - data = (const char*)glGetString(GL_VENDOR); - if (data != nullptr) - this->vendor = data; - data = (const char*)glGetString(GL_RENDERER); - if (data != nullptr) - this->renderer = data; + const char *data = (const char*)glGetString(GL_VERSION); + if (data != nullptr) + this->version = data; + // printf("check -version: %s\n", version.c_str()); + data = (const char*)glGetString(0x8B8C); // GL_SHADING_LANGUAGE_VERSION + if (data != nullptr) + this->glsl_version = data; + data = (const char*)glGetString(GL_VENDOR); + if (data != nullptr) + this->vendor = data; + data = (const char*)glGetString(GL_RENDERER); + if (data != nullptr) + this->renderer = data; // Opengl32.dll wglDeleteContext(glcontext); - ::ReleaseDC(hWnd, ourWindowHandleToDeviceContext); + ::ReleaseDC(hWnd, ourWindowHandleToDeviceContext); this->success = true; - } + } - static LRESULT CALLBACK supports_opengl2_wndproc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) - { - switch(message) - { - case WM_CREATE: - { - CREATESTRUCT *pCreate = reinterpret_cast(lParam); - OpenGLVersionCheck *ogl_data = reinterpret_cast(pCreate->lpCreateParams); - ogl_data->check(hWnd); - DestroyWindow(hWnd); - return 0; - } - case WM_NCDESTROY: - message_pump_exit = true; - return 0; - default: - return DefWindowProc(hWnd, message, wParam, lParam); - } - } + static LRESULT CALLBACK supports_opengl2_wndproc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) + { + switch(message) + { + case WM_CREATE: + { + CREATESTRUCT *pCreate = reinterpret_cast(lParam); + OpenGLVersionCheck *ogl_data = reinterpret_cast(pCreate->lpCreateParams); + ogl_data->check(hWnd); + DestroyWindow(hWnd); + return 0; + } + case WM_NCDESTROY: + message_pump_exit = true; + return 0; + default: + return DefWindowProc(hWnd, message, wParam, lParam); + } + } }; bool OpenGLVersionCheck::message_pump_exit = false; #endif /* SLIC3R_GUI */ extern "C" { - typedef int (__stdcall *Slic3rMainFunc)(int argc, wchar_t **argv); - Slic3rMainFunc slic3r_main = nullptr; + typedef int (__stdcall *Slic3rMainFunc)(int argc, wchar_t **argv); + Slic3rMainFunc slic3r_main = nullptr; } +extern "C" { + #ifdef SLIC3R_WRAPPER_NOCONSOLE int APIENTRY wWinMain(HINSTANCE /* hInstance */, HINSTANCE /* hPrevInstance */, PWSTR /* lpCmdLine */, int /* nCmdShow */) { - int argc; - wchar_t **argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + int argc; + wchar_t **argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); #else int wmain(int argc, wchar_t **argv) { #endif - std::vector argv_extended; - argv_extended.emplace_back(argv[0]); + std::vector argv_extended; + argv_extended.emplace_back(argv[0]); #ifdef SLIC3R_GUI - // Here one may push some additional parameters based on the wrapper type. - bool force_mesa = false; + // Here one may push some additional parameters based on the wrapper type. + bool force_mesa = false; #endif /* SLIC3R_GUI */ - for (int i = 1; i < argc; ++ i) { + for (int i = 1; i < argc; ++ i) { #ifdef SLIC3R_GUI - if (wcscmp(argv[i], L"--sw-renderer") == 0) - force_mesa = true; - else if (wcscmp(argv[i], L"--no-sw-renderer") == 0) - force_mesa = false; + if (wcscmp(argv[i], L"--sw-renderer") == 0) + force_mesa = true; + else if (wcscmp(argv[i], L"--no-sw-renderer") == 0) + force_mesa = false; #endif /* SLIC3R_GUI */ - argv_extended.emplace_back(argv[i]); - } - argv_extended.emplace_back(nullptr); + argv_extended.emplace_back(argv[i]); + } + argv_extended.emplace_back(nullptr); #ifdef SLIC3R_GUI - OpenGLVersionCheck opengl_version_check; - bool load_mesa = - // Forced from the command line. - force_mesa || - // Running over a rempote desktop, and the RemoteFX is not enabled, therefore Windows will only provide SW OpenGL 1.1 context. - // In that case, use Mesa. - ::GetSystemMetrics(SM_REMOTESESSION) || - // Try to load the default OpenGL driver and test its context version. - ! opengl_version_check.load_opengl_dll() || ! opengl_version_check.is_version_greater_or_equal_to(2, 0); + OpenGLVersionCheck opengl_version_check; + bool load_mesa = + // Forced from the command line. + force_mesa || + // Running over a rempote desktop, and the RemoteFX is not enabled, therefore Windows will only provide SW OpenGL 1.1 context. + // In that case, use Mesa. + ::GetSystemMetrics(SM_REMOTESESSION) || + // Try to load the default OpenGL driver and test its context version. + ! opengl_version_check.load_opengl_dll() || ! opengl_version_check.is_version_greater_or_equal_to(2, 0); #endif /* SLIC3R_GUI */ - wchar_t path_to_exe[MAX_PATH + 1] = { 0 }; - ::GetModuleFileNameW(nullptr, path_to_exe, MAX_PATH); - wchar_t drive[_MAX_DRIVE]; - wchar_t dir[_MAX_DIR]; - wchar_t fname[_MAX_FNAME]; - wchar_t ext[_MAX_EXT]; - _wsplitpath(path_to_exe, drive, dir, fname, ext); - _wmakepath(path_to_exe, drive, dir, nullptr, nullptr); + wchar_t path_to_exe[MAX_PATH + 1] = { 0 }; + ::GetModuleFileNameW(nullptr, path_to_exe, MAX_PATH); + wchar_t drive[_MAX_DRIVE]; + wchar_t dir[_MAX_DIR]; + wchar_t fname[_MAX_FNAME]; + wchar_t ext[_MAX_EXT]; + _wsplitpath(path_to_exe, drive, dir, fname, ext); + _wmakepath(path_to_exe, drive, dir, nullptr, nullptr); #ifdef SLIC3R_GUI // https://wiki.qt.io/Cross_compiling_Mesa_for_Windows // http://download.qt.io/development_releases/prebuilt/llvmpipe/windows/ - if (load_mesa) { - opengl_version_check.unload_opengl_dll(); - wchar_t path_to_mesa[MAX_PATH + 1] = { 0 }; - wcscpy(path_to_mesa, path_to_exe); - wcscat(path_to_mesa, L"mesa\\opengl32.dll"); - printf("Loading MESA OpenGL library: %S\n", path_to_mesa); - HINSTANCE hInstance_OpenGL = LoadLibraryExW(path_to_mesa, nullptr, 0); - if (hInstance_OpenGL == nullptr) { - printf("MESA OpenGL library was not loaded\n"); - } else - printf("MESA OpenGL library was loaded sucessfully\n"); - } + if (load_mesa) { + opengl_version_check.unload_opengl_dll(); + wchar_t path_to_mesa[MAX_PATH + 1] = { 0 }; + wcscpy(path_to_mesa, path_to_exe); + wcscat(path_to_mesa, L"mesa\\opengl32.dll"); + printf("Loading MESA OpenGL library: %S\n", path_to_mesa); + HINSTANCE hInstance_OpenGL = LoadLibraryExW(path_to_mesa, nullptr, 0); + if (hInstance_OpenGL == nullptr) { + printf("MESA OpenGL library was not loaded\n"); + } else + printf("MESA OpenGL library was loaded sucessfully\n"); + } #endif /* SLIC3R_GUI */ - wchar_t path_to_slic3r[MAX_PATH + 1] = { 0 }; - wcscpy(path_to_slic3r, path_to_exe); - wcscat(path_to_slic3r, L"PrusaSlicer.dll"); + wchar_t path_to_slic3r[MAX_PATH + 1] = { 0 }; + wcscpy(path_to_slic3r, path_to_exe); + wcscat(path_to_slic3r, L"PrusaSlicer.dll"); // printf("Loading Slic3r library: %S\n", path_to_slic3r); - HINSTANCE hInstance_Slic3r = LoadLibraryExW(path_to_slic3r, nullptr, 0); - if (hInstance_Slic3r == nullptr) { - printf("PrusaSlicer.dll was not loaded\n"); - return -1; - } + HINSTANCE hInstance_Slic3r = LoadLibraryExW(path_to_slic3r, nullptr, 0); + if (hInstance_Slic3r == nullptr) { + printf("PrusaSlicer.dll was not loaded\n"); + return -1; + } - // resolve function address here - slic3r_main = (Slic3rMainFunc)GetProcAddress(hInstance_Slic3r, + // resolve function address here + slic3r_main = (Slic3rMainFunc)GetProcAddress(hInstance_Slic3r, #ifdef _WIN64 - // there is just a single calling conversion, therefore no mangling of the function name. - "slic3r_main" + // there is just a single calling conversion, therefore no mangling of the function name. + "slic3r_main" #else // stdcall calling convention declaration - "_slic3r_main@8" + "_slic3r_main@8" #endif - ); - if (slic3r_main == nullptr) { - printf("could not locate the function slic3r_main in PrusaSlicer.dll\n"); - return -1; - } - // argc minus the trailing nullptr of the argv - return slic3r_main((int)argv_extended.size() - 1, argv_extended.data()); + ); + if (slic3r_main == nullptr) { + printf("could not locate the function slic3r_main in PrusaSlicer.dll\n"); + return -1; + } + // argc minus the trailing nullptr of the argv + return slic3r_main((int)argv_extended.size() - 1, argv_extended.data()); +} } diff --git a/src/admesh/CMakeLists.txt b/src/admesh/CMakeLists.txt index 941a7eeb55..7d0177782f 100644 --- a/src/admesh/CMakeLists.txt +++ b/src/admesh/CMakeLists.txt @@ -11,4 +11,4 @@ add_library(admesh STATIC util.cpp ) -target_include_directories(admesh SYSTEM PRIVATE ${Boost_INCLUDE_DIRS}) +target_link_libraries(admesh PRIVATE boost_headeronly) diff --git a/src/admesh/connect.cpp b/src/admesh/connect.cpp index fb32132194..d6de6ce6a4 100644 --- a/src/admesh/connect.cpp +++ b/src/admesh/connect.cpp @@ -28,894 +28,731 @@ #include #include -#include +#include +#include +// Boost pool: Don't use mutexes to synchronize memory allocation. +#define BOOST_POOL_NO_MT +#include #include "stl.h" +struct HashEdge { + // Key of a hash edge: sorted vertices of the edge. + uint32_t key[6]; + // Compare two keys. + bool operator==(const HashEdge &rhs) const { return memcmp(key, rhs.key, sizeof(key)) == 0; } + bool operator!=(const HashEdge &rhs) const { return ! (*this == rhs); } + int hash(int M) const { return ((key[0] / 11 + key[1] / 7 + key[2] / 3) ^ (key[3] / 11 + key[4] / 7 + key[5] / 3)) % M; } -static void stl_match_neighbors_nearby(stl_file *stl, - stl_hash_edge *edge_a, stl_hash_edge *edge_b); -static void stl_record_neighbors(stl_file *stl, - stl_hash_edge *edge_a, stl_hash_edge *edge_b); -static void stl_initialize_facet_check_exact(stl_file *stl); -static void stl_initialize_facet_check_nearby(stl_file *stl); -static void stl_load_edge_exact(stl_file *stl, stl_hash_edge *edge, const stl_vertex *a, const stl_vertex *b); -static int stl_load_edge_nearby(stl_file *stl, stl_hash_edge *edge, - stl_vertex *a, stl_vertex *b, float tolerance); -static void insert_hash_edge(stl_file *stl, stl_hash_edge edge, - void (*match_neighbors)(stl_file *stl, - stl_hash_edge *edge_a, stl_hash_edge *edge_b)); -static int stl_compare_function(stl_hash_edge *edge_a, stl_hash_edge *edge_b); -static void stl_free_edges(stl_file *stl); -static void stl_remove_facet(stl_file *stl, int facet_number); -static void stl_change_vertices(stl_file *stl, int facet_num, int vnot, - stl_vertex new_vertex); -static void stl_which_vertices_to_change(stl_file *stl, stl_hash_edge *edge_a, - stl_hash_edge *edge_b, int *facet1, int *vertex1, - int *facet2, int *vertex2, - stl_vertex *new_vertex1, stl_vertex *new_vertex2); -static void stl_remove_degenerate(stl_file *stl, int facet); -extern int stl_check_normal_vector(stl_file *stl, - int facet_num, int normal_fix_flag); -static void stl_update_connects_remove_1(stl_file *stl, int facet_num); + // Index of a facet owning this edge. + int facet_number; + // Index of this edge inside the facet with an index of facet_number. + // If this edge is stored backwards, which_edge is increased by 3. + int which_edge; + HashEdge *next; + + void load_exact(stl_file *stl, const stl_vertex *a, const stl_vertex *b) + { + { + stl_vertex diff = (*a - *b).cwiseAbs(); + float max_diff = std::max(diff(0), std::max(diff(1), diff(2))); + stl->stats.shortest_edge = std::min(max_diff, stl->stats.shortest_edge); + } + + // Ensure identical vertex ordering of equal edges. + // This method is numerically robust. + if (vertex_lower(*a, *b)) { + } else { + // This edge is loaded backwards. + std::swap(a, b); + this->which_edge += 3; + } + memcpy(&this->key[0], a->data(), sizeof(stl_vertex)); + memcpy(&this->key[3], b->data(), sizeof(stl_vertex)); + // Switch negative zeros to positive zeros, so memcmp will consider them to be equal. + for (size_t i = 0; i < 6; ++ i) { + unsigned char *p = (unsigned char*)(this->key + i); + #if BOOST_ENDIAN_LITTLE_BYTE + if (p[0] == 0 && p[1] == 0 && p[2] == 0 && p[3] == 0x80) + // Negative zero, switch to positive zero. + p[3] = 0; + #else /* BOOST_ENDIAN_LITTLE_BYTE */ + if (p[0] == 0x80 && p[1] == 0 && p[2] == 0 && p[3] == 0) + // Negative zero, switch to positive zero. + p[0] = 0; + #endif /* BOOST_ENDIAN_LITTLE_BYTE */ + } + } + + bool load_nearby(const stl_file *stl, const stl_vertex &a, const stl_vertex &b, float tolerance) + { + // Index of a grid cell spaced by tolerance. + typedef Eigen::Matrix Vec3i; + Vec3i vertex1 = ((a - stl->stats.min) / tolerance).cast(); + Vec3i vertex2 = ((b - stl->stats.min) / tolerance).cast(); + static_assert(sizeof(Vec3i) == 12, "size of Vec3i incorrect"); + + if (vertex1 == vertex2) + // Both vertices hash to the same value + return false; + + // Ensure identical vertex ordering of edges, which vertices land into equal grid cells. + // This method is numerically robust. + if ((vertex1[0] != vertex2[0]) ? + (vertex1[0] < vertex2[0]) : + ((vertex1[1] != vertex2[1]) ? + (vertex1[1] < vertex2[1]) : + (vertex1[2] < vertex2[2]))) { + memcpy(&this->key[0], vertex1.data(), sizeof(stl_vertex)); + memcpy(&this->key[3], vertex2.data(), sizeof(stl_vertex)); + } else { + memcpy(&this->key[0], vertex2.data(), sizeof(stl_vertex)); + memcpy(&this->key[3], vertex1.data(), sizeof(stl_vertex)); + this->which_edge += 3; /* this edge is loaded backwards */ + } + return true; + } + +private: + inline bool vertex_lower(const stl_vertex &a, const stl_vertex &b) { + return (a(0) != b(0)) ? (a(0) < b(0)) : + ((a(1) != b(1)) ? (a(1) < b(1)) : (a(2) < b(2))); + } +}; + +struct HashTableEdges { + HashTableEdges(size_t number_of_faces) { + this->M = (int)hash_size_from_nr_faces(number_of_faces); + this->heads.assign(this->M, nullptr); + this->tail = pool.construct(); + this->tail->next = this->tail; + for (int i = 0; i < this->M; ++ i) + this->heads[i] = this->tail; + } + ~HashTableEdges() { +#ifndef NDEBUG + for (int i = 0; i < this->M; ++ i) + for (HashEdge *temp = this->heads[i]; temp != this->tail; temp = temp->next) + ++ this->freed; + this->tail = nullptr; +#endif /* NDEBUG */ + } + + void insert_edge_exact(stl_file *stl, const HashEdge &edge) + { + this->insert_edge(stl, edge, [stl](const HashEdge& edge1, const HashEdge& edge2) { record_neighbors(stl, edge1, edge2); }); + } + + void insert_edge_nearby(stl_file *stl, const HashEdge &edge) + { + this->insert_edge(stl, edge, [stl](const HashEdge& edge1, const HashEdge& edge2) { match_neighbors_nearby(stl, edge1, edge2); }); + } + + // Hash table on edges + std::vector heads; + HashEdge* tail; + int M; + boost::object_pool pool; + +#ifndef NDEBUG + size_t malloced = 0; + size_t freed = 0; + size_t collisions = 0; +#endif /* NDEBUG */ + +private: + static inline size_t hash_size_from_nr_faces(const size_t nr_faces) + { + // Good primes for addressing a cca. 30 bit space. + // https://planetmath.org/goodhashtableprimes + static std::vector primes{ 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741 }; + // Find a prime number for 50% filling of the shared triangle edges in the mesh. + auto it = std::upper_bound(primes.begin(), primes.end(), nr_faces * 3 * 2 - 1); + return (it == primes.end()) ? primes.back() : *it; + } + + + // MatchNeighbors(stl_file *stl, const HashEdge &edge_a, const HashEdge &edge_b) + template + void insert_edge(stl_file *stl, const HashEdge &edge, MatchNeighbors match_neighbors) + { + int chain_number = edge.hash(this->M); + HashEdge *link = this->heads[chain_number]; + if (link == this->tail) { + // This list doesn't have any edges currently in it. Add this one. + HashEdge *new_edge = pool.construct(edge); +#ifndef NDEBUG + ++ this->malloced; +#endif /* NDEBUG */ + new_edge->next = this->tail; + this->heads[chain_number] = new_edge; + } else if (edges_equal(edge, *link)) { + // This is a match. Record result in neighbors list. + match_neighbors(edge, *link); + // Delete the matched edge from the list. + this->heads[chain_number] = link->next; + // pool.destroy(link); +#ifndef NDEBUG + ++ this->freed; +#endif /* NDEBUG */ + } else { + // Continue through the rest of the list. + for (;;) { + if (link->next == this->tail) { + // This is the last item in the list. Insert a new edge. + HashEdge *new_edge = pool.construct(); +#ifndef NDEBUG + ++ this->malloced; +#endif /* NDEBUG */ + *new_edge = edge; + new_edge->next = this->tail; + link->next = new_edge; +#ifndef NDEBUG + ++ this->collisions; +#endif /* NDEBUG */ + break; + } + if (edges_equal(edge, *link->next)) { + // This is a match. Record result in neighbors list. + match_neighbors(edge, *link->next); + // Delete the matched edge from the list. + HashEdge *temp = link->next; + link->next = link->next->next; + // pool.destroy(temp); +#ifndef NDEBUG + ++ this->freed; +#endif /* NDEBUG */ + break; + } + // This is not a match. Go to the next link. + link = link->next; +#ifndef NDEBUG + ++ this->collisions; +#endif /* NDEBUG */ + } + } + } + + // Edges equal for hashing. Edgesof different facet are allowed to be matched. + static inline bool edges_equal(const HashEdge &edge_a, const HashEdge &edge_b) + { + return edge_a.facet_number != edge_b.facet_number && edge_a == edge_b; + } + + static void record_neighbors(stl_file *stl, const HashEdge &edge_a, const HashEdge &edge_b) + { + // Facet a's neighbor is facet b + stl->neighbors_start[edge_a.facet_number].neighbor[edge_a.which_edge % 3] = edge_b.facet_number; /* sets the .neighbor part */ + stl->neighbors_start[edge_a.facet_number].which_vertex_not[edge_a.which_edge % 3] = (edge_b.which_edge + 2) % 3; /* sets the .which_vertex_not part */ + + // Facet b's neighbor is facet a + stl->neighbors_start[edge_b.facet_number].neighbor[edge_b.which_edge % 3] = edge_a.facet_number; /* sets the .neighbor part */ + stl->neighbors_start[edge_b.facet_number].which_vertex_not[edge_b.which_edge % 3] = (edge_a.which_edge + 2) % 3; /* sets the .which_vertex_not part */ + + if (((edge_a.which_edge < 3) && (edge_b.which_edge < 3)) || ((edge_a.which_edge > 2) && (edge_b.which_edge > 2))) { + // These facets are oriented in opposite directions, their normals are probably messed up. + stl->neighbors_start[edge_a.facet_number].which_vertex_not[edge_a.which_edge % 3] += 3; + stl->neighbors_start[edge_b.facet_number].which_vertex_not[edge_b.which_edge % 3] += 3; + } + + // Count successful connects: + // Total connects: + stl->stats.connected_edges += 2; + // Count individual connects: + switch (stl->neighbors_start[edge_a.facet_number].num_neighbors()) { + case 1: ++ stl->stats.connected_facets_1_edge; break; + case 2: ++ stl->stats.connected_facets_2_edge; break; + case 3: ++ stl->stats.connected_facets_3_edge; break; + default: assert(false); + } + switch (stl->neighbors_start[edge_b.facet_number].num_neighbors()) { + case 1: ++ stl->stats.connected_facets_1_edge; break; + case 2: ++ stl->stats.connected_facets_2_edge; break; + case 3: ++ stl->stats.connected_facets_3_edge; break; + default: assert(false); + } + } + + static void match_neighbors_nearby(stl_file *stl, const HashEdge &edge_a, const HashEdge &edge_b) + { + record_neighbors(stl, edge_a, edge_b); + + // Which vertices to change + int facet1 = -1; + int facet2 = -1; + int vertex1, vertex2; + stl_vertex new_vertex1, new_vertex2; + { + int v1a; // pair 1, facet a + int v1b; // pair 1, facet b + int v2a; // pair 2, facet a + int v2b; // pair 2, facet b + // Find first pair. + if (edge_a.which_edge < 3) { + v1a = edge_a.which_edge; + v2a = (edge_a.which_edge + 1) % 3; + } else { + v2a = edge_a.which_edge % 3; + v1a = (edge_a.which_edge + 1) % 3; + } + if (edge_b.which_edge < 3) { + v1b = edge_b.which_edge; + v2b = (edge_b.which_edge + 1) % 3; + } else { + v2b = edge_b.which_edge % 3; + v1b = (edge_b.which_edge + 1) % 3; + } + + // Of the first pair, which vertex, if any, should be changed + if (stl->facet_start[edge_a.facet_number].vertex[v1a] != stl->facet_start[edge_b.facet_number].vertex[v1b]) { + // These facets are different. + if ( (stl->neighbors_start[edge_a.facet_number].neighbor[v1a] == -1) + && (stl->neighbors_start[edge_a.facet_number].neighbor[(v1a + 2) % 3] == -1)) { + // This vertex has no neighbors. This is a good one to change. + facet1 = edge_a.facet_number; + vertex1 = v1a; + new_vertex1 = stl->facet_start[edge_b.facet_number].vertex[v1b]; + } else { + facet1 = edge_b.facet_number; + vertex1 = v1b; + new_vertex1 = stl->facet_start[edge_a.facet_number].vertex[v1a]; + } + } + + // Of the second pair, which vertex, if any, should be changed. + if (stl->facet_start[edge_a.facet_number].vertex[v2a] == stl->facet_start[edge_b.facet_number].vertex[v2b]) { + // These facets are different. + if ( (stl->neighbors_start[edge_a.facet_number].neighbor[v2a] == -1) + && (stl->neighbors_start[edge_a.facet_number].neighbor[(v2a + 2) % 3] == -1)) { + // This vertex has no neighbors. This is a good one to change. + facet2 = edge_a.facet_number; + vertex2 = v2a; + new_vertex2 = stl->facet_start[edge_b.facet_number].vertex[v2b]; + } else { + facet2 = edge_b.facet_number; + vertex2 = v2b; + new_vertex2 = stl->facet_start[edge_a.facet_number].vertex[v2a]; + } + } + } + + auto change_vertices = [stl](int facet_num, int vnot, stl_vertex new_vertex) + { + int first_facet = facet_num; + bool direction = false; + + for (;;) { + int pivot_vertex; + int next_edge; + if (vnot > 2) { + if (direction) { + pivot_vertex = (vnot + 1) % 3; + next_edge = vnot % 3; + } + else { + pivot_vertex = (vnot + 2) % 3; + next_edge = pivot_vertex; + } + direction = !direction; + } + else { + if (direction) { + pivot_vertex = (vnot + 2) % 3; + next_edge = pivot_vertex; + } + else { + pivot_vertex = (vnot + 1) % 3; + next_edge = vnot; + } + } + #if 0 + if (stl->facet_start[facet_num].vertex[pivot_vertex](0) == new_vertex(0) && + stl->facet_start[facet_num].vertex[pivot_vertex](1) == new_vertex(1) && + stl->facet_start[facet_num].vertex[pivot_vertex](2) == new_vertex(2)) + printf("Changing vertex %f,%f,%f: Same !!!\r\n", new_vertex(0), new_vertex(1), new_vertex(2)); + else { + if (stl->facet_start[facet_num].vertex[pivot_vertex](0) != new_vertex(0)) + printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", + stl->facet_start[facet_num].vertex[pivot_vertex](0), + *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](0)), + new_vertex(0), + *reinterpret_cast(&new_vertex(0))); + if (stl->facet_start[facet_num].vertex[pivot_vertex](1) != new_vertex(1)) + printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", + stl->facet_start[facet_num].vertex[pivot_vertex](1), + *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](1)), + new_vertex(1), + *reinterpret_cast(&new_vertex(1))); + if (stl->facet_start[facet_num].vertex[pivot_vertex](2) != new_vertex(2)) + printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", + stl->facet_start[facet_num].vertex[pivot_vertex](2), + *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](2)), + new_vertex(2), + *reinterpret_cast(&new_vertex(2))); + } + #endif + stl->facet_start[facet_num].vertex[pivot_vertex] = new_vertex; + vnot = stl->neighbors_start[facet_num].which_vertex_not[next_edge]; + facet_num = stl->neighbors_start[facet_num].neighbor[next_edge]; + if (facet_num == -1) + break; + + if (facet_num == first_facet) { + // back to the beginning + BOOST_LOG_TRIVIAL(info) << "Back to the first facet changing vertices: probably a mobius part. Try using a smaller tolerance or don't do a nearby check."; + return; + } + } + }; + + if (facet1 != -1) { + int vnot1 = (facet1 == edge_a.facet_number) ? + (edge_a.which_edge + 2) % 3 : + (edge_b.which_edge + 2) % 3; + if (((vnot1 + 2) % 3) == vertex1) + vnot1 += 3; + change_vertices(facet1, vnot1, new_vertex1); + } + if (facet2 != -1) { + int vnot2 = (facet2 == edge_a.facet_number) ? + (edge_a.which_edge + 2) % 3 : + (edge_b.which_edge + 2) % 3; + if (((vnot2 + 2) % 3) == vertex2) + vnot2 += 3; + change_vertices(facet2, vnot2, new_vertex2); + } + stl->stats.edges_fixed += 2; + } +}; // This function builds the neighbors list. No modifications are made // to any of the facets. The edges are said to match only if all six // floats of the first edge matches all six floats of the second edge. void stl_check_facets_exact(stl_file *stl) { - if (stl->error) - return; + assert(stl->facet_start.size() == stl->neighbors_start.size()); - stl->stats.connected_edges = 0; - stl->stats.connected_facets_1_edge = 0; - stl->stats.connected_facets_2_edge = 0; - stl->stats.connected_facets_3_edge = 0; + stl->stats.connected_edges = 0; + stl->stats.connected_facets_1_edge = 0; + stl->stats.connected_facets_2_edge = 0; + stl->stats.connected_facets_3_edge = 0; - // If any two of the three vertices are found to be exactally the same, call them degenerate and remove the facet. - // Do it before the next step, as the next step stores references to the face indices in the hash tables and removing a facet - // will break the references. - for (int i = 0; i < stl->stats.number_of_facets;) { - stl_facet &facet = stl->facet_start[i]; - if (facet.vertex[0] == facet.vertex[1] || facet.vertex[1] == facet.vertex[2] || facet.vertex[0] == facet.vertex[2]) { - // Remove the degenerate facet. - facet = stl->facet_start[--stl->stats.number_of_facets]; - stl->stats.facets_removed += 1; - stl->stats.degenerate_facets += 1; - } else - ++ i; - } + // If any two of the three vertices are found to be exactally the same, call them degenerate and remove the facet. + // Do it before the next step, as the next step stores references to the face indices in the hash tables and removing a facet + // will break the references. + for (uint32_t i = 0; i < stl->stats.number_of_facets;) { + stl_facet &facet = stl->facet_start[i]; + if (facet.vertex[0] == facet.vertex[1] || facet.vertex[1] == facet.vertex[2] || facet.vertex[0] == facet.vertex[2]) { + // Remove the degenerate facet. + facet = stl->facet_start[-- stl->stats.number_of_facets]; + stl->facet_start.pop_back(); + stl->neighbors_start.pop_back(); + stl->stats.facets_removed += 1; + stl->stats.degenerate_facets += 1; + } else + ++ i; + } - // Connect neighbor edges. - stl_initialize_facet_check_exact(stl); - for (int i = 0; i < stl->stats.number_of_facets; i++) { - const stl_facet &facet = stl->facet_start[i]; - for (int j = 0; j < 3; j++) { - stl_hash_edge edge; - edge.facet_number = i; - edge.which_edge = j; - stl_load_edge_exact(stl, &edge, &facet.vertex[j], &facet.vertex[(j + 1) % 3]); - insert_hash_edge(stl, edge, stl_record_neighbors); - } - } - stl_free_edges(stl); + // Initialize hash table. + HashTableEdges hash_table(stl->stats.number_of_facets); + for (auto &neighbor : stl->neighbors_start) + neighbor.reset(); + + // Connect neighbor edges. + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { + const stl_facet &facet = stl->facet_start[i]; + for (int j = 0; j < 3; ++ j) { + HashEdge edge; + edge.facet_number = i; + edge.which_edge = j; + edge.load_exact(stl, &facet.vertex[j], &facet.vertex[(j + 1) % 3]); + hash_table.insert_edge_exact(stl, edge); + } + } #if 0 - printf("Number of faces: %d, number of manifold edges: %d, number of connected edges: %d, number of unconnected edges: %d\r\n", - stl->stats.number_of_facets, stl->stats.number_of_facets * 3, - stl->stats.connected_edges, stl->stats.number_of_facets * 3 - stl->stats.connected_edges); + printf("Number of faces: %d, number of manifold edges: %d, number of connected edges: %d, number of unconnected edges: %d\r\n", + stl->stats.number_of_facets, stl->stats.number_of_facets * 3, + stl->stats.connected_edges, stl->stats.number_of_facets * 3 - stl->stats.connected_edges); #endif } -static void stl_load_edge_exact(stl_file *stl, stl_hash_edge *edge, const stl_vertex *a, const stl_vertex *b) { - - if (stl->error) return; - - { - stl_vertex diff = (*a - *b).cwiseAbs(); - float max_diff = std::max(diff(0), std::max(diff(1), diff(2))); - stl->stats.shortest_edge = std::min(max_diff, stl->stats.shortest_edge); - } - - // Ensure identical vertex ordering of equal edges. - // This method is numerically robust. - if (stl_vertex_lower(*a, *b)) { - } else { - std::swap(a, b); - edge->which_edge += 3; /* this edge is loaded backwards */ - } - memcpy(&edge->key[0], a->data(), sizeof(stl_vertex)); - memcpy(&edge->key[3], b->data(), sizeof(stl_vertex)); - // Switch negative zeros to positive zeros, so memcmp will consider them to be equal. - for (size_t i = 0; i < 6; ++ i) { - unsigned char *p = (unsigned char*)(edge->key + i); -#ifdef BOOST_LITTLE_ENDIAN - if (p[0] == 0 && p[1] == 0 && p[2] == 0 && p[3] == 0x80) - // Negative zero, switch to positive zero. - p[3] = 0; -#else /* BOOST_LITTLE_ENDIAN */ - if (p[0] == 0x80 && p[1] == 0 && p[2] == 0 && p[3] == 0) - // Negative zero, switch to positive zero. - p[0] = 0; -#endif /* BOOST_LITTLE_ENDIAN */ - } -} - -static inline size_t hash_size_from_nr_faces(const size_t nr_faces) -{ - // Good primes for addressing a cca. 30 bit space. - // https://planetmath.org/goodhashtableprimes - static std::vector primes{ 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741 }; - // Find a prime number for 50% filling of the shared triangle edges in the mesh. - auto it = std::upper_bound(primes.begin(), primes.end(), nr_faces * 3 * 2 - 1); - return (it == primes.end()) ? primes.back() : *it; -} - -static void -stl_initialize_facet_check_exact(stl_file *stl) { - int i; - - if (stl->error) return; - - stl->stats.malloced = 0; - stl->stats.freed = 0; - stl->stats.collisions = 0; - - stl->M = hash_size_from_nr_faces(stl->stats.number_of_facets); - - for (i = 0; i < stl->stats.number_of_facets ; i++) { - /* initialize neighbors list to -1 to mark unconnected edges */ - stl->neighbors_start[i].neighbor[0] = -1; - stl->neighbors_start[i].neighbor[1] = -1; - stl->neighbors_start[i].neighbor[2] = -1; - } - - stl->heads = (stl_hash_edge**)calloc(stl->M, sizeof(*stl->heads)); - if(stl->heads == NULL) perror("stl_initialize_facet_check_exact"); - - stl->tail = (stl_hash_edge*)malloc(sizeof(stl_hash_edge)); - if(stl->tail == NULL) perror("stl_initialize_facet_check_exact"); - - stl->tail->next = stl->tail; - - for(i = 0; i < stl->M; i++) { - stl->heads[i] = stl->tail; - } -} - -static void insert_hash_edge(stl_file *stl, stl_hash_edge edge, - void (*match_neighbors)(stl_file *stl, - stl_hash_edge *edge_a, stl_hash_edge *edge_b)) -{ - if (stl->error) return; - - int chain_number = edge.hash(stl->M); - stl_hash_edge *link = stl->heads[chain_number]; - - stl_hash_edge *new_edge; - stl_hash_edge *temp; - if(link == stl->tail) { - /* This list doesn't have any edges currently in it. Add this one. */ - new_edge = (stl_hash_edge*)malloc(sizeof(stl_hash_edge)); - if(new_edge == NULL) perror("insert_hash_edge"); - stl->stats.malloced++; - *new_edge = edge; - new_edge->next = stl->tail; - stl->heads[chain_number] = new_edge; - return; - } else if(!stl_compare_function(&edge, link)) { - /* This is a match. Record result in neighbors list. */ - match_neighbors(stl, &edge, link); - /* Delete the matched edge from the list. */ - stl->heads[chain_number] = link->next; - free(link); - stl->stats.freed++; - return; - } else { - /* Continue through the rest of the list */ - for(;;) { - if(link->next == stl->tail) { - /* This is the last item in the list. Insert a new edge. */ - new_edge = (stl_hash_edge*)malloc(sizeof(stl_hash_edge)); - if(new_edge == NULL) perror("insert_hash_edge"); - stl->stats.malloced++; - *new_edge = edge; - new_edge->next = stl->tail; - link->next = new_edge; - stl->stats.collisions++; - return; - } else if(!stl_compare_function(&edge, link->next)) { - /* This is a match. Record result in neighbors list. */ - match_neighbors(stl, &edge, link->next); - - /* Delete the matched edge from the list. */ - temp = link->next; - link->next = link->next->next; - free(temp); - stl->stats.freed++; - return; - } else { - /* This is not a match. Go to the next link */ - link = link->next; - stl->stats.collisions++; - } - } - } -} - -// Return 1 if the edges are not matched. -static inline int stl_compare_function(stl_hash_edge *edge_a, stl_hash_edge *edge_b) -{ - // Don't match edges of the same facet - return (edge_a->facet_number == edge_b->facet_number) || (*edge_a != *edge_b); -} - void stl_check_facets_nearby(stl_file *stl, float tolerance) { - if (stl->error) - return; + if ( (stl->stats.connected_facets_1_edge == stl->stats.number_of_facets) + && (stl->stats.connected_facets_2_edge == stl->stats.number_of_facets) + && (stl->stats.connected_facets_3_edge == stl->stats.number_of_facets)) { + // No need to check any further. All facets are connected. + return; + } - if( (stl->stats.connected_facets_1_edge == stl->stats.number_of_facets) - && (stl->stats.connected_facets_2_edge == stl->stats.number_of_facets) - && (stl->stats.connected_facets_3_edge == stl->stats.number_of_facets)) { - /* No need to check any further. All facets are connected */ - return; - } - - stl_initialize_facet_check_nearby(stl); - - for (int i = 0; i < stl->stats.number_of_facets; ++ i) { - //FIXME is the copy necessary? - stl_facet facet = stl->facet_start[i]; - for (int j = 0; j < 3; j++) { - if(stl->neighbors_start[i].neighbor[j] == -1) { - stl_hash_edge edge; - edge.facet_number = i; - edge.which_edge = j; - if(stl_load_edge_nearby(stl, &edge, &facet.vertex[j], - &facet.vertex[(j + 1) % 3], - tolerance)) { - /* only insert edges that have different keys */ - insert_hash_edge(stl, edge, stl_match_neighbors_nearby); - } - } - } - } - - stl_free_edges(stl); -} - -static int stl_load_edge_nearby(stl_file *stl, stl_hash_edge *edge, stl_vertex *a, stl_vertex *b, float tolerance) -{ - // Index of a grid cell spaced by tolerance. - typedef Eigen::Matrix Vec3i; - Vec3i vertex1 = ((*a - stl->stats.min) / tolerance).cast(); - Vec3i vertex2 = ((*b - stl->stats.min) / tolerance).cast(); - static_assert(sizeof(Vec3i) == 12, "size of Vec3i incorrect"); - - if (vertex1 == vertex2) - // Both vertices hash to the same value - return 0; - - // Ensure identical vertex ordering of edges, which vertices land into equal grid cells. - // This method is numerically robust. - if ((vertex1[0] != vertex2[0]) ? - (vertex1[0] < vertex2[0]) : - ((vertex1[1] != vertex2[1]) ? - (vertex1[1] < vertex2[1]) : - (vertex1[2] < vertex2[2]))) { - memcpy(&edge->key[0], vertex1.data(), sizeof(stl_vertex)); - memcpy(&edge->key[3], vertex2.data(), sizeof(stl_vertex)); - } else { - memcpy(&edge->key[0], vertex2.data(), sizeof(stl_vertex)); - memcpy(&edge->key[3], vertex1.data(), sizeof(stl_vertex)); - edge->which_edge += 3; /* this edge is loaded backwards */ - } - return 1; -} - -static void stl_free_edges(stl_file *stl) -{ - if (stl->error) - return; - - if(stl->stats.malloced != stl->stats.freed) { - for (int i = 0; i < stl->M; i++) { - for (stl_hash_edge *temp = stl->heads[i]; stl->heads[i] != stl->tail; temp = stl->heads[i]) { - stl->heads[i] = stl->heads[i]->next; - free(temp); - ++ stl->stats.freed; - } - } - } - free(stl->heads); - stl->heads = nullptr; - free(stl->tail); - stl->tail = nullptr; -} - -static void stl_initialize_facet_check_nearby(stl_file *stl) -{ - int i; - - if (stl->error) return; - - stl->stats.malloced = 0; - stl->stats.freed = 0; - stl->stats.collisions = 0; - - /* tolerance = STL_MAX(stl->stats.shortest_edge, tolerance);*/ - /* tolerance = STL_MAX((stl->stats.bounding_diameter / 500000.0), tolerance);*/ - /* tolerance *= 0.5;*/ - - stl->M = hash_size_from_nr_faces(stl->stats.number_of_facets); - - stl->heads = (stl_hash_edge**)calloc(stl->M, sizeof(*stl->heads)); - if(stl->heads == NULL) perror("stl_initialize_facet_check_nearby"); - - stl->tail = (stl_hash_edge*)malloc(sizeof(stl_hash_edge)); - if(stl->tail == NULL) perror("stl_initialize_facet_check_nearby"); - - stl->tail->next = stl->tail; - - for(i = 0; i < stl->M; i++) { - stl->heads[i] = stl->tail; - } -} - - - -static void -stl_record_neighbors(stl_file *stl, - stl_hash_edge *edge_a, stl_hash_edge *edge_b) { - int i; - int j; - - if (stl->error) return; - - /* Facet a's neighbor is facet b */ - stl->neighbors_start[edge_a->facet_number].neighbor[edge_a->which_edge % 3] = - edge_b->facet_number; /* sets the .neighbor part */ - - stl->neighbors_start[edge_a->facet_number]. - which_vertex_not[edge_a->which_edge % 3] = - (edge_b->which_edge + 2) % 3; /* sets the .which_vertex_not part */ - - /* Facet b's neighbor is facet a */ - stl->neighbors_start[edge_b->facet_number].neighbor[edge_b->which_edge % 3] = - edge_a->facet_number; /* sets the .neighbor part */ - - stl->neighbors_start[edge_b->facet_number]. - which_vertex_not[edge_b->which_edge % 3] = - (edge_a->which_edge + 2) % 3; /* sets the .which_vertex_not part */ - - if( ((edge_a->which_edge < 3) && (edge_b->which_edge < 3)) - || ((edge_a->which_edge > 2) && (edge_b->which_edge > 2))) { - /* these facets are oriented in opposite directions. */ - /* their normals are probably messed up. */ - stl->neighbors_start[edge_a->facet_number]. - which_vertex_not[edge_a->which_edge % 3] += 3; - stl->neighbors_start[edge_b->facet_number]. - which_vertex_not[edge_b->which_edge % 3] += 3; - } - - - /* Count successful connects */ - /* Total connects */ - stl->stats.connected_edges += 2; - /* Count individual connects */ - i = ((stl->neighbors_start[edge_a->facet_number].neighbor[0] == -1) + - (stl->neighbors_start[edge_a->facet_number].neighbor[1] == -1) + - (stl->neighbors_start[edge_a->facet_number].neighbor[2] == -1)); - j = ((stl->neighbors_start[edge_b->facet_number].neighbor[0] == -1) + - (stl->neighbors_start[edge_b->facet_number].neighbor[1] == -1) + - (stl->neighbors_start[edge_b->facet_number].neighbor[2] == -1)); - if(i == 2) { - stl->stats.connected_facets_1_edge +=1; - } else if(i == 1) { - stl->stats.connected_facets_2_edge +=1; - } else { - stl->stats.connected_facets_3_edge +=1; - } - if(j == 2) { - stl->stats.connected_facets_1_edge +=1; - } else if(j == 1) { - stl->stats.connected_facets_2_edge +=1; - } else { - stl->stats.connected_facets_3_edge +=1; - } -} - -static void stl_match_neighbors_nearby(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b) -{ - int facet1; - int facet2; - int vertex1; - int vertex2; - int vnot1; - int vnot2; - stl_vertex new_vertex1; - stl_vertex new_vertex2; - - if (stl->error) return; - - stl_record_neighbors(stl, edge_a, edge_b); - stl_which_vertices_to_change(stl, edge_a, edge_b, &facet1, &vertex1, - &facet2, &vertex2, &new_vertex1, &new_vertex2); - if(facet1 != -1) { - if(facet1 == edge_a->facet_number) { - vnot1 = (edge_a->which_edge + 2) % 3; - } else { - vnot1 = (edge_b->which_edge + 2) % 3; - } - if(((vnot1 + 2) % 3) == vertex1) { - vnot1 += 3; - } - stl_change_vertices(stl, facet1, vnot1, new_vertex1); - } - if(facet2 != -1) { - if(facet2 == edge_a->facet_number) { - vnot2 = (edge_a->which_edge + 2) % 3; - } else { - vnot2 = (edge_b->which_edge + 2) % 3; - } - if(((vnot2 + 2) % 3) == vertex2) { - vnot2 += 3; - } - stl_change_vertices(stl, facet2, vnot2, new_vertex2); - } - stl->stats.edges_fixed += 2; -} - - -static void stl_change_vertices(stl_file *stl, int facet_num, int vnot, stl_vertex new_vertex) { - int first_facet; - int direction; - int next_edge; - int pivot_vertex; - - if (stl->error) return; - - first_facet = facet_num; - direction = 0; - - for(;;) { - if(vnot > 2) { - if(direction == 0) { - pivot_vertex = (vnot + 2) % 3; - next_edge = pivot_vertex; - direction = 1; - } else { - pivot_vertex = (vnot + 1) % 3; - next_edge = vnot % 3; - direction = 0; - } - } else { - if(direction == 0) { - pivot_vertex = (vnot + 1) % 3; - next_edge = vnot; - } else { - pivot_vertex = (vnot + 2) % 3; - next_edge = pivot_vertex; - } - } -#if 0 - if (stl->facet_start[facet_num].vertex[pivot_vertex](0) == new_vertex(0) && - stl->facet_start[facet_num].vertex[pivot_vertex](1) == new_vertex(1) && - stl->facet_start[facet_num].vertex[pivot_vertex](2) == new_vertex(2)) - printf("Changing vertex %f,%f,%f: Same !!!\r\n", - new_vertex(0), new_vertex(1), new_vertex(2)); - else { - if (stl->facet_start[facet_num].vertex[pivot_vertex](0) != new_vertex(0)) - printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", - stl->facet_start[facet_num].vertex[pivot_vertex](0), - *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](0)), - new_vertex(0), - *reinterpret_cast(&new_vertex(0))); - if (stl->facet_start[facet_num].vertex[pivot_vertex](1) != new_vertex(1)) - printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", - stl->facet_start[facet_num].vertex[pivot_vertex](1), - *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](1)), - new_vertex(1), - *reinterpret_cast(&new_vertex(1))); - if (stl->facet_start[facet_num].vertex[pivot_vertex](2) != new_vertex(2)) - printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", - stl->facet_start[facet_num].vertex[pivot_vertex](2), - *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](2)), - new_vertex(2), - *reinterpret_cast(&new_vertex(2))); - } -#endif - stl->facet_start[facet_num].vertex[pivot_vertex] = new_vertex; - vnot = stl->neighbors_start[facet_num].which_vertex_not[next_edge]; - facet_num = stl->neighbors_start[facet_num].neighbor[next_edge]; - - if(facet_num == -1) { - break; - } - - if(facet_num == first_facet) { - /* back to the beginning */ - printf("\ -Back to the first facet changing vertices: probably a mobius part.\n\ -Try using a smaller tolerance or don't do a nearby check\n"); - return; - } - } -} - -static void -stl_which_vertices_to_change(stl_file *stl, stl_hash_edge *edge_a, - stl_hash_edge *edge_b, int *facet1, int *vertex1, - int *facet2, int *vertex2, - stl_vertex *new_vertex1, stl_vertex *new_vertex2) { - int v1a; /* pair 1, facet a */ - int v1b; /* pair 1, facet b */ - int v2a; /* pair 2, facet a */ - int v2b; /* pair 2, facet b */ - - /* Find first pair */ - if(edge_a->which_edge < 3) { - v1a = edge_a->which_edge; - v2a = (edge_a->which_edge + 1) % 3; - } else { - v2a = edge_a->which_edge % 3; - v1a = (edge_a->which_edge + 1) % 3; - } - if(edge_b->which_edge < 3) { - v1b = edge_b->which_edge; - v2b = (edge_b->which_edge + 1) % 3; - } else { - v2b = edge_b->which_edge % 3; - v1b = (edge_b->which_edge + 1) % 3; - } - - // Of the first pair, which vertex, if any, should be changed - if(stl->facet_start[edge_a->facet_number].vertex[v1a] == - stl->facet_start[edge_b->facet_number].vertex[v1b]) { - // These facets are already equal. No need to change. - *facet1 = -1; - } else { - if( (stl->neighbors_start[edge_a->facet_number].neighbor[v1a] == -1) - && (stl->neighbors_start[edge_a->facet_number]. - neighbor[(v1a + 2) % 3] == -1)) { - /* This vertex has no neighbors. This is a good one to change */ - *facet1 = edge_a->facet_number; - *vertex1 = v1a; - *new_vertex1 = stl->facet_start[edge_b->facet_number].vertex[v1b]; - } else { - *facet1 = edge_b->facet_number; - *vertex1 = v1b; - *new_vertex1 = stl->facet_start[edge_a->facet_number].vertex[v1a]; - } - } - - /* Of the second pair, which vertex, if any, should be changed */ - if(stl->facet_start[edge_a->facet_number].vertex[v2a] == - stl->facet_start[edge_b->facet_number].vertex[v2b]) { - // These facets are already equal. No need to change. - *facet2 = -1; - } else { - if( (stl->neighbors_start[edge_a->facet_number].neighbor[v2a] == -1) - && (stl->neighbors_start[edge_a->facet_number]. - neighbor[(v2a + 2) % 3] == -1)) { - /* This vertex has no neighbors. This is a good one to change */ - *facet2 = edge_a->facet_number; - *vertex2 = v2a; - *new_vertex2 = stl->facet_start[edge_b->facet_number].vertex[v2b]; - } else { - *facet2 = edge_b->facet_number; - *vertex2 = v2b; - *new_vertex2 = stl->facet_start[edge_a->facet_number].vertex[v2a]; - } - } -} - -static void -stl_remove_facet(stl_file *stl, int facet_number) { - int neighbor[3]; - int vnot[3]; - int i; - int j; - - if (stl->error) return; - - stl->stats.facets_removed += 1; - /* Update list of connected edges */ - j = ((stl->neighbors_start[facet_number].neighbor[0] == -1) + - (stl->neighbors_start[facet_number].neighbor[1] == -1) + - (stl->neighbors_start[facet_number].neighbor[2] == -1)); - if(j == 2) { - stl->stats.connected_facets_1_edge -= 1; - } else if(j == 1) { - stl->stats.connected_facets_2_edge -= 1; - stl->stats.connected_facets_1_edge -= 1; - } else if(j == 0) { - stl->stats.connected_facets_3_edge -= 1; - stl->stats.connected_facets_2_edge -= 1; - stl->stats.connected_facets_1_edge -= 1; - } - - stl->facet_start[facet_number] = - stl->facet_start[stl->stats.number_of_facets - 1]; - /* I could reallocate at this point, but it is not really necessary. */ - stl->neighbors_start[facet_number] = - stl->neighbors_start[stl->stats.number_of_facets - 1]; - stl->stats.number_of_facets -= 1; - - for(i = 0; i < 3; i++) { - neighbor[i] = stl->neighbors_start[facet_number].neighbor[i]; - vnot[i] = stl->neighbors_start[facet_number].which_vertex_not[i]; - } - - for(i = 0; i < 3; i++) { - if(neighbor[i] != -1) { - if(stl->neighbors_start[neighbor[i]].neighbor[(vnot[i] + 1)% 3] != - stl->stats.number_of_facets) { - printf("\ -in stl_remove_facet: neighbor = %d numfacets = %d this is wrong\n", - stl->neighbors_start[neighbor[i]].neighbor[(vnot[i] + 1)% 3], - stl->stats.number_of_facets); - return; - } - stl->neighbors_start[neighbor[i]].neighbor[(vnot[i] + 1)% 3] - = facet_number; - } - } + HashTableEdges hash_table(stl->stats.number_of_facets); + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { + //FIXME is the copy necessary? + stl_facet facet = stl->facet_start[i]; + for (int j = 0; j < 3; j++) { + if (stl->neighbors_start[i].neighbor[j] == -1) { + HashEdge edge; + edge.facet_number = i; + edge.which_edge = j; + if (edge.load_nearby(stl, facet.vertex[j], facet.vertex[(j + 1) % 3], tolerance)) + // Only insert edges that have different keys. + hash_table.insert_edge_nearby(stl, edge); + } + } + } } void stl_remove_unconnected_facets(stl_file *stl) { - /* A couple of things need to be done here. One is to remove any */ - /* completely unconnected facets (0 edges connected) since these are */ - /* useless and could be completely wrong. The second thing that needs to */ - /* be done is to remove any degenerate facets that were created during */ - /* stl_check_facets_nearby(). */ - if (stl->error) - return; + // A couple of things need to be done here. One is to remove any completely unconnected facets (0 edges connected) since these are + // useless and could be completely wrong. The second thing that needs to be done is to remove any degenerate facets that were created during + // stl_check_facets_nearby(). + auto remove_facet = [stl](int facet_number) + { + ++ stl->stats.facets_removed; + /* Update list of connected edges */ + stl_neighbors &neighbors = stl->neighbors_start[facet_number]; + // Update statistics on unconnected triangle edges. + switch ((neighbors.neighbor[0] == -1) + (neighbors.neighbor[1] == -1) + (neighbors.neighbor[2] == -1)) { + case 0: // Facet has 3 neighbors + -- stl->stats.connected_facets_3_edge; + -- stl->stats.connected_facets_2_edge; + -- stl->stats.connected_facets_1_edge; + break; + case 1: // Facet has 2 neighbors + -- stl->stats.connected_facets_2_edge; + -- stl->stats.connected_facets_1_edge; + break; + case 2: // Facet has 1 neighbor + -- stl->stats.connected_facets_1_edge; + case 3: // Facet has 0 neighbors + break; + default: + assert(false); + } - // remove degenerate facets - for (int i = 0; i < stl->stats.number_of_facets; ++ i) { - if(stl->facet_start[i].vertex[0] == stl->facet_start[i].vertex[1] || - stl->facet_start[i].vertex[0] == stl->facet_start[i].vertex[2] || - stl->facet_start[i].vertex[1] == stl->facet_start[i].vertex[2]) { - stl_remove_degenerate(stl, i); - i--; - } - } + if (facet_number < -- stl->stats.number_of_facets) { + // Removing a face, which was not the last one. + // Copy the face and neighborship from the last face to facet_number. + stl->facet_start[facet_number] = stl->facet_start[stl->stats.number_of_facets]; + neighbors = stl->neighbors_start[stl->stats.number_of_facets]; + // Update neighborship of faces, which used to point to the last face, now moved to facet_number. + for (int i = 0; i < 3; ++ i) + if (neighbors.neighbor[i] != -1) { + int &other_face_idx = stl->neighbors_start[neighbors.neighbor[i]].neighbor[(neighbors.which_vertex_not[i] + 1) % 3]; + if (other_face_idx != stl->stats.number_of_facets) { + BOOST_LOG_TRIVIAL(info) << "in remove_facet: neighbor = " << other_face_idx << " numfacets = " << stl->stats.number_of_facets << " this is wrong"; + return; + } + other_face_idx = facet_number; + } + } - if(stl->stats.connected_facets_1_edge < stl->stats.number_of_facets) { - // remove completely unconnected facets - for (int i = 0; i < stl->stats.number_of_facets; i++) { - if (stl->neighbors_start[i].neighbor[0] == -1 && - stl->neighbors_start[i].neighbor[1] == -1 && - stl->neighbors_start[i].neighbor[2] == -1) { - // This facet is completely unconnected. Remove it. - stl_remove_facet(stl, i); - -- i; - } - } - } + stl->facet_start.pop_back(); + stl->neighbors_start.pop_back(); + }; + + auto remove_degenerate = [stl, remove_facet](int facet) + { + // Update statistics on face connectivity. + auto stl_update_connects_remove_1 = [stl](int facet_num) { + //FIXME when decreasing 3_edge, should I increase 2_edge etc? + switch ((stl->neighbors_start[facet_num].neighbor[0] == -1) + (stl->neighbors_start[facet_num].neighbor[1] == -1) + (stl->neighbors_start[facet_num].neighbor[2] == -1)) { + case 0: // Facet has 3 neighbors + -- stl->stats.connected_facets_3_edge; break; + case 1: // Facet has 2 neighbors + -- stl->stats.connected_facets_2_edge; break; + case 2: // Facet has 1 neighbor + -- stl->stats.connected_facets_1_edge; break; + case 3: // Facet has 0 neighbors + break; + default: + assert(false); + } + }; + + int edge_to_collapse = 0; + if (stl->facet_start[facet].vertex[0] == stl->facet_start[facet].vertex[1]) { + if (stl->facet_start[facet].vertex[1] == stl->facet_start[facet].vertex[2]) { + // All 3 vertices are equal. Collapse the edge with no neighbor if it exists. + const int *nbr = stl->neighbors_start[facet].neighbor; + edge_to_collapse = (nbr[0] == -1) ? 0 : (nbr[1] == -1) ? 1 : 2; + } else { + edge_to_collapse = 0; + } + } else if (stl->facet_start[facet].vertex[1] == stl->facet_start[facet].vertex[2]) { + edge_to_collapse = 1; + } else if (stl->facet_start[facet].vertex[2] == stl->facet_start[facet].vertex[0]) { + edge_to_collapse = 2; + } else { + // No degenerate. Function shouldn't have been called. + return; + } + + int edge[3] = { (edge_to_collapse + 1) % 3, (edge_to_collapse + 2) % 3, edge_to_collapse }; + int neighbor[] = { + stl->neighbors_start[facet].neighbor[edge[0]], + stl->neighbors_start[facet].neighbor[edge[1]], + stl->neighbors_start[facet].neighbor[edge[2]] + }; + int vnot[] = { + stl->neighbors_start[facet].which_vertex_not[edge[0]], + stl->neighbors_start[facet].which_vertex_not[edge[1]], + stl->neighbors_start[facet].which_vertex_not[edge[2]] + }; + // Update statistics on edge connectivity. + if (neighbor[0] == -1) + stl_update_connects_remove_1(neighbor[1]); + if (neighbor[1] == -1) + stl_update_connects_remove_1(neighbor[0]); + + if (neighbor[0] >= 0) { + if (neighbor[1] >= 0) { + // Adjust the "flip" flag for the which_vertex_not values. + if (vnot[0] > 2) { + if (vnot[1] > 2) { + // The face to be removed has its normal flipped compared to the left & right neighbors, therefore after removing this face + // the two remaining neighbors will be oriented correctly. + vnot[0] -= 3; + vnot[1] -= 3; + } else + // One neighbor has its normal inverted compared to the face to be removed, the other is oriented equally. + // After removal, the two neighbors will have their normals flipped. + vnot[1] += 3; + } else if (vnot[1] > 2) + // One neighbor has its normal inverted compared to the face to be removed, the other is oriented equally. + // After removal, the two neighbors will have their normals flipped. + vnot[0] += 3; + } + stl->neighbors_start[neighbor[0]].neighbor[(vnot[0] + 1) % 3] = (neighbor[0] == neighbor[1]) ? -1 : neighbor[1]; + stl->neighbors_start[neighbor[0]].which_vertex_not[(vnot[0] + 1) % 3] = vnot[1]; + } + if (neighbor[1] >= 0) { + stl->neighbors_start[neighbor[1]].neighbor[(vnot[1] + 1) % 3] = (neighbor[0] == neighbor[1]) ? -1 : neighbor[0]; + stl->neighbors_start[neighbor[1]].which_vertex_not[(vnot[1] + 1) % 3] = vnot[0]; + } + if (neighbor[2] >= 0) { + stl_update_connects_remove_1(neighbor[2]); + stl->neighbors_start[neighbor[2]].neighbor[(vnot[2] + 1) % 3] = -1; + } + + remove_facet(facet); + }; + + // remove degenerate facets + for (uint32_t i = 0; i < stl->stats.number_of_facets;) + if (stl->facet_start[i].vertex[0] == stl->facet_start[i].vertex[1] || + stl->facet_start[i].vertex[0] == stl->facet_start[i].vertex[2] || + stl->facet_start[i].vertex[1] == stl->facet_start[i].vertex[2]) { + remove_degenerate(i); +// assert(stl_validate(stl)); + } else + ++ i; + + if (stl->stats.connected_facets_1_edge < (int)stl->stats.number_of_facets) { + // remove completely unconnected facets + for (uint32_t i = 0; i < stl->stats.number_of_facets;) + if (stl->neighbors_start[i].neighbor[0] == -1 && + stl->neighbors_start[i].neighbor[1] == -1 && + stl->neighbors_start[i].neighbor[2] == -1) { + // This facet is completely unconnected. Remove it. + remove_facet(i); + assert(stl_validate(stl)); + } else + ++ i; + } } -static void -stl_remove_degenerate(stl_file *stl, int facet) { - int edge1; - int edge2; - int edge3; - int neighbor1; - int neighbor2; - int neighbor3; - int vnot1; - int vnot2; - int vnot3; +void stl_fill_holes(stl_file *stl) +{ + // Insert all unconnected edges into hash list. + HashTableEdges hash_table(stl->stats.number_of_facets); + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { + stl_facet facet = stl->facet_start[i]; + for (int j = 0; j < 3; ++ j) { + if(stl->neighbors_start[i].neighbor[j] != -1) + continue; + HashEdge edge; + edge.facet_number = i; + edge.which_edge = j; + edge.load_exact(stl, &facet.vertex[j], &facet.vertex[(j + 1) % 3]); + hash_table.insert_edge_exact(stl, edge); + } + } - if (stl->error) return; + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { + stl_facet facet = stl->facet_start[i]; + int neighbors_initial[3] = { stl->neighbors_start[i].neighbor[0], stl->neighbors_start[i].neighbor[1], stl->neighbors_start[i].neighbor[2] }; + int first_facet = i; + for (int j = 0; j < 3; ++ j) { + if (stl->neighbors_start[i].neighbor[j] != -1) + continue; - if (stl->facet_start[facet].vertex[0] == stl->facet_start[facet].vertex[1] && - stl->facet_start[facet].vertex[1] == stl->facet_start[facet].vertex[2]) { - /* all 3 vertices are equal. Just remove the facet. I don't think*/ - /* this is really possible, but just in case... */ - printf("removing a facet in stl_remove_degenerate\n"); - stl_remove_facet(stl, facet); - return; - } + stl_facet new_facet; + new_facet.vertex[0] = facet.vertex[j]; + new_facet.vertex[1] = facet.vertex[(j + 1) % 3]; + bool direction = neighbors_initial[(j + 2) % 3] == -1; + int facet_num = i; + int vnot = (j + 2) % 3; - if (stl->facet_start[facet].vertex[0] == stl->facet_start[facet].vertex[1]) { - edge1 = 1; - edge2 = 2; - edge3 = 0; - } else if (stl->facet_start[facet].vertex[1] == stl->facet_start[facet].vertex[2]) { - edge1 = 0; - edge2 = 2; - edge3 = 1; - } else if (stl->facet_start[facet].vertex[2] == stl->facet_start[facet].vertex[0]) { - edge1 = 0; - edge2 = 1; - edge3 = 2; - } else { - /* No degenerate. Function shouldn't have been called. */ - return; - } - neighbor1 = stl->neighbors_start[facet].neighbor[edge1]; - neighbor2 = stl->neighbors_start[facet].neighbor[edge2]; + for (;;) { + int pivot_vertex = 0; + int next_edge = 0; + if (vnot > 2) { + if (direction) { + pivot_vertex = (vnot + 1) % 3; + next_edge = vnot % 3; + } else { + pivot_vertex = (vnot + 2) % 3; + next_edge = pivot_vertex; + } + direction = ! direction; + } else { + if(direction == 0) { + pivot_vertex = (vnot + 1) % 3; + next_edge = vnot; + } else { + pivot_vertex = (vnot + 2) % 3; + next_edge = pivot_vertex; + } + } - if(neighbor1 == -1) { - stl_update_connects_remove_1(stl, neighbor2); - } - if(neighbor2 == -1) { - stl_update_connects_remove_1(stl, neighbor1); - } + int next_facet = stl->neighbors_start[facet_num].neighbor[next_edge]; + if (next_facet == -1) { + new_facet.vertex[2] = stl->facet_start[facet_num].vertex[vnot % 3]; + stl_add_facet(stl, &new_facet); + for (int k = 0; k < 3; ++ k) { + HashEdge edge; + edge.facet_number = stl->stats.number_of_facets - 1; + edge.which_edge = k; + edge.load_exact(stl, &new_facet.vertex[k], &new_facet.vertex[(k + 1) % 3]); + hash_table.insert_edge_exact(stl, edge); + } + break; + } + vnot = stl->neighbors_start[facet_num].which_vertex_not[next_edge]; + facet_num = next_facet; - neighbor3 = stl->neighbors_start[facet].neighbor[edge3]; - vnot1 = stl->neighbors_start[facet].which_vertex_not[edge1]; - vnot2 = stl->neighbors_start[facet].which_vertex_not[edge2]; - vnot3 = stl->neighbors_start[facet].which_vertex_not[edge3]; - - if(neighbor1 >= 0){ - stl->neighbors_start[neighbor1].neighbor[(vnot1 + 1) % 3] = neighbor2; - stl->neighbors_start[neighbor1].which_vertex_not[(vnot1 + 1) % 3] = vnot2; - } - if(neighbor2 >= 0){ - stl->neighbors_start[neighbor2].neighbor[(vnot2 + 1) % 3] = neighbor1; - stl->neighbors_start[neighbor2].which_vertex_not[(vnot2 + 1) % 3] = vnot1; - } - - stl_remove_facet(stl, facet); - - if(neighbor3 >= 0) { - stl_update_connects_remove_1(stl, neighbor3); - stl->neighbors_start[neighbor3].neighbor[(vnot3 + 1) % 3] = -1; - } + if (facet_num == first_facet) { + // back to the beginning + BOOST_LOG_TRIVIAL(info) << "Back to the first facet filling holes: probably a mobius part. Try using a smaller tolerance or don't do a nearby check."; + return; + } + } + } + } } -void -stl_update_connects_remove_1(stl_file *stl, int facet_num) { - int j; - - if (stl->error) return; - /* Update list of connected edges */ - j = ((stl->neighbors_start[facet_num].neighbor[0] == -1) + - (stl->neighbors_start[facet_num].neighbor[1] == -1) + - (stl->neighbors_start[facet_num].neighbor[2] == -1)); - if(j == 0) { /* Facet has 3 neighbors */ - stl->stats.connected_facets_3_edge -= 1; - } else if(j == 1) { /* Facet has 2 neighbors */ - stl->stats.connected_facets_2_edge -= 1; - } else if(j == 2) { /* Facet has 1 neighbor */ - stl->stats.connected_facets_1_edge -= 1; - } -} - -void -stl_fill_holes(stl_file *stl) { - stl_facet facet; - stl_facet new_facet; - int neighbors_initial[3]; - stl_hash_edge edge; - int first_facet; - int direction; - int facet_num; - int vnot; - int next_edge; - int pivot_vertex; - int next_facet; - int i; - int j; - int k; - - if (stl->error) return; - - /* Insert all unconnected edges into hash list */ - stl_initialize_facet_check_nearby(stl); - for(i = 0; i < stl->stats.number_of_facets; i++) { - facet = stl->facet_start[i]; - for(j = 0; j < 3; j++) { - if(stl->neighbors_start[i].neighbor[j] != -1) continue; - edge.facet_number = i; - edge.which_edge = j; - stl_load_edge_exact(stl, &edge, &facet.vertex[j], - &facet.vertex[(j + 1) % 3]); - - insert_hash_edge(stl, edge, stl_record_neighbors); - } - } - - for(i = 0; i < stl->stats.number_of_facets; i++) { - facet = stl->facet_start[i]; - neighbors_initial[0] = stl->neighbors_start[i].neighbor[0]; - neighbors_initial[1] = stl->neighbors_start[i].neighbor[1]; - neighbors_initial[2] = stl->neighbors_start[i].neighbor[2]; - first_facet = i; - for(j = 0; j < 3; j++) { - if(stl->neighbors_start[i].neighbor[j] != -1) continue; - - new_facet.vertex[0] = facet.vertex[j]; - new_facet.vertex[1] = facet.vertex[(j + 1) % 3]; - if(neighbors_initial[(j + 2) % 3] == -1) { - direction = 1; - } else { - direction = 0; - } - - facet_num = i; - vnot = (j + 2) % 3; - - for(;;) { - if(vnot > 2) { - if(direction == 0) { - pivot_vertex = (vnot + 2) % 3; - next_edge = pivot_vertex; - direction = 1; - } else { - pivot_vertex = (vnot + 1) % 3; - next_edge = vnot % 3; - direction = 0; - } - } else { - if(direction == 0) { - pivot_vertex = (vnot + 1) % 3; - next_edge = vnot; - } else { - pivot_vertex = (vnot + 2) % 3; - next_edge = pivot_vertex; - } - } - next_facet = stl->neighbors_start[facet_num].neighbor[next_edge]; - - if(next_facet == -1) { - new_facet.vertex[2] = stl->facet_start[facet_num]. - vertex[vnot % 3]; - stl_add_facet(stl, &new_facet); - for(k = 0; k < 3; k++) { - edge.facet_number = stl->stats.number_of_facets - 1; - edge.which_edge = k; - stl_load_edge_exact(stl, &edge, &new_facet.vertex[k], - &new_facet.vertex[(k + 1) % 3]); - - insert_hash_edge(stl, edge, stl_record_neighbors); - } - break; - } else { - vnot = stl->neighbors_start[facet_num]. - which_vertex_not[next_edge]; - facet_num = next_facet; - } - - if(facet_num == first_facet) { - /* back to the beginning */ - printf("\ -Back to the first facet filling holes: probably a mobius part.\n\ -Try using a smaller tolerance or don't do a nearby check\n"); - return; - } - } - } - } -} - -void -stl_add_facet(stl_file *stl, stl_facet *new_facet) { - if (stl->error) return; - - stl->stats.facets_added += 1; - if(stl->stats.facets_malloced < stl->stats.number_of_facets + 1) { - stl->facet_start = (stl_facet*)realloc(stl->facet_start, - (sizeof(stl_facet) * (stl->stats.facets_malloced + 256))); - if(stl->facet_start == NULL) perror("stl_add_facet"); - stl->neighbors_start = (stl_neighbors*)realloc(stl->neighbors_start, - (sizeof(stl_neighbors) * (stl->stats.facets_malloced + 256))); - if(stl->neighbors_start == NULL) perror("stl_add_facet"); - stl->stats.facets_malloced += 256; - } - stl->facet_start[stl->stats.number_of_facets] = *new_facet; - - /* note that the normal vector is not set here, just initialized to 0 */ - stl->facet_start[stl->stats.number_of_facets].normal = stl_normal::Zero(); - - stl->neighbors_start[stl->stats.number_of_facets].neighbor[0] = -1; - stl->neighbors_start[stl->stats.number_of_facets].neighbor[1] = -1; - stl->neighbors_start[stl->stats.number_of_facets].neighbor[2] = -1; - stl->stats.number_of_facets += 1; +void stl_add_facet(stl_file *stl, const stl_facet *new_facet) +{ + assert(stl->facet_start.size() == stl->stats.number_of_facets); + assert(stl->neighbors_start.size() == stl->stats.number_of_facets); + stl->facet_start.emplace_back(*new_facet); + // note that the normal vector is not set here, just initialized to 0. + stl->facet_start[stl->stats.number_of_facets].normal = stl_normal::Zero(); + stl->neighbors_start.emplace_back(); + ++ stl->stats.facets_added; + ++ stl->stats.number_of_facets; } diff --git a/src/admesh/normals.cpp b/src/admesh/normals.cpp index ecf08b59c9..16bb3daab5 100644 --- a/src/admesh/normals.cpp +++ b/src/admesh/normals.cpp @@ -25,271 +25,214 @@ #include #include +// Boost pool: Don't use mutexes to synchronize memory allocation. +#define BOOST_POOL_NO_MT +#include + #include "stl.h" -static int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag); +static void reverse_facet(stl_file *stl, int facet_num) +{ + ++ stl->stats.facets_reversed; -static void -stl_reverse_facet(stl_file *stl, int facet_num) { - stl_vertex tmp_vertex; - /* int tmp_neighbor;*/ - int neighbor[3]; - int vnot[3]; + int neighbor[3] = { stl->neighbors_start[facet_num].neighbor[0], stl->neighbors_start[facet_num].neighbor[1], stl->neighbors_start[facet_num].neighbor[2] }; + int vnot[3] = { stl->neighbors_start[facet_num].which_vertex_not[0], stl->neighbors_start[facet_num].which_vertex_not[1], stl->neighbors_start[facet_num].which_vertex_not[2] }; - stl->stats.facets_reversed += 1; + // reverse the facet + stl_vertex tmp_vertex = stl->facet_start[facet_num].vertex[0]; + stl->facet_start[facet_num].vertex[0] = stl->facet_start[facet_num].vertex[1]; + stl->facet_start[facet_num].vertex[1] = tmp_vertex; - neighbor[0] = stl->neighbors_start[facet_num].neighbor[0]; - neighbor[1] = stl->neighbors_start[facet_num].neighbor[1]; - neighbor[2] = stl->neighbors_start[facet_num].neighbor[2]; - vnot[0] = stl->neighbors_start[facet_num].which_vertex_not[0]; - vnot[1] = stl->neighbors_start[facet_num].which_vertex_not[1]; - vnot[2] = stl->neighbors_start[facet_num].which_vertex_not[2]; + // fix the vnots of the neighboring facets + if (neighbor[0] != -1) + stl->neighbors_start[neighbor[0]].which_vertex_not[(vnot[0] + 1) % 3] = (stl->neighbors_start[neighbor[0]].which_vertex_not[(vnot[0] + 1) % 3] + 3) % 6; + if (neighbor[1] != -1) + stl->neighbors_start[neighbor[1]].which_vertex_not[(vnot[1] + 1) % 3] = (stl->neighbors_start[neighbor[1]].which_vertex_not[(vnot[1] + 1) % 3] + 4) % 6; + if (neighbor[2] != -1) + stl->neighbors_start[neighbor[2]].which_vertex_not[(vnot[2] + 1) % 3] = (stl->neighbors_start[neighbor[2]].which_vertex_not[(vnot[2] + 1) % 3] + 2) % 6; - /* reverse the facet */ - tmp_vertex = stl->facet_start[facet_num].vertex[0]; - stl->facet_start[facet_num].vertex[0] = - stl->facet_start[facet_num].vertex[1]; - stl->facet_start[facet_num].vertex[1] = tmp_vertex; + // swap the neighbors of the facet that is being reversed + stl->neighbors_start[facet_num].neighbor[1] = neighbor[2]; + stl->neighbors_start[facet_num].neighbor[2] = neighbor[1]; - /* fix the vnots of the neighboring facets */ - if(neighbor[0] != -1) - stl->neighbors_start[neighbor[0]].which_vertex_not[(vnot[0] + 1) % 3] = - (stl->neighbors_start[neighbor[0]]. - which_vertex_not[(vnot[0] + 1) % 3] + 3) % 6; - if(neighbor[1] != -1) - stl->neighbors_start[neighbor[1]].which_vertex_not[(vnot[1] + 1) % 3] = - (stl->neighbors_start[neighbor[1]]. - which_vertex_not[(vnot[1] + 1) % 3] + 4) % 6; - if(neighbor[2] != -1) - stl->neighbors_start[neighbor[2]].which_vertex_not[(vnot[2] + 1) % 3] = - (stl->neighbors_start[neighbor[2]]. - which_vertex_not[(vnot[2] + 1) % 3] + 2) % 6; + // swap the vnots of the facet that is being reversed + stl->neighbors_start[facet_num].which_vertex_not[1] = vnot[2]; + stl->neighbors_start[facet_num].which_vertex_not[2] = vnot[1]; - /* swap the neighbors of the facet that is being reversed */ - stl->neighbors_start[facet_num].neighbor[1] = neighbor[2]; - stl->neighbors_start[facet_num].neighbor[2] = neighbor[1]; - - /* swap the vnots of the facet that is being reversed */ - stl->neighbors_start[facet_num].which_vertex_not[1] = vnot[2]; - stl->neighbors_start[facet_num].which_vertex_not[2] = vnot[1]; - - /* reverse the values of the vnots of the facet that is being reversed */ - stl->neighbors_start[facet_num].which_vertex_not[0] = - (stl->neighbors_start[facet_num].which_vertex_not[0] + 3) % 6; - stl->neighbors_start[facet_num].which_vertex_not[1] = - (stl->neighbors_start[facet_num].which_vertex_not[1] + 3) % 6; - stl->neighbors_start[facet_num].which_vertex_not[2] = - (stl->neighbors_start[facet_num].which_vertex_not[2] + 3) % 6; + // reverse the values of the vnots of the facet that is being reversed + stl->neighbors_start[facet_num].which_vertex_not[0] = (stl->neighbors_start[facet_num].which_vertex_not[0] + 3) % 6; + stl->neighbors_start[facet_num].which_vertex_not[1] = (stl->neighbors_start[facet_num].which_vertex_not[1] + 3) % 6; + stl->neighbors_start[facet_num].which_vertex_not[2] = (stl->neighbors_start[facet_num].which_vertex_not[2] + 3) % 6; } -void -stl_fix_normal_directions(stl_file *stl) { - char *norm_sw; - /* int edge_num;*/ - /* int vnot;*/ - int checked = 0; - int facet_num; - /* int next_facet;*/ - int i; - int j; - struct stl_normal { - int facet_num; - struct stl_normal *next; - }; - struct stl_normal *head; - struct stl_normal *tail; - struct stl_normal *newn; - struct stl_normal *temp; +// Returns true if the normal was flipped. +static bool check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag) +{ + stl_facet *facet = &stl->facet_start[facet_num]; - int* reversed_ids; - int reversed_count = 0; - int id; - int force_exit = 0; + stl_normal normal; + stl_calculate_normal(normal, facet); + stl_normalize_vector(normal); + stl_normal normal_dif = (normal - facet->normal).cwiseAbs(); - if (stl->error) return; + const float eps = 0.001f; + if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) { + // Normal is within tolerance. It is not really necessary to change the values here, but just for consistency, I will. + facet->normal = normal; + return false; + } - // this may happen for malformed models, see: https://github.com/prusa3d/PrusaSlicer/issues/2209 - if (stl->stats.number_of_facets == 0) return; + stl_normal test_norm = facet->normal; + stl_normalize_vector(test_norm); + normal_dif = (normal - test_norm).cwiseAbs(); + if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) { + // The normal is not within tolerance, but direction is OK. + if (normal_fix_flag) { + facet->normal = normal; + ++ stl->stats.normals_fixed; + } + return false; + } - /* Initialize linked list. */ - head = (struct stl_normal*)malloc(sizeof(struct stl_normal)); - if(head == NULL) perror("stl_fix_normal_directions"); - tail = (struct stl_normal*)malloc(sizeof(struct stl_normal)); - if(tail == NULL) perror("stl_fix_normal_directions"); - head->next = tail; - tail->next = tail; - - /* Initialize list that keeps track of already fixed facets. */ - norm_sw = (char*)calloc(stl->stats.number_of_facets, sizeof(char)); - if(norm_sw == NULL) perror("stl_fix_normal_directions"); - - /* Initialize list that keeps track of reversed facets. */ - reversed_ids = (int*)calloc(stl->stats.number_of_facets, sizeof(int)); - if (reversed_ids == NULL) perror("stl_fix_normal_directions reversed_ids"); - - facet_num = 0; - /* If normal vector is not within tolerance and backwards: - Arbitrarily starts at face 0. If this one is wrong, we're screwed. Thankfully, the chances - of it being wrong randomly are low if most of the triangles are right: */ - if (stl_check_normal_vector(stl, 0, 0) == 2) { - stl_reverse_facet(stl, 0); - reversed_ids[reversed_count++] = 0; - } - - /* Say that we've fixed this facet: */ - norm_sw[facet_num] = 1; - checked++; - - for(;;) { - /* Add neighbors_to_list. - Add unconnected neighbors to the list:a */ - for(j = 0; j < 3; j++) { - /* Reverse the neighboring facets if necessary. */ - if(stl->neighbors_start[facet_num].which_vertex_not[j] > 2) { - /* If the facet has a neighbor that is -1, it means that edge isn't shared by another facet */ - if(stl->neighbors_start[facet_num].neighbor[j] != -1) { - if (norm_sw[stl->neighbors_start[facet_num].neighbor[j]] == 1) { - /* trying to modify a facet already marked as fixed, revert all changes made until now and exit (fixes: #716, #574, #413, #269, #262, #259, #230, #228, #206) */ - for (id = reversed_count - 1; id >= 0; --id) { - stl_reverse_facet(stl, reversed_ids[id]); - } - force_exit = 1; - break; - } else { - stl_reverse_facet(stl, stl->neighbors_start[facet_num].neighbor[j]); - reversed_ids[reversed_count++] = stl->neighbors_start[facet_num].neighbor[j]; - } - } - } - /* If this edge of the facet is connected: */ - if(stl->neighbors_start[facet_num].neighbor[j] != -1) { - /* If we haven't fixed this facet yet, add it to the list: */ - if(norm_sw[stl->neighbors_start[facet_num].neighbor[j]] != 1) { - /* Add node to beginning of list. */ - newn = (struct stl_normal*)malloc(sizeof(struct stl_normal)); - if(newn == NULL) perror("stl_fix_normal_directions"); - newn->facet_num = stl->neighbors_start[facet_num].neighbor[j]; - newn->next = head->next; - head->next = newn; - } - } - } - - /* an error occourred, quit the for loop and exit */ - if (force_exit) break; - - /* Get next facet to fix from top of list. */ - if(head->next != tail) { - facet_num = head->next->facet_num; - if(norm_sw[facet_num] != 1) { /* If facet is in list mutiple times */ - norm_sw[facet_num] = 1; /* Record this one as being fixed. */ - checked++; - } - temp = head->next; /* Delete this facet from the list. */ - head->next = head->next->next; - free(temp); - } else { /* if we ran out of facets to fix: */ - /* All of the facets in this part have been fixed. */ - stl->stats.number_of_parts += 1; - if(checked >= stl->stats.number_of_facets) { - /* All of the facets have been checked. Bail out. */ - break; - } else { - /* There is another part here. Find it and continue. */ - for(i = 0; i < stl->stats.number_of_facets; i++) { - if(norm_sw[i] == 0) { - /* This is the first facet of the next part. */ - facet_num = i; - if(stl_check_normal_vector(stl, i, 0) == 2) { - stl_reverse_facet(stl, i); - reversed_ids[reversed_count++] = i; - } - - norm_sw[facet_num] = 1; - checked++; - break; - } - } - } - } - } - free(head); - free(tail); - free(reversed_ids); - free(norm_sw); + test_norm *= -1.f; + normal_dif = (normal - test_norm).cwiseAbs(); + if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) { + // The normal is not within tolerance and backwards. + if (normal_fix_flag) { + facet->normal = normal; + ++ stl->stats.normals_fixed; + } + return true; + } + if (normal_fix_flag) { + facet->normal = normal; + ++ stl->stats.normals_fixed; + } + // Status is unknown. + return false; } -static int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag) { - /* Returns 0 if the normal is within tolerance */ - /* Returns 1 if the normal is not within tolerance, but direction is OK */ - /* Returns 2 if the normal is not within tolerance and backwards */ - /* Returns 4 if the status is unknown. */ +void stl_fix_normal_directions(stl_file *stl) +{ + // This may happen for malformed models, see: https://github.com/prusa3d/PrusaSlicer/issues/2209 + if (stl->stats.number_of_facets == 0) + return; - stl_facet *facet; + struct stl_normal { + int facet_num; + stl_normal *next; + }; - facet = &stl->facet_start[facet_num]; + // Initialize linked list. + boost::object_pool pool; + stl_normal *head = pool.construct(); + stl_normal *tail = pool.construct(); + head->next = tail; + tail->next = tail; - stl_normal normal; - stl_calculate_normal(normal, facet); - stl_normalize_vector(normal); - stl_normal normal_dif = (normal - facet->normal).cwiseAbs(); + // Initialize list that keeps track of already fixed facets. + std::vector norm_sw(stl->stats.number_of_facets, 0); + // Initialize list that keeps track of reversed facets. + std::vector reversed_ids(stl->stats.number_of_facets, 0); - const float eps = 0.001f; - if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) { - /* It is not really necessary to change the values here */ - /* but just for consistency, I will. */ - facet->normal = normal; - return 0; - } + int facet_num = 0; + int reversed_count = 0; + // If normal vector is not within tolerance and backwards: + // Arbitrarily starts at face 0. If this one is wrong, we're screwed. Thankfully, the chances + // of it being wrong randomly are low if most of the triangles are right: + if (check_normal_vector(stl, 0, 0)) { + reverse_facet(stl, 0); + reversed_ids[reversed_count ++] = 0; + } - stl_normal test_norm = facet->normal; - stl_normalize_vector(test_norm); - normal_dif = (normal - test_norm).cwiseAbs(); - if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) { - if(normal_fix_flag) { - facet->normal = normal; - stl->stats.normals_fixed += 1; - } - return 1; - } + // Say that we've fixed this facet: + norm_sw[facet_num] = 1; + int checked = 1; - test_norm *= -1.f; - normal_dif = (normal - test_norm).cwiseAbs(); - if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) { - // Facet is backwards. - if(normal_fix_flag) { - facet->normal = normal; - stl->stats.normals_fixed += 1; - } - return 2; - } - if(normal_fix_flag) { - facet->normal = normal; - stl->stats.normals_fixed += 1; - } - return 4; + for (;;) { + // Add neighbors_to_list. Add unconnected neighbors to the list. + bool force_exit = false; + for (int j = 0; j < 3; ++ j) { + // Reverse the neighboring facets if necessary. + if (stl->neighbors_start[facet_num].which_vertex_not[j] > 2) { + // If the facet has a neighbor that is -1, it means that edge isn't shared by another facet + if (stl->neighbors_start[facet_num].neighbor[j] != -1) { + if (norm_sw[stl->neighbors_start[facet_num].neighbor[j]] == 1) { + // trying to modify a facet already marked as fixed, revert all changes made until now and exit (fixes: #716, #574, #413, #269, #262, #259, #230, #228, #206) + for (int id = reversed_count - 1; id >= 0; -- id) + reverse_facet(stl, reversed_ids[id]); + force_exit = true; + break; + } + reverse_facet(stl, stl->neighbors_start[facet_num].neighbor[j]); + reversed_ids[reversed_count ++] = stl->neighbors_start[facet_num].neighbor[j]; + } + } + // If this edge of the facet is connected: + if (stl->neighbors_start[facet_num].neighbor[j] != -1) { + // If we haven't fixed this facet yet, add it to the list: + if (norm_sw[stl->neighbors_start[facet_num].neighbor[j]] != 1) { + // Add node to beginning of list. + stl_normal *newn = pool.construct(); + newn->facet_num = stl->neighbors_start[facet_num].neighbor[j]; + newn->next = head->next; + head->next = newn; + } + } + } + + // an error occourred, quit the for loop and exit + if (force_exit) + break; + + // Get next facet to fix from top of list. + if (head->next != tail) { + facet_num = head->next->facet_num; + if (norm_sw[facet_num] != 1) { // If facet is in list mutiple times + norm_sw[facet_num] = 1; // Record this one as being fixed. + ++ checked; + } + stl_normal *temp = head->next; // Delete this facet from the list. + head->next = head->next->next; + // pool.destroy(temp); + } else { // If we ran out of facets to fix: All of the facets in this part have been fixed. + ++ stl->stats.number_of_parts; + if (checked >= stl->stats.number_of_facets) + // All of the facets have been checked. Bail out. + break; + // There is another part here. Find it and continue. + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) + if (norm_sw[i] == 0) { + // This is the first facet of the next part. + facet_num = i; + if (check_normal_vector(stl, i, 0)) { + reverse_facet(stl, i); + reversed_ids[reversed_count++] = i; + } + norm_sw[facet_num] = 1; + ++ checked; + break; + } + } + } + + // pool.destroy(head); + // pool.destroy(tail); } -void stl_fix_normal_values(stl_file *stl) { - int i; - - if (stl->error) return; - - for(i = 0; i < stl->stats.number_of_facets; i++) { - stl_check_normal_vector(stl, i, 1); - } +void stl_fix_normal_values(stl_file *stl) +{ + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) + check_normal_vector(stl, i, 1); } void stl_reverse_all_facets(stl_file *stl) { - if (stl->error) - return; - - stl_normal normal; - for(int i = 0; i < stl->stats.number_of_facets; i++) { - stl_reverse_facet(stl, i); - stl_calculate_normal(normal, &stl->facet_start[i]); - stl_normalize_vector(normal); - stl->facet_start[i].normal = normal; - } + stl_normal normal; + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { + reverse_facet(stl, i); + stl_calculate_normal(normal, &stl->facet_start[i]); + stl_normalize_vector(normal); + stl->facet_start[i].normal = normal; + } } diff --git a/src/admesh/shared.cpp b/src/admesh/shared.cpp index c8c17ccd59..902bbfc9b8 100644 --- a/src/admesh/shared.cpp +++ b/src/admesh/shared.cpp @@ -23,242 +23,237 @@ #include #include +#include + +#include #include #include "stl.h" -void -stl_invalidate_shared_vertices(stl_file *stl) { - if (stl->error) return; +void stl_generate_shared_vertices(stl_file *stl, indexed_triangle_set &its) +{ + // 3 indices to vertex per face + its.indices.assign(stl->stats.number_of_facets, stl_triangle_vertex_indices(-1, -1, -1)); + // Shared vertices (3D coordinates) + its.vertices.clear(); + its.vertices.reserve(stl->stats.number_of_facets / 2); - if (stl->v_indices != NULL) { - free(stl->v_indices); - stl->v_indices = NULL; - } - if (stl->v_shared != NULL) { - free(stl->v_shared); - stl->v_shared = NULL; - } + // A degenerate mesh may contain loops: Traversing a fan will end up in an endless loop + // while never reaching the starting face. To avoid these endless loops, traversed faces at each fan traversal + // are marked with a unique fan_traversal_stamp. + unsigned int fan_traversal_stamp = 0; + std::vector fan_traversal_facet_visited(stl->stats.number_of_facets, 0); + + for (uint32_t facet_idx = 0; facet_idx < stl->stats.number_of_facets; ++ facet_idx) { + for (int j = 0; j < 3; ++ j) { + if (its.indices[facet_idx][j] != -1) + // Shared vertex was already assigned. + continue; + // Create a new shared vertex. + its.vertices.emplace_back(stl->facet_start[facet_idx].vertex[j]); + // Traverse the fan around the j-th vertex of the i-th face, assign the newly created shared vertex index to all the neighboring triangles in the triangle fan. + int facet_in_fan_idx = facet_idx; + bool edge_direction = false; + bool traversal_reversed = false; + int vnot = (j + 2) % 3; + // Increase the + ++ fan_traversal_stamp; + for (;;) { + // Next edge on facet_in_fan_idx to be traversed. The edge is indexed by its starting vertex index. + int next_edge = 0; + // Vertex index in facet_in_fan_idx, which is being pivoted around, and which is being assigned a new shared vertex. + int pivot_vertex = 0; + if (vnot > 2) { + // The edge of facet_in_fan_idx opposite to vnot is equally oriented, therefore + // the neighboring facet is flipped. + if (! edge_direction) { + pivot_vertex = (vnot + 2) % 3; + next_edge = pivot_vertex; + } else { + pivot_vertex = (vnot + 1) % 3; + next_edge = vnot % 3; + } + edge_direction = ! edge_direction; + } else { + // The neighboring facet is correctly oriented. + if (! edge_direction) { + pivot_vertex = (vnot + 1) % 3; + next_edge = vnot; + } else { + pivot_vertex = (vnot + 2) % 3; + next_edge = pivot_vertex; + } + } + its.indices[facet_in_fan_idx][pivot_vertex] = its.vertices.size() - 1; + fan_traversal_facet_visited[facet_in_fan_idx] = fan_traversal_stamp; + + // next_edge is an index of the starting vertex of the edge, not an index of the opposite vertex to the edge! + int next_facet = stl->neighbors_start[facet_in_fan_idx].neighbor[next_edge]; + if (next_facet == -1) { + // No neighbor going in the current direction. + if (traversal_reversed) { + // Went to one limit, then turned back and reached the other limit. Quit the fan traversal. + break; + } else { + // Reached the first limit. Now try to reverse and traverse up to the other limit. + edge_direction = true; + vnot = (j + 1) % 3; + traversal_reversed = true; + facet_in_fan_idx = facet_idx; + } + } else if (next_facet == facet_idx) { + // Traversed a closed fan all around. +// assert(! traversal_reversed); + break; + } else if (next_facet >= (int)stl->stats.number_of_facets) { + // The mesh is not valid! + // assert(false); + break; + } else if (fan_traversal_facet_visited[next_facet] == fan_traversal_stamp) { + // Traversed a closed fan all around, but did not reach the starting face. + // This indicates an invalid geometry (non-manifold). + //assert(false); + break; + } else { + // Continue traversal. + // next_edge is an index of the starting vertex of the edge, not an index of the opposite vertex to the edge! + vnot = stl->neighbors_start[facet_in_fan_idx].which_vertex_not[next_edge]; + facet_in_fan_idx = next_facet; + } + } + } + } } -void -stl_generate_shared_vertices(stl_file *stl) { - int i; - int j; - int first_facet; - int direction; - int facet_num; - int vnot; - int next_edge; - int pivot_vertex; - int next_facet; - int reversed; +bool its_write_off(const indexed_triangle_set &its, const char *file) +{ + /* Open the file */ + FILE *fp = boost::nowide::fopen(file, "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) << "stl_write_ascii: Couldn't open " << file << " for writing"; + return false; + } - if (stl->error) return; + fprintf(fp, "OFF\n"); + fprintf(fp, "%d %d 0\n", (int)its.vertices.size(), (int)its.indices.size()); + for (int i = 0; i < its.vertices.size(); ++ i) + fprintf(fp, "\t%f %f %f\n", its.vertices[i](0), its.vertices[i](1), its.vertices[i](2)); + for (uint32_t i = 0; i < its.indices.size(); ++ i) + fprintf(fp, "\t3 %d %d %d\n", its.indices[i][0], its.indices[i][1], its.indices[i][2]); + fclose(fp); + return true; +} - /* make sure this function is idempotent and does not leak memory */ - stl_invalidate_shared_vertices(stl); +bool its_write_vrml(const indexed_triangle_set &its, const char *file) +{ + /* Open the file */ + FILE *fp = boost::nowide::fopen(file, "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) << "stl_write_vrml: Couldn't open " << file << " for writing"; + return false; + } - stl->v_indices = (v_indices_struct*) - calloc(stl->stats.number_of_facets, sizeof(v_indices_struct)); - if(stl->v_indices == NULL) perror("stl_generate_shared_vertices"); - stl->v_shared = (stl_vertex*) - calloc((stl->stats.number_of_facets / 2), sizeof(stl_vertex)); - if(stl->v_shared == NULL) perror("stl_generate_shared_vertices"); - stl->stats.shared_malloced = stl->stats.number_of_facets / 2; - stl->stats.shared_vertices = 0; + fprintf(fp, "#VRML V1.0 ascii\n\n"); + fprintf(fp, "Separator {\n"); + fprintf(fp, "\tDEF STLShape ShapeHints {\n"); + fprintf(fp, "\t\tvertexOrdering COUNTERCLOCKWISE\n"); + fprintf(fp, "\t\tfaceType CONVEX\n"); + fprintf(fp, "\t\tshapeType SOLID\n"); + fprintf(fp, "\t\tcreaseAngle 0.0\n"); + fprintf(fp, "\t}\n"); + fprintf(fp, "\tDEF STLModel Separator {\n"); + fprintf(fp, "\t\tDEF STLColor Material {\n"); + fprintf(fp, "\t\t\temissiveColor 0.700000 0.700000 0.000000\n"); + fprintf(fp, "\t\t}\n"); + fprintf(fp, "\t\tDEF STLVertices Coordinate3 {\n"); + fprintf(fp, "\t\t\tpoint [\n"); - for(i = 0; i < stl->stats.number_of_facets; i++) { - stl->v_indices[i].vertex[0] = -1; - stl->v_indices[i].vertex[1] = -1; - stl->v_indices[i].vertex[2] = -1; - } + int i = 0; + for (; i + 1 < its.vertices.size(); ++ i) + fprintf(fp, "\t\t\t\t%f %f %f,\n", its.vertices[i](0), its.vertices[i](1), its.vertices[i](2)); + fprintf(fp, "\t\t\t\t%f %f %f]\n", its.vertices[i](0), its.vertices[i](1), its.vertices[i](2)); + fprintf(fp, "\t\t}\n"); + fprintf(fp, "\t\tDEF STLTriangles IndexedFaceSet {\n"); + fprintf(fp, "\t\t\tcoordIndex [\n"); + + for (size_t i = 0; i + 1 < its.indices.size(); ++ i) + fprintf(fp, "\t\t\t\t%d, %d, %d, -1,\n", its.indices[i][0], its.indices[i][1], its.indices[i][2]); + fprintf(fp, "\t\t\t\t%d, %d, %d, -1]\n", its.indices[i][0], its.indices[i][1], its.indices[i][2]); + fprintf(fp, "\t\t}\n"); + fprintf(fp, "\t}\n"); + fprintf(fp, "}\n"); + fclose(fp); + return true; +} + +bool its_write_obj(const indexed_triangle_set &its, const char *file) +{ + + FILE *fp = boost::nowide::fopen(file, "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) << "stl_write_obj: Couldn't open " << file << " for writing"; + return false; + } + + for (size_t i = 0; i < its.vertices.size(); ++ i) + fprintf(fp, "v %f %f %f\n", its.vertices[i](0), its.vertices[i](1), its.vertices[i](2)); + for (size_t i = 0; i < its.indices.size(); ++ i) + fprintf(fp, "f %d %d %d\n", its.indices[i][0]+1, its.indices[i][1]+1, its.indices[i][2]+1); + fclose(fp); + return true; +} - for(i = 0; i < stl->stats.number_of_facets; i++) { - first_facet = i; - for(j = 0; j < 3; j++) { - if(stl->v_indices[i].vertex[j] != -1) { - continue; - } - if(stl->stats.shared_vertices == stl->stats.shared_malloced) { - stl->stats.shared_malloced += 1024; - stl->v_shared = (stl_vertex*)realloc(stl->v_shared, - stl->stats.shared_malloced * sizeof(stl_vertex)); - if(stl->v_shared == NULL) perror("stl_generate_shared_vertices"); - } +// Check validity of the mesh, assert on error. +bool stl_validate(const stl_file *stl, const indexed_triangle_set &its) +{ + assert(! stl->facet_start.empty()); + assert(stl->facet_start.size() == stl->stats.number_of_facets); + assert(stl->neighbors_start.size() == stl->stats.number_of_facets); + assert(stl->facet_start.size() == stl->neighbors_start.size()); + assert(! stl->neighbors_start.empty()); + assert((its.indices.empty()) == (its.vertices.empty())); + assert(stl->stats.number_of_facets > 0); + assert(its.vertices.empty() || its.indices.size() == stl->stats.number_of_facets); - stl->v_shared[stl->stats.shared_vertices] = - stl->facet_start[i].vertex[j]; - - direction = 0; - reversed = 0; - facet_num = i; - vnot = (j + 2) % 3; - - for(;;) { - if(vnot > 2) { - if(direction == 0) { - pivot_vertex = (vnot + 2) % 3; - next_edge = pivot_vertex; - direction = 1; - } else { - pivot_vertex = (vnot + 1) % 3; - next_edge = vnot % 3; - direction = 0; - } - } else { - if(direction == 0) { - pivot_vertex = (vnot + 1) % 3; - next_edge = vnot; - } else { - pivot_vertex = (vnot + 2) % 3; - next_edge = pivot_vertex; - } +#ifdef _DEBUG + // Verify validity of neighborship data. + for (int facet_idx = 0; facet_idx < (int)stl->stats.number_of_facets; ++ facet_idx) { + const stl_neighbors &nbr = stl->neighbors_start[facet_idx]; + const int *vertices = its.indices.empty() ? nullptr : its.indices[facet_idx].data(); + for (int nbr_idx = 0; nbr_idx < 3; ++ nbr_idx) { + int nbr_face = stl->neighbors_start[facet_idx].neighbor[nbr_idx]; + assert(nbr_face < (int)stl->stats.number_of_facets); + if (nbr_face != -1) { + int nbr_vnot = nbr.which_vertex_not[nbr_idx]; + assert(nbr_vnot >= 0 && nbr_vnot < 6); + // Neighbor of the neighbor is the original face. + assert(stl->neighbors_start[nbr_face].neighbor[(nbr_vnot + 1) % 3] == facet_idx); + int vnot_back = stl->neighbors_start[nbr_face].which_vertex_not[(nbr_vnot + 1) % 3]; + assert(vnot_back >= 0 && vnot_back < 6); + assert((nbr_vnot < 3) == (vnot_back < 3)); + assert(vnot_back % 3 == (nbr_idx + 2) % 3); + if (vertices != nullptr) { + // Has shared vertices. + if (nbr_vnot < 3) { + // Faces facet_idx and nbr_face share two vertices accross the common edge. Faces are correctly oriented. + assert((its.indices[nbr_face][(nbr_vnot + 1) % 3] == vertices[(nbr_idx + 1) % 3] && its.indices[nbr_face][(nbr_vnot + 2) % 3] == vertices[nbr_idx])); + } else { + // Faces facet_idx and nbr_face share two vertices accross the common edge. Faces are incorrectly oriented, one of them is flipped. + assert((its.indices[nbr_face][(nbr_vnot + 2) % 3] == vertices[(nbr_idx + 1) % 3] && its.indices[nbr_face][(nbr_vnot + 1) % 3] == vertices[nbr_idx])); + } + } + } } - stl->v_indices[facet_num].vertex[pivot_vertex] = - stl->stats.shared_vertices; - - next_facet = stl->neighbors_start[facet_num].neighbor[next_edge]; - if(next_facet == -1) { - if(reversed) { - break; - } else { - direction = 1; - vnot = (j + 1) % 3; - reversed = 1; - facet_num = first_facet; - } - } else if(next_facet != first_facet) { - vnot = stl->neighbors_start[facet_num]. - which_vertex_not[next_edge]; - facet_num = next_facet; - } else { - break; - } - } - stl->stats.shared_vertices += 1; } - } +#endif /* _DEBUG */ + + return true; } -void -stl_write_off(stl_file *stl, const char *file) { - int i; - FILE *fp; - char *error_msg; - - if (stl->error) return; - - /* Open the file */ - fp = boost::nowide::fopen(file, "w"); - if(fp == NULL) { - error_msg = (char*) - malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ - sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", - file); - perror(error_msg); - free(error_msg); - stl->error = 1; - return; - } - - fprintf(fp, "OFF\n"); - fprintf(fp, "%d %d 0\n", - stl->stats.shared_vertices, stl->stats.number_of_facets); - - for(i = 0; i < stl->stats.shared_vertices; i++) { - fprintf(fp, "\t%f %f %f\n", - stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); - } - for(i = 0; i < stl->stats.number_of_facets; i++) { - fprintf(fp, "\t3 %d %d %d\n", stl->v_indices[i].vertex[0], - stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[2]); - } - fclose(fp); -} - -void -stl_write_vrml(stl_file *stl, const char *file) { - int i; - FILE *fp; - char *error_msg; - - if (stl->error) return; - - /* Open the file */ - fp = boost::nowide::fopen(file, "w"); - if(fp == NULL) { - error_msg = (char*) - malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ - sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", - file); - perror(error_msg); - free(error_msg); - stl->error = 1; - return; - } - - fprintf(fp, "#VRML V1.0 ascii\n\n"); - fprintf(fp, "Separator {\n"); - fprintf(fp, "\tDEF STLShape ShapeHints {\n"); - fprintf(fp, "\t\tvertexOrdering COUNTERCLOCKWISE\n"); - fprintf(fp, "\t\tfaceType CONVEX\n"); - fprintf(fp, "\t\tshapeType SOLID\n"); - fprintf(fp, "\t\tcreaseAngle 0.0\n"); - fprintf(fp, "\t}\n"); - fprintf(fp, "\tDEF STLModel Separator {\n"); - fprintf(fp, "\t\tDEF STLColor Material {\n"); - fprintf(fp, "\t\t\temissiveColor 0.700000 0.700000 0.000000\n"); - fprintf(fp, "\t\t}\n"); - fprintf(fp, "\t\tDEF STLVertices Coordinate3 {\n"); - fprintf(fp, "\t\t\tpoint [\n"); - - for(i = 0; i < (stl->stats.shared_vertices - 1); i++) { - fprintf(fp, "\t\t\t\t%f %f %f,\n", - stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); - } - fprintf(fp, "\t\t\t\t%f %f %f]\n", - stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); - fprintf(fp, "\t\t}\n"); - fprintf(fp, "\t\tDEF STLTriangles IndexedFaceSet {\n"); - fprintf(fp, "\t\t\tcoordIndex [\n"); - - for(i = 0; i < (stl->stats.number_of_facets - 1); i++) { - fprintf(fp, "\t\t\t\t%d, %d, %d, -1,\n", stl->v_indices[i].vertex[0], - stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[2]); - } - fprintf(fp, "\t\t\t\t%d, %d, %d, -1]\n", stl->v_indices[i].vertex[0], - stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[2]); - fprintf(fp, "\t\t}\n"); - fprintf(fp, "\t}\n"); - fprintf(fp, "}\n"); - fclose(fp); -} - -void stl_write_obj (stl_file *stl, const char *file) { - int i; - FILE* fp; - - if (stl->error) return; - - /* Open the file */ - fp = boost::nowide::fopen(file, "w"); - if (fp == NULL) { - char* error_msg = (char*)malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ - sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", file); - perror(error_msg); - free(error_msg); - stl->error = 1; - return; - } - - for (i = 0; i < stl->stats.shared_vertices; i++) { - fprintf(fp, "v %f %f %f\n", stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); - } - for (i = 0; i < stl->stats.number_of_facets; i++) { - fprintf(fp, "f %d %d %d\n", stl->v_indices[i].vertex[0]+1, stl->v_indices[i].vertex[1]+1, stl->v_indices[i].vertex[2]+1); - } - - fclose(fp); +// Check validity of the mesh, assert on error. +bool stl_validate(const stl_file *stl) +{ + indexed_triangle_set its; + return stl_validate(stl, its); } diff --git a/src/admesh/stl.h b/src/admesh/stl.h index f867e197bd..9b1146f8da 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -27,6 +27,7 @@ #include #include +#include #include // Size of the binary STL header, free form. @@ -40,22 +41,23 @@ typedef Eigen::Matrix stl_vertex; typedef Eigen::Matrix stl_normal; +typedef Eigen::Matrix stl_triangle_vertex_indices; static_assert(sizeof(stl_vertex) == 12, "size of stl_vertex incorrect"); static_assert(sizeof(stl_normal) == 12, "size of stl_normal incorrect"); struct stl_facet { - stl_normal normal; - stl_vertex vertex[3]; - char extra[2]; + stl_normal normal; + stl_vertex vertex[3]; + char extra[2]; - stl_facet rotated(const Eigen::Quaternion &rot) { - stl_facet out; - out.normal = rot * this->normal; - out.vertex[0] = rot * this->vertex[0]; - out.vertex[1] = rot * this->vertex[1]; - out.vertex[2] = rot * this->vertex[2]; - return out; - } + stl_facet rotated(const Eigen::Quaternion &rot) const { + stl_facet out; + out.normal = rot * this->normal; + out.vertex[0] = rot * this->vertex[0]; + out.vertex[1] = rot * this->vertex[1]; + out.vertex[2] = rot * this->vertex[2]; + return out; + } }; #define SIZEOF_STL_FACET 50 @@ -67,104 +69,102 @@ static_assert(sizeof(stl_facet) >= SIZEOF_STL_FACET, "size of stl_facet incorrec typedef enum {binary, ascii, inmemory} stl_type; -typedef struct { - stl_vertex p1; - stl_vertex p2; - int facet_number; -} stl_edge; +struct stl_neighbors { + stl_neighbors() { reset(); } + void reset() { + neighbor[0] = -1; + neighbor[1] = -1; + neighbor[2] = -1; + which_vertex_not[0] = -1; + which_vertex_not[1] = -1; + which_vertex_not[2] = -1; + } + int num_neighbors_missing() const { return (this->neighbor[0] == -1) + (this->neighbor[1] == -1) + (this->neighbor[2] == -1); } + int num_neighbors() const { return 3 - this->num_neighbors_missing(); } -typedef struct stl_hash_edge { - // Key of a hash edge: sorted vertices of the edge. - uint32_t key[6]; - // Compare two keys. - bool operator==(const stl_hash_edge &rhs) { return memcmp(key, rhs.key, sizeof(key)) == 0; } - bool operator!=(const stl_hash_edge &rhs) { return ! (*this == rhs); } - int hash(int M) const { return ((key[0] / 11 + key[1] / 7 + key[2] / 3) ^ (key[3] / 11 + key[4] / 7 + key[5] / 3)) % M; } - // Index of a facet owning this edge. - int facet_number; - // Index of this edge inside the facet with an index of facet_number. - // If this edge is stored backwards, which_edge is increased by 3. - int which_edge; - struct stl_hash_edge *next; -} stl_hash_edge; + // Index of a neighbor facet. + int neighbor[3]; + // Index of an opposite vertex at the neighbor face. + char which_vertex_not[3]; +}; -typedef struct { - // Index of a neighbor facet. - int neighbor[3]; - // Index of an opposite vertex at the neighbor face. - char which_vertex_not[3]; -} stl_neighbors; +struct stl_stats { + stl_stats() { this->reset(); } + void reset() { memset(this, 0, sizeof(stl_stats)); this->volume = -1.0; } + char header[81]; + stl_type type; + uint32_t number_of_facets; + stl_vertex max; + stl_vertex min; + stl_vertex size; + float bounding_diameter; + float shortest_edge; + float volume; + int connected_edges; + int connected_facets_1_edge; + int connected_facets_2_edge; + int connected_facets_3_edge; + int facets_w_1_bad_edge; + int facets_w_2_bad_edge; + int facets_w_3_bad_edge; + int original_num_facets; + int edges_fixed; + int degenerate_facets; + int facets_removed; + int facets_added; + int facets_reversed; + int backwards_edges; + int normals_fixed; + int number_of_parts; +}; -typedef struct { - int vertex[3]; -} v_indices_struct; +struct stl_file { + stl_file() {} -typedef struct { - char header[81]; - stl_type type; - uint32_t number_of_facets; - stl_vertex max; - stl_vertex min; - stl_vertex size; - float bounding_diameter; - float shortest_edge; - float volume; - unsigned number_of_blocks; - int connected_edges; - int connected_facets_1_edge; - int connected_facets_2_edge; - int connected_facets_3_edge; - int facets_w_1_bad_edge; - int facets_w_2_bad_edge; - int facets_w_3_bad_edge; - int original_num_facets; - int edges_fixed; - int degenerate_facets; - int facets_removed; - int facets_added; - int facets_reversed; - int backwards_edges; - int normals_fixed; - int number_of_parts; - int malloced; - int freed; - int facets_malloced; - int collisions; - int shared_vertices; - int shared_malloced; -} stl_stats; + void clear() { + this->facet_start.clear(); + this->neighbors_start.clear(); + this->stats.reset(); + } -typedef struct { - FILE *fp; - stl_facet *facet_start; - stl_hash_edge **heads; - stl_hash_edge *tail; - int M; - stl_neighbors *neighbors_start; - v_indices_struct *v_indices; - stl_vertex *v_shared; - stl_stats stats; - char error; -} stl_file; + size_t memsize() const { + return sizeof(*this) + sizeof(stl_facet) * facet_start.size() + sizeof(stl_neighbors) * neighbors_start.size(); + } + std::vector facet_start; + std::vector neighbors_start; + // Statistics + stl_stats stats; +}; -extern void stl_open(stl_file *stl, const char *file); -extern void stl_close(stl_file *stl); +struct indexed_triangle_set +{ + indexed_triangle_set() {} + + void clear() { indices.clear(); vertices.clear(); } + + size_t memsize() const { + return sizeof(*this) + sizeof(stl_triangle_vertex_indices) * indices.size() + sizeof(stl_vertex) * vertices.size(); + } + + std::vector indices; + std::vector vertices; + //FIXME add normals once we get rid of the stl_file from TriangleMesh completely. + //std::vector normals +}; + +extern bool stl_open(stl_file *stl, const char *file); extern void stl_stats_out(stl_file *stl, FILE *file, char *input_file); -extern void stl_print_neighbors(stl_file *stl, char *file); -extern void stl_put_little_int(FILE *fp, int value_in); -extern void stl_put_little_float(FILE *fp, float value_in); -extern void stl_write_ascii(stl_file *stl, const char *file, const char *label); -extern void stl_write_binary(stl_file *stl, const char *file, const char *label); -extern void stl_write_binary_block(stl_file *stl, FILE *fp); +extern bool stl_print_neighbors(stl_file *stl, char *file); +extern bool stl_write_ascii(stl_file *stl, const char *file, const char *label); +extern bool stl_write_binary(stl_file *stl, const char *file, const char *label); extern void stl_check_facets_exact(stl_file *stl); extern void stl_check_facets_nearby(stl_file *stl, float tolerance); extern void stl_remove_unconnected_facets(stl_file *stl); extern void stl_write_vertex(stl_file *stl, int facet, int vertex); extern void stl_write_facet(stl_file *stl, char *label, int facet); -extern void stl_write_edge(stl_file *stl, char *label, stl_hash_edge edge); extern void stl_write_neighbor(stl_file *stl, int facet); -extern void stl_write_quad_object(stl_file *stl, char *file); +extern bool stl_write_quad_object(stl_file *stl, char *file); extern void stl_verify_neighbors(stl_file *stl); extern void stl_fill_holes(stl_file *stl); extern void stl_fix_normal_directions(stl_file *stl); @@ -186,36 +186,30 @@ extern void stl_get_size(stl_file *stl); template extern void stl_transform(stl_file *stl, T *trafo3x4) { - if (stl->error) - return; + for (uint32_t i_face = 0; i_face < stl->stats.number_of_facets; ++ i_face) { + stl_facet &face = stl->facet_start[i_face]; + for (int i_vertex = 0; i_vertex < 3; ++ i_vertex) { + stl_vertex &v_dst = face.vertex[i_vertex]; + stl_vertex v_src = v_dst; + v_dst(0) = T(trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2] * v_src(2) + trafo3x4[3]); + v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6] * v_src(2) + trafo3x4[7]); + v_dst(2) = T(trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2) + trafo3x4[11]); + } + stl_vertex &v_dst = face.normal; + stl_vertex v_src = v_dst; + v_dst(0) = T(trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2] * v_src(2)); + v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6] * v_src(2)); + v_dst(2) = T(trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2)); + } - for (uint32_t i_face = 0; i_face < stl->stats.number_of_facets; ++ i_face) { - stl_facet &face = stl->facet_start[i_face]; - for (int i_vertex = 0; i_vertex < 3; ++ i_vertex) { - stl_vertex &v_dst = face.vertex[i_vertex]; - stl_vertex v_src = v_dst; - v_dst(0) = T(trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2] * v_src(2) + trafo3x4[3]); - v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6] * v_src(2) + trafo3x4[7]); - v_dst(2) = T(trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2) + trafo3x4[11]); - } - stl_vertex &v_dst = face.normal; - stl_vertex v_src = v_dst; - v_dst(0) = T(trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2] * v_src(2)); - v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6] * v_src(2)); - v_dst(2) = T(trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2)); - } - - stl_get_size(stl); + stl_get_size(stl); } template inline void stl_transform(stl_file *stl, const Eigen::Transform& t) { - if (stl->error) - return; - const Eigen::Matrix r = t.matrix().template block<3, 3>(0, 0); - for (size_t i = 0; i < stl->stats.number_of_facets; ++i) { + for (size_t i = 0; i < stl->stats.number_of_facets; ++ i) { stl_facet &f = stl->facet_start[i]; for (size_t j = 0; j < 3; ++j) f.vertex[j] = (t * f.vertex[j].template cast()).template cast().eval(); @@ -228,10 +222,7 @@ inline void stl_transform(stl_file *stl, const Eigen::Transform inline void stl_transform(stl_file *stl, const Eigen::Matrix& m) { - if (stl->error) - return; - - for (size_t i = 0; i < stl->stats.number_of_facets; ++i) { + for (size_t i = 0; i < stl->stats.number_of_facets; ++ i) { stl_facet &f = stl->facet_start[i]; for (size_t j = 0; j < 3; ++j) f.vertex[j] = (m * f.vertex[j].template cast()).template cast().eval(); @@ -241,13 +232,43 @@ inline void stl_transform(stl_file *stl, const Eigen::Matrix +extern void its_transform(indexed_triangle_set &its, T *trafo3x4) +{ + for (stl_vertex &v_dst : its.vertices) { + stl_vertex v_src = v_dst; + v_dst(0) = T(trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2] * v_src(2) + trafo3x4[3]); + v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6] * v_src(2) + trafo3x4[7]); + v_dst(2) = T(trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2) + trafo3x4[11]); + } +} + +template +inline void its_transform(indexed_triangle_set &its, const Eigen::Transform& t) +{ + const Eigen::Matrix r = t.matrix().template block<3, 3>(0, 0); + for (stl_vertex &v : its.vertices) + v = (t * v.template cast()).template cast().eval(); +} + +template +inline void its_transform(indexed_triangle_set &its, const Eigen::Matrix& m) +{ + for (stl_vertex &v : its.vertices) + v = (m * v.template cast()).template cast().eval(); +} + +extern void its_rotate_x(indexed_triangle_set &its, float angle); +extern void its_rotate_y(indexed_triangle_set &its, float angle); +extern void its_rotate_z(indexed_triangle_set &its, float angle); + +extern void stl_generate_shared_vertices(stl_file *stl, indexed_triangle_set &its); +extern bool its_write_obj(const indexed_triangle_set &its, const char *file); +extern bool its_write_off(const indexed_triangle_set &its, const char *file); +extern bool its_write_vrml(const indexed_triangle_set &its, const char *file); + +extern bool stl_write_dxf(stl_file *stl, const char *file, char *label); inline void stl_calculate_normal(stl_normal &normal, stl_facet *facet) { normal = (facet->vertex[1] - facet->vertex[0]).cross(facet->vertex[2] - facet->vertex[0]); } @@ -258,24 +279,18 @@ inline void stl_normalize_vector(stl_normal &normal) { else normal *= float(1.0 / length); } -inline bool stl_vertex_lower(const stl_vertex &a, const stl_vertex &b) { - return (a(0) != b(0)) ? (a(0) < b(0)) : - ((a(1) != b(1)) ? (a(1) < b(1)) : (a(2) < b(2))); -} extern void stl_calculate_volume(stl_file *stl); -extern void stl_repair(stl_file *stl, int fixall_flag, int exact_flag, int tolerance_flag, float tolerance, int increment_flag, float increment, int nearby_flag, int iterations, int remove_unconnected_flag, int fill_holes_flag, int normal_directions_flag, int normal_values_flag, int reverse_all_flag, int verbose_flag); +extern void stl_repair(stl_file *stl, bool fixall_flag, bool exact_flag, bool tolerance_flag, float tolerance, bool increment_flag, float increment, bool nearby_flag, int iterations, bool remove_unconnected_flag, bool fill_holes_flag, bool normal_directions_flag, bool normal_values_flag, bool reverse_all_flag, bool verbose_flag); -extern void stl_initialize(stl_file *stl); -extern void stl_count_facets(stl_file *stl, const char *file); extern void stl_allocate(stl_file *stl); extern void stl_read(stl_file *stl, int first_facet, bool first); extern void stl_facet_stats(stl_file *stl, stl_facet facet, bool &first); extern void stl_reallocate(stl_file *stl); -extern void stl_add_facet(stl_file *stl, stl_facet *new_facet); +extern void stl_add_facet(stl_file *stl, const stl_facet *new_facet); -extern void stl_clear_error(stl_file *stl); -extern int stl_get_error(stl_file *stl); -extern void stl_exit_on_error(stl_file *stl); +// Validate the mesh, assert on error. +extern bool stl_validate(const stl_file *stl); +extern bool stl_validate(const stl_file *stl, const indexed_triangle_set &its); #endif diff --git a/src/admesh/stl_io.cpp b/src/admesh/stl_io.cpp index 85f66785b3..464c98907a 100644 --- a/src/admesh/stl_io.cpp +++ b/src/admesh/stl_io.cpp @@ -22,159 +22,86 @@ #include #include + +#include +#include +#include + #include "stl.h" -#include -#include - -#if !defined(SEEK_SET) -#define SEEK_SET 0 -#define SEEK_CUR 1 -#define SEEK_END 2 -#endif - -void -stl_stats_out(stl_file *stl, FILE *file, char *input_file) { - if (stl->error) return; - - /* this is here for Slic3r, without our config.h - it won't use this part of the code anyway */ +void stl_stats_out(stl_file *stl, FILE *file, char *input_file) +{ + // This is here for Slic3r, without our config.h it won't use this part of the code anyway. #ifndef VERSION #define VERSION "unknown" #endif - fprintf(file, "\n\ -================= Results produced by ADMesh version " VERSION " ================\n"); - fprintf(file, "\ -Input file : %s\n", input_file); - if(stl->stats.type == binary) { - fprintf(file, "\ -File type : Binary STL file\n"); - } else { - fprintf(file, "\ -File type : ASCII STL file\n"); - } - fprintf(file, "\ -Header : %s\n", stl->stats.header); - fprintf(file, "============== Size ==============\n"); - fprintf(file, "Min X = % f, Max X = % f\n", - stl->stats.min(0), stl->stats.max(0)); - fprintf(file, "Min Y = % f, Max Y = % f\n", - stl->stats.min(1), stl->stats.max(1)); - fprintf(file, "Min Z = % f, Max Z = % f\n", - stl->stats.min(2), stl->stats.max(2)); - - fprintf(file, "\ -========= Facet Status ========== Original ============ Final ====\n"); - fprintf(file, "\ -Number of facets : %5d %5d\n", - stl->stats.original_num_facets, stl->stats.number_of_facets); - fprintf(file, "\ -Facets with 1 disconnected edge : %5d %5d\n", - stl->stats.facets_w_1_bad_edge, stl->stats.connected_facets_2_edge - - stl->stats.connected_facets_3_edge); - fprintf(file, "\ -Facets with 2 disconnected edges : %5d %5d\n", - stl->stats.facets_w_2_bad_edge, stl->stats.connected_facets_1_edge - - stl->stats.connected_facets_2_edge); - fprintf(file, "\ -Facets with 3 disconnected edges : %5d %5d\n", - stl->stats.facets_w_3_bad_edge, stl->stats.number_of_facets - - stl->stats.connected_facets_1_edge); - fprintf(file, "\ -Total disconnected facets : %5d %5d\n", - stl->stats.facets_w_1_bad_edge + stl->stats.facets_w_2_bad_edge + - stl->stats.facets_w_3_bad_edge, stl->stats.number_of_facets - - stl->stats.connected_facets_3_edge); - - fprintf(file, - "=== Processing Statistics === ===== Other Statistics =====\n"); - fprintf(file, "\ -Number of parts : %5d Volume : % f\n", - stl->stats.number_of_parts, stl->stats.volume); - fprintf(file, "\ -Degenerate facets : %5d\n", stl->stats.degenerate_facets); - fprintf(file, "\ -Edges fixed : %5d\n", stl->stats.edges_fixed); - fprintf(file, "\ -Facets removed : %5d\n", stl->stats.facets_removed); - fprintf(file, "\ -Facets added : %5d\n", stl->stats.facets_added); - fprintf(file, "\ -Facets reversed : %5d\n", stl->stats.facets_reversed); - fprintf(file, "\ -Backwards edges : %5d\n", stl->stats.backwards_edges); - fprintf(file, "\ -Normals fixed : %5d\n", stl->stats.normals_fixed); + fprintf(file, "\n================= Results produced by ADMesh version " VERSION " ================\n"); + fprintf(file, "Input file : %s\n", input_file); + if (stl->stats.type == binary) + fprintf(file, "File type : Binary STL file\n"); + else + fprintf(file, "File type : ASCII STL file\n"); + fprintf(file, "Header : %s\n", stl->stats.header); + fprintf(file, "============== Size ==============\n"); + fprintf(file, "Min X = % f, Max X = % f\n", stl->stats.min(0), stl->stats.max(0)); + fprintf(file, "Min Y = % f, Max Y = % f\n", stl->stats.min(1), stl->stats.max(1)); + fprintf(file, "Min Z = % f, Max Z = % f\n", stl->stats.min(2), stl->stats.max(2)); + fprintf(file, "========= Facet Status ========== Original ============ Final ====\n"); + fprintf(file, "Number of facets : %5d %5d\n", stl->stats.original_num_facets, stl->stats.number_of_facets); + fprintf(file, "Facets with 1 disconnected edge : %5d %5d\n", + stl->stats.facets_w_1_bad_edge, stl->stats.connected_facets_2_edge - stl->stats.connected_facets_3_edge); + fprintf(file, "Facets with 2 disconnected edges : %5d %5d\n", + stl->stats.facets_w_2_bad_edge, stl->stats.connected_facets_1_edge - stl->stats.connected_facets_2_edge); + fprintf(file, "Facets with 3 disconnected edges : %5d %5d\n", + stl->stats.facets_w_3_bad_edge, stl->stats.number_of_facets - stl->stats.connected_facets_1_edge); + fprintf(file, "Total disconnected facets : %5d %5d\n", + stl->stats.facets_w_1_bad_edge + stl->stats.facets_w_2_bad_edge + stl->stats.facets_w_3_bad_edge, stl->stats.number_of_facets - stl->stats.connected_facets_3_edge); + fprintf(file, "=== Processing Statistics === ===== Other Statistics =====\n"); + fprintf(file, "Number of parts : %5d Volume : %f\n", stl->stats.number_of_parts, stl->stats.volume); + fprintf(file, "Degenerate facets : %5d\n", stl->stats.degenerate_facets); + fprintf(file, "Edges fixed : %5d\n", stl->stats.edges_fixed); + fprintf(file, "Facets removed : %5d\n", stl->stats.facets_removed); + fprintf(file, "Facets added : %5d\n", stl->stats.facets_added); + fprintf(file, "Facets reversed : %5d\n", stl->stats.facets_reversed); + fprintf(file, "Backwards edges : %5d\n", stl->stats.backwards_edges); + fprintf(file, "Normals fixed : %5d\n", stl->stats.normals_fixed); } -void -stl_write_ascii(stl_file *stl, const char *file, const char *label) { - int i; - char *error_msg; +bool stl_write_ascii(stl_file *stl, const char *file, const char *label) +{ + FILE *fp = boost::nowide::fopen(file, "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) << "stl_write_ascii: Couldn't open " << file << " for writing"; + return false; + } - if (stl->error) return; + fprintf(fp, "solid %s\n", label); - /* Open the file */ - FILE *fp = boost::nowide::fopen(file, "w"); - if(fp == NULL) { - error_msg = (char*) - malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ - sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", - file); - perror(error_msg); - free(error_msg); - stl->error = 1; - return; - } + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { + fprintf(fp, " facet normal % .8E % .8E % .8E\n", stl->facet_start[i].normal(0), stl->facet_start[i].normal(1), stl->facet_start[i].normal(2)); + fprintf(fp, " outer loop\n"); + fprintf(fp, " vertex % .8E % .8E % .8E\n", stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), stl->facet_start[i].vertex[0](2)); + fprintf(fp, " vertex % .8E % .8E % .8E\n", stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), stl->facet_start[i].vertex[1](2)); + fprintf(fp, " vertex % .8E % .8E % .8E\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2)); + fprintf(fp, " endloop\n"); + fprintf(fp, " endfacet\n"); + } - fprintf(fp, "solid %s\n", label); - - for(i = 0; i < stl->stats.number_of_facets; i++) { - fprintf(fp, " facet normal % .8E % .8E % .8E\n", - stl->facet_start[i].normal(0), stl->facet_start[i].normal(1), - stl->facet_start[i].normal(2)); - fprintf(fp, " outer loop\n"); - fprintf(fp, " vertex % .8E % .8E % .8E\n", - stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), - stl->facet_start[i].vertex[0](2)); - fprintf(fp, " vertex % .8E % .8E % .8E\n", - stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), - stl->facet_start[i].vertex[1](2)); - fprintf(fp, " vertex % .8E % .8E % .8E\n", - stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), - stl->facet_start[i].vertex[2](2)); - fprintf(fp, " endloop\n"); - fprintf(fp, " endfacet\n"); - } - - fprintf(fp, "endsolid %s\n", label); - - fclose(fp); + fprintf(fp, "endsolid %s\n", label); + fclose(fp); + return true; } -void -stl_print_neighbors(stl_file *stl, char *file) { - int i; - FILE *fp; - char *error_msg; +bool stl_print_neighbors(stl_file *stl, char *file) +{ + FILE *fp = boost::nowide::fopen(file, "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) << "stl_print_neighbors: Couldn't open " << file << " for writing"; + return false; + } - if (stl->error) return; - - /* Open the file */ - fp = boost::nowide::fopen(file, "w"); - if(fp == NULL) { - error_msg = (char*) - malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ - sprintf(error_msg, "stl_print_neighbors: Couldn't open %s for writing", - file); - perror(error_msg); - free(error_msg); - stl->error = 1; - return; - } - - for(i = 0; i < stl->stats.number_of_facets; i++) { - fprintf(fp, "%d, %d,%d, %d,%d, %d,%d\n", + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { + fprintf(fp, "%d, %d,%d, %d,%d, %d,%d\n", i, stl->neighbors_start[i].neighbor[0], (int)stl->neighbors_start[i].which_vertex_not[0], @@ -182,234 +109,142 @@ stl_print_neighbors(stl_file *stl, char *file) { (int)stl->neighbors_start[i].which_vertex_not[1], stl->neighbors_start[i].neighbor[2], (int)stl->neighbors_start[i].which_vertex_not[2]); - } - fclose(fp); + } + fclose(fp); + return true; } -#ifndef BOOST_LITTLE_ENDIAN +#if BOOST_ENDIAN_BIG_BYTE // Swap a buffer of 32bit data from little endian to big endian and vice versa. void stl_internal_reverse_quads(char *buf, size_t cnt) { - for (size_t i = 0; i < cnt; i += 4) { - std::swap(buf[i], buf[i+3]); - std::swap(buf[i+1], buf[i+2]); - } + for (size_t i = 0; i < cnt; i += 4) { + std::swap(buf[i], buf[i+3]); + std::swap(buf[i+1], buf[i+2]); + } } #endif -void -stl_write_binary(stl_file *stl, const char *file, const char *label) { - FILE *fp; - int i; - char *error_msg; +bool stl_write_binary(stl_file *stl, const char *file, const char *label) +{ + FILE *fp = boost::nowide::fopen(file, "wb"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) << "stl_write_binary: Couldn't open " << file << " for writing"; + return false; + } - if (stl->error) return; + fprintf(fp, "%s", label); + for (size_t i = strlen(label); i < LABEL_SIZE; ++ i) + putc(0, fp); - /* Open the file */ - fp = boost::nowide::fopen(file, "wb"); - if(fp == NULL) { - error_msg = (char*) - malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ - sprintf(error_msg, "stl_write_binary: Couldn't open %s for writing", - file); - perror(error_msg); - free(error_msg); - stl->error = 1; - return; - } - - fprintf(fp, "%s", label); - for(i = strlen(label); i < LABEL_SIZE; i++) putc(0, fp); - - fseek(fp, LABEL_SIZE, SEEK_SET); -#ifdef BOOST_LITTLE_ENDIAN - fwrite(&stl->stats.number_of_facets, 4, 1, fp); - for (i = 0; i < stl->stats.number_of_facets; ++ i) - fwrite(stl->facet_start + i, SIZEOF_STL_FACET, 1, fp); -#else /* BOOST_LITTLE_ENDIAN */ - char buffer[50]; - // Convert the number of facets to little endian. - memcpy(buffer, &stl->stats.number_of_facets, 4); - stl_internal_reverse_quads(buffer, 4); - fwrite(buffer, 4, 1, fp); - for (i = 0; i < stl->stats.number_of_facets; ++ i) { - memcpy(buffer, stl->facet_start + i, 50); - // Convert to little endian. - stl_internal_reverse_quads(buffer, 48); - fwrite(buffer, SIZEOF_STL_FACET, 1, fp); - } -#endif /* BOOST_LITTLE_ENDIAN */ - fclose(fp); +#if !defined(SEEK_SET) + #define SEEK_SET 0 +#endif + fseek(fp, LABEL_SIZE, SEEK_SET); +#if BOOST_ENDIAN_LITTLE_BYTE + fwrite(&stl->stats.number_of_facets, 4, 1, fp); + for (const stl_facet &facet : stl->facet_start) + fwrite(&facet, SIZEOF_STL_FACET, 1, fp); +#else /* BOOST_ENDIAN_LITTLE_BYTE */ + char buffer[50]; + // Convert the number of facets to little endian. + memcpy(buffer, &stl->stats.number_of_facets, 4); + stl_internal_reverse_quads(buffer, 4); + fwrite(buffer, 4, 1, fp); + for (i = 0; i < stl->stats.number_of_facets; ++ i) { + memcpy(buffer, stl->facet_start + i, 50); + // Convert to little endian. + stl_internal_reverse_quads(buffer, 48); + fwrite(buffer, SIZEOF_STL_FACET, 1, fp); + } +#endif /* BOOST_ENDIAN_LITTLE_BYTE */ + fclose(fp); + return true; } -void -stl_write_vertex(stl_file *stl, int facet, int vertex) { - if (stl->error) return; - printf(" vertex %d/%d % .8E % .8E % .8E\n", vertex, facet, +void stl_write_vertex(stl_file *stl, int facet, int vertex) +{ + printf(" vertex %d/%d % .8E % .8E % .8E\n", vertex, facet, stl->facet_start[facet].vertex[vertex](0), stl->facet_start[facet].vertex[vertex](1), stl->facet_start[facet].vertex[vertex](2)); } -void -stl_write_facet(stl_file *stl, char *label, int facet) { - if (stl->error) return; - printf("facet (%d)/ %s\n", facet, label); - stl_write_vertex(stl, facet, 0); - stl_write_vertex(stl, facet, 1); - stl_write_vertex(stl, facet, 2); +void stl_write_facet(stl_file *stl, char *label, int facet) +{ + printf("facet (%d)/ %s\n", facet, label); + stl_write_vertex(stl, facet, 0); + stl_write_vertex(stl, facet, 1); + stl_write_vertex(stl, facet, 2); } -void -stl_write_edge(stl_file *stl, char *label, stl_hash_edge edge) { - if (stl->error) return; - printf("edge (%d)/(%d) %s\n", edge.facet_number, edge.which_edge, label); - if(edge.which_edge < 3) { - stl_write_vertex(stl, edge.facet_number, edge.which_edge % 3); - stl_write_vertex(stl, edge.facet_number, (edge.which_edge + 1) % 3); - } else { - stl_write_vertex(stl, edge.facet_number, (edge.which_edge + 1) % 3); - stl_write_vertex(stl, edge.facet_number, edge.which_edge % 3); - } +void stl_write_neighbor(stl_file *stl, int facet) +{ + printf("Neighbors %d: %d, %d, %d ; %d, %d, %d\n", facet, + stl->neighbors_start[facet].neighbor[0], + stl->neighbors_start[facet].neighbor[1], + stl->neighbors_start[facet].neighbor[2], + stl->neighbors_start[facet].which_vertex_not[0], + stl->neighbors_start[facet].which_vertex_not[1], + stl->neighbors_start[facet].which_vertex_not[2]); } -void -stl_write_neighbor(stl_file *stl, int facet) { - if (stl->error) return; - printf("Neighbors %d: %d, %d, %d ; %d, %d, %d\n", facet, - stl->neighbors_start[facet].neighbor[0], - stl->neighbors_start[facet].neighbor[1], - stl->neighbors_start[facet].neighbor[2], - stl->neighbors_start[facet].which_vertex_not[0], - stl->neighbors_start[facet].which_vertex_not[1], - stl->neighbors_start[facet].which_vertex_not[2]); -} +bool stl_write_quad_object(stl_file *stl, char *file) +{ + stl_vertex connect_color = stl_vertex::Zero(); + stl_vertex uncon_1_color = stl_vertex::Zero(); + stl_vertex uncon_2_color = stl_vertex::Zero(); + stl_vertex uncon_3_color = stl_vertex::Zero(); + stl_vertex color; -void -stl_write_quad_object(stl_file *stl, char *file) { - FILE *fp; - int i; - int j; - char *error_msg; - stl_vertex connect_color = stl_vertex::Zero(); - stl_vertex uncon_1_color = stl_vertex::Zero(); - stl_vertex uncon_2_color = stl_vertex::Zero(); - stl_vertex uncon_3_color = stl_vertex::Zero(); - stl_vertex color; + FILE *fp = boost::nowide::fopen(file, "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) << "stl_write_quad_object: Couldn't open " << file << " for writing"; + return false; + } - if (stl->error) return; - - /* Open the file */ - fp = boost::nowide::fopen(file, "w"); - if(fp == NULL) { - error_msg = (char*) - malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ - sprintf(error_msg, "stl_write_quad_object: Couldn't open %s for writing", - file); - perror(error_msg); - free(error_msg); - stl->error = 1; - return; - } - - fprintf(fp, "CQUAD\n"); - for(i = 0; i < stl->stats.number_of_facets; i++) { - j = ((stl->neighbors_start[i].neighbor[0] == -1) + - (stl->neighbors_start[i].neighbor[1] == -1) + - (stl->neighbors_start[i].neighbor[2] == -1)); - if(j == 0) { - color = connect_color; - } else if(j == 1) { - color = uncon_1_color; - } else if(j == 2) { - color = uncon_2_color; - } else { - color = uncon_3_color; - } - fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", - stl->facet_start[i].vertex[0](0), - stl->facet_start[i].vertex[0](1), - stl->facet_start[i].vertex[0](2), color(0), color(1), color(2)); - fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", - stl->facet_start[i].vertex[1](0), - stl->facet_start[i].vertex[1](1), - stl->facet_start[i].vertex[1](2), color(0), color(1), color(2)); - fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", - stl->facet_start[i].vertex[2](0), - stl->facet_start[i].vertex[2](1), - stl->facet_start[i].vertex[2](2), color(0), color(1), color(2)); - fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", - stl->facet_start[i].vertex[2](0), - stl->facet_start[i].vertex[2](1), - stl->facet_start[i].vertex[2](2), color(0), color(1), color(2)); + fprintf(fp, "CQUAD\n"); + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { + switch (stl->neighbors_start[i].num_neighbors_missing()) { + case 0: color = connect_color; break; + case 1: color = uncon_1_color; break; + case 2: color = uncon_2_color; break; + default: color = uncon_3_color; + } + fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), stl->facet_start[i].vertex[0](2), color(0), color(1), color(2)); + fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), stl->facet_start[i].vertex[1](2), color(0), color(1), color(2)); + fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2), color(0), color(1), color(2)); + fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2), color(0), color(1), color(2)); } fclose(fp); + return true; } -void -stl_write_dxf(stl_file *stl, const char *file, char *label) { - int i; - FILE *fp; - char *error_msg; +bool stl_write_dxf(stl_file *stl, const char *file, char *label) +{ + FILE *fp = boost::nowide::fopen(file, "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) << "stl_write_quad_object: Couldn't open " << file << " for writing"; + return false; + } - if (stl->error) return; + fprintf(fp, "999\n%s\n", label); + fprintf(fp, "0\nSECTION\n2\nHEADER\n0\nENDSEC\n"); + fprintf(fp, "0\nSECTION\n2\nTABLES\n0\nTABLE\n2\nLAYER\n70\n1\n\ + 0\nLAYER\n2\n0\n70\n0\n62\n7\n6\nCONTINUOUS\n0\nENDTAB\n0\nENDSEC\n"); + fprintf(fp, "0\nSECTION\n2\nBLOCKS\n0\nENDSEC\n"); - /* Open the file */ - fp = boost::nowide::fopen(file, "w"); - if(fp == NULL) { - error_msg = (char*) - malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ - sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", - file); - perror(error_msg); - free(error_msg); - stl->error = 1; - return; - } + fprintf(fp, "0\nSECTION\n2\nENTITIES\n"); - fprintf(fp, "999\n%s\n", label); - fprintf(fp, "0\nSECTION\n2\nHEADER\n0\nENDSEC\n"); - fprintf(fp, "0\nSECTION\n2\nTABLES\n0\nTABLE\n2\nLAYER\n70\n1\n\ -0\nLAYER\n2\n0\n70\n0\n62\n7\n6\nCONTINUOUS\n0\nENDTAB\n0\nENDSEC\n"); - fprintf(fp, "0\nSECTION\n2\nBLOCKS\n0\nENDSEC\n"); + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { + fprintf(fp, "0\n3DFACE\n8\n0\n"); + fprintf(fp, "10\n%f\n20\n%f\n30\n%f\n", stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), stl->facet_start[i].vertex[0](2)); + fprintf(fp, "11\n%f\n21\n%f\n31\n%f\n", stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), stl->facet_start[i].vertex[1](2)); + fprintf(fp, "12\n%f\n22\n%f\n32\n%f\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2)); + fprintf(fp, "13\n%f\n23\n%f\n33\n%f\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2)); + } - fprintf(fp, "0\nSECTION\n2\nENTITIES\n"); - - for(i = 0; i < stl->stats.number_of_facets; i++) { - fprintf(fp, "0\n3DFACE\n8\n0\n"); - fprintf(fp, "10\n%f\n20\n%f\n30\n%f\n", - stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), - stl->facet_start[i].vertex[0](2)); - fprintf(fp, "11\n%f\n21\n%f\n31\n%f\n", - stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), - stl->facet_start[i].vertex[1](2)); - fprintf(fp, "12\n%f\n22\n%f\n32\n%f\n", - stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), - stl->facet_start[i].vertex[2](2)); - fprintf(fp, "13\n%f\n23\n%f\n33\n%f\n", - stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), - stl->facet_start[i].vertex[2](2)); - } - - fprintf(fp, "0\nENDSEC\n0\nEOF\n"); - - fclose(fp); -} - -void -stl_clear_error(stl_file *stl) { - stl->error = 0; -} - -void -stl_exit_on_error(stl_file *stl) { - if (!stl->error) return; - stl->error = 0; - stl_close(stl); - exit(1); -} - -int -stl_get_error(stl_file *stl) { - return stl->error; + fprintf(fp, "0\nENDSEC\n0\nEOF\n"); + fclose(fp); + return true; } diff --git a/src/admesh/stlinit.cpp b/src/admesh/stlinit.cpp index 911f4f5e82..a328baa75f 100644 --- a/src/admesh/stlinit.cpp +++ b/src/admesh/stlinit.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -35,351 +36,236 @@ #error "SEEK_SET not defined" #endif -void -stl_open(stl_file *stl, const char *file) { - stl_initialize(stl); - stl_count_facets(stl, file); - stl_allocate(stl); - stl_read(stl, 0, true); - if (stl->fp != nullptr) { - fclose(stl->fp); - stl->fp = nullptr; - } +static FILE* stl_open_count_facets(stl_file *stl, const char *file) +{ + // Open the file in binary mode first. + FILE *fp = boost::nowide::fopen(file, "rb"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) << "stl_open_count_facets: Couldn't open " << file << " for reading"; + return nullptr; + } + // Find size of file. + fseek(fp, 0, SEEK_END); + long file_size = ftell(fp); + + // Check for binary or ASCII file. + fseek(fp, HEADER_SIZE, SEEK_SET); + unsigned char chtest[128]; + if (! fread(chtest, sizeof(chtest), 1, fp)) { + BOOST_LOG_TRIVIAL(error) << "stl_open_count_facets: The input is an empty file: " << file; + fclose(fp); + return nullptr; + } + stl->stats.type = ascii; + for (size_t s = 0; s < sizeof(chtest); s++) { + if (chtest[s] > 127) { + stl->stats.type = binary; + break; + } + } + rewind(fp); + + uint32_t num_facets = 0; + + // Get the header and the number of facets in the .STL file. + // If the .STL file is binary, then do the following: + if (stl->stats.type == binary) { + // Test if the STL file has the right size. + if (((file_size - HEADER_SIZE) % SIZEOF_STL_FACET != 0) || (file_size < STL_MIN_FILE_SIZE)) { + BOOST_LOG_TRIVIAL(error) << "stl_open_count_facets: The file " << file << " has the wrong size."; + fclose(fp); + return nullptr; + } + num_facets = (file_size - HEADER_SIZE) / SIZEOF_STL_FACET; + + // Read the header. + if (fread(stl->stats.header, LABEL_SIZE, 1, fp) > 79) + stl->stats.header[80] = '\0'; + + // Read the int following the header. This should contain # of facets. + uint32_t header_num_facets; + bool header_num_faces_read = fread(&header_num_facets, sizeof(uint32_t), 1, fp) != 0; +#ifndef BOOST_LITTLE_ENDIAN + // Convert from little endian to big endian. + stl_internal_reverse_quads((char*)&header_num_facets, 4); +#endif /* BOOST_LITTLE_ENDIAN */ + if (! header_num_faces_read || num_facets != header_num_facets) + BOOST_LOG_TRIVIAL(info) << "stl_open_count_facets: Warning: File size doesn't match number of facets in the header: " << file; + } + // Otherwise, if the .STL file is ASCII, then do the following: + else + { + // Reopen the file in text mode (for getting correct newlines on Windows) + // fix to silence a warning about unused return value. + // obviously if it fails we have problems.... + fp = boost::nowide::freopen(file, "r", fp); + + // do another null check to be safe + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) << "stl_open_count_facets: Couldn't open " << file << " for reading"; + fclose(fp); + return nullptr; + } + + // Find the number of facets. + char linebuf[100]; + int num_lines = 1; + while (fgets(linebuf, 100, fp) != nullptr) { + // Don't count short lines. + if (strlen(linebuf) <= 4) + continue; + // Skip solid/endsolid lines as broken STL file generators may put several of them. + if (strncmp(linebuf, "solid", 5) == 0 || strncmp(linebuf, "endsolid", 8) == 0) + continue; + ++ num_lines; + } + + rewind(fp); + + // Get the header. + int i = 0; + for (; i < 80 && (stl->stats.header[i] = getc(fp)) != '\n'; ++ i) ; + stl->stats.header[i] = '\0'; // Lose the '\n' + stl->stats.header[80] = '\0'; + + num_facets = num_lines / ASCII_LINES_PER_FACET; + } + + stl->stats.number_of_facets += num_facets; + stl->stats.original_num_facets = stl->stats.number_of_facets; + return fp; } -void -stl_initialize(stl_file *stl) { - memset(stl, 0, sizeof(stl_file)); - stl->stats.volume = -1.0; +/* Reads the contents of the file pointed to by fp into the stl structure, + starting at facet first_facet. The second argument says if it's our first + time running this for the stl and therefore we should reset our max and min stats. */ +static bool stl_read(stl_file *stl, FILE *fp, int first_facet, bool first) +{ + if (stl->stats.type == binary) + fseek(fp, HEADER_SIZE, SEEK_SET); + else + rewind(fp); + + char normal_buf[3][32]; + for (uint32_t i = first_facet; i < stl->stats.number_of_facets; ++ i) { + stl_facet facet; + + if (stl->stats.type == binary) { + // Read a single facet from a binary .STL file. We assume little-endian architecture! + if (fread(&facet, 1, SIZEOF_STL_FACET, fp) != SIZEOF_STL_FACET) + return false; +#ifndef BOOST_LITTLE_ENDIAN + // Convert the loaded little endian data to big endian. + stl_internal_reverse_quads((char*)&facet, 48); +#endif /* BOOST_LITTLE_ENDIAN */ + } else { + // Read a single facet from an ASCII .STL file + // skip solid/endsolid + // (in this order, otherwise it won't work when they are paired in the middle of a file) + fscanf(fp, "endsolid%*[^\n]\n"); + fscanf(fp, "solid%*[^\n]\n"); // name might contain spaces so %*s doesn't work and it also can be empty (just "solid") + // Leading space in the fscanf format skips all leading white spaces including numerous new lines and tabs. + int res_normal = fscanf(fp, " facet normal %31s %31s %31s", normal_buf[0], normal_buf[1], normal_buf[2]); + assert(res_normal == 3); + int res_outer_loop = fscanf(fp, " outer loop"); + assert(res_outer_loop == 0); + int res_vertex1 = fscanf(fp, " vertex %f %f %f", &facet.vertex[0](0), &facet.vertex[0](1), &facet.vertex[0](2)); + assert(res_vertex1 == 3); + int res_vertex2 = fscanf(fp, " vertex %f %f %f", &facet.vertex[1](0), &facet.vertex[1](1), &facet.vertex[1](2)); + assert(res_vertex2 == 3); + int res_vertex3 = fscanf(fp, " vertex %f %f %f", &facet.vertex[2](0), &facet.vertex[2](1), &facet.vertex[2](2)); + assert(res_vertex3 == 3); + int res_endloop = fscanf(fp, " endloop"); + assert(res_endloop == 0); + // There is a leading and trailing white space around endfacet to eat up all leading and trailing white spaces including numerous tabs and new lines. + int res_endfacet = fscanf(fp, " endfacet "); + if (res_normal != 3 || res_outer_loop != 0 || res_vertex1 != 3 || res_vertex2 != 3 || res_vertex3 != 3 || res_endloop != 0 || res_endfacet != 0) { + BOOST_LOG_TRIVIAL(error) << "Something is syntactically very wrong with this ASCII STL! "; + return false; + } + + // The facet normal has been parsed as a single string as to workaround for not a numbers in the normal definition. + if (sscanf(normal_buf[0], "%f", &facet.normal(0)) != 1 || + sscanf(normal_buf[1], "%f", &facet.normal(1)) != 1 || + sscanf(normal_buf[2], "%f", &facet.normal(2)) != 1) { + // Normal was mangled. Maybe denormals or "not a number" were stored? + // Just reset the normal and silently ignore it. + memset(&facet.normal, 0, sizeof(facet.normal)); + } + } + +#if 0 + // Report close to zero vertex coordinates. Due to the nature of the floating point numbers, + // close to zero values may be represented with singificantly higher precision than the rest of the vertices. + // It may be worth to round these numbers to zero during loading to reduce the number of errors reported + // during the STL import. + for (size_t j = 0; j < 3; ++ j) { + if (facet.vertex[j](0) > -1e-12f && facet.vertex[j](0) < 1e-12f) + printf("stl_read: facet %d(0) = %e\r\n", j, facet.vertex[j](0)); + if (facet.vertex[j](1) > -1e-12f && facet.vertex[j](1) < 1e-12f) + printf("stl_read: facet %d(1) = %e\r\n", j, facet.vertex[j](1)); + if (facet.vertex[j](2) > -1e-12f && facet.vertex[j](2) < 1e-12f) + printf("stl_read: facet %d(2) = %e\r\n", j, facet.vertex[j](2)); + } +#endif + + // Write the facet into memory. + stl->facet_start[i] = facet; + stl_facet_stats(stl, facet, first); + } + + stl->stats.size = stl->stats.max - stl->stats.min; + stl->stats.bounding_diameter = stl->stats.size.norm(); + return true; +} + +bool stl_open(stl_file *stl, const char *file) +{ + stl->clear(); + FILE *fp = stl_open_count_facets(stl, file); + if (fp == nullptr) + return false; + stl_allocate(stl); + bool result = stl_read(stl, fp, 0, true); + fclose(fp); + return result; } #ifndef BOOST_LITTLE_ENDIAN extern void stl_internal_reverse_quads(char *buf, size_t cnt); #endif /* BOOST_LITTLE_ENDIAN */ -void -stl_count_facets(stl_file *stl, const char *file) { - long file_size; - uint32_t header_num_facets; - uint32_t num_facets; - int i; - size_t s; - unsigned char chtest[128]; - int num_lines = 1; - char *error_msg; - - if (stl->error) return; - - /* Open the file in binary mode first */ - stl->fp = boost::nowide::fopen(file, "rb"); - if(stl->fp == NULL) { - error_msg = (char*) - malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ - sprintf(error_msg, "stl_initialize: Couldn't open %s for reading", - file); - perror(error_msg); - free(error_msg); - stl->error = 1; - return; - } - /* Find size of file */ - fseek(stl->fp, 0, SEEK_END); - file_size = ftell(stl->fp); - - /* Check for binary or ASCII file */ - fseek(stl->fp, HEADER_SIZE, SEEK_SET); - if (!fread(chtest, sizeof(chtest), 1, stl->fp)) { - perror("The input is an empty file"); - stl->error = 1; - return; - } - stl->stats.type = ascii; - for(s = 0; s < sizeof(chtest); s++) { - if(chtest[s] > 127) { - stl->stats.type = binary; - break; - } - } - rewind(stl->fp); - - /* Get the header and the number of facets in the .STL file */ - /* If the .STL file is binary, then do the following */ - if(stl->stats.type == binary) { - /* Test if the STL file has the right size */ - if(((file_size - HEADER_SIZE) % SIZEOF_STL_FACET != 0) - || (file_size < STL_MIN_FILE_SIZE)) { - fprintf(stderr, "The file %s has the wrong size.\n", file); - stl->error = 1; - return; - } - num_facets = (file_size - HEADER_SIZE) / SIZEOF_STL_FACET; - - /* Read the header */ - if (fread(stl->stats.header, LABEL_SIZE, 1, stl->fp) > 79) { - stl->stats.header[80] = '\0'; - } - - /* Read the int following the header. This should contain # of facets */ - bool header_num_faces_read = fread(&header_num_facets, sizeof(uint32_t), 1, stl->fp) != 0; -#ifndef BOOST_LITTLE_ENDIAN - // Convert from little endian to big endian. - stl_internal_reverse_quads((char*)&header_num_facets, 4); -#endif /* BOOST_LITTLE_ENDIAN */ - if (! header_num_faces_read || num_facets != header_num_facets) { - fprintf(stderr, - "Warning: File size doesn't match number of facets in the header\n"); - } - } - /* Otherwise, if the .STL file is ASCII, then do the following */ - else { - /* Reopen the file in text mode (for getting correct newlines on Windows) */ - // fix to silence a warning about unused return value. - // obviously if it fails we have problems.... - stl->fp = boost::nowide::freopen(file, "r", stl->fp); - - // do another null check to be safe - if(stl->fp == NULL) { - error_msg = (char*) - malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ - sprintf(error_msg, "stl_initialize: Couldn't open %s for reading", - file); - perror(error_msg); - free(error_msg); - stl->error = 1; - return; - } - - /* Find the number of facets */ - char linebuf[100]; - while (fgets(linebuf, 100, stl->fp) != NULL) { - /* don't count short lines */ - if (strlen(linebuf) <= 4) continue; - - /* skip solid/endsolid lines as broken STL file generators may put several of them */ - if (strncmp(linebuf, "solid", 5) == 0 || strncmp(linebuf, "endsolid", 8) == 0) continue; - - ++num_lines; - } - - rewind(stl->fp); - - /* Get the header */ - for(i = 0; - (i < 80) && (stl->stats.header[i] = getc(stl->fp)) != '\n'; i++); - stl->stats.header[i] = '\0'; /* Lose the '\n' */ - stl->stats.header[80] = '\0'; - - num_facets = num_lines / ASCII_LINES_PER_FACET; - } - stl->stats.number_of_facets += num_facets; - stl->stats.original_num_facets = stl->stats.number_of_facets; +void stl_allocate(stl_file *stl) +{ + // Allocate memory for the entire .STL file. + stl->facet_start.assign(stl->stats.number_of_facets, stl_facet()); + // Allocate memory for the neighbors list. + stl->neighbors_start.assign(stl->stats.number_of_facets, stl_neighbors()); } -void -stl_allocate(stl_file *stl) { - if (stl->error) return; - - /* Allocate memory for the entire .STL file */ - stl->facet_start = (stl_facet*)calloc(stl->stats.number_of_facets, - sizeof(stl_facet)); - if(stl->facet_start == NULL) perror("stl_initialize"); - stl->stats.facets_malloced = stl->stats.number_of_facets; - - /* Allocate memory for the neighbors list */ - stl->neighbors_start = (stl_neighbors*) - calloc(stl->stats.number_of_facets, sizeof(stl_neighbors)); - if(stl->facet_start == NULL) perror("stl_initialize"); -} - -void -stl_open_merge(stl_file *stl, char *file_to_merge) { - int num_facets_so_far; - stl_type origStlType; - FILE *origFp; - stl_file stl_to_merge; - - if (stl->error) return; - - /* Record how many facets we have so far from the first file. We will start putting - facets in the next position. Since we're 0-indexed, it'l be the same position. */ - num_facets_so_far = stl->stats.number_of_facets; - - /* Record the file type we started with: */ - origStlType=stl->stats.type; - /* Record the file pointer too: */ - origFp=stl->fp; - - /* Initialize the sturucture with zero stats, header info and sizes: */ - stl_initialize(&stl_to_merge); - stl_count_facets(&stl_to_merge, file_to_merge); - - /* Copy what we need to into stl so that we can read the file_to_merge directly into it - using stl_read: Save the rest of the valuable info: */ - stl->stats.type=stl_to_merge.stats.type; - stl->fp=stl_to_merge.fp; - - /* Add the number of facets we already have in stl with what we we found in stl_to_merge but - haven't read yet. */ - stl->stats.number_of_facets=num_facets_so_far+stl_to_merge.stats.number_of_facets; - - /* Allocate enough room for stl->stats.number_of_facets facets and neighbors: */ - stl_reallocate(stl); - - /* Read the file to merge directly into stl, adding it to what we have already. - Start at num_facets_so_far, the index to the first unused facet. Also say - that this isn't our first time so we should augment stats like min and max - instead of erasing them. */ - stl_read(stl, num_facets_so_far, false); - - /* Restore the stl information we overwrote (for stl_read) so that it still accurately - reflects the subject part: */ - stl->stats.type=origStlType; - stl->fp=origFp; -} - -extern void -stl_reallocate(stl_file *stl) { - if (stl->error) return; - /* Reallocate more memory for the .STL file(s) */ - stl->facet_start = (stl_facet*)realloc(stl->facet_start, stl->stats.number_of_facets * - sizeof(stl_facet)); - if(stl->facet_start == NULL) perror("stl_initialize"); - stl->stats.facets_malloced = stl->stats.number_of_facets; - - /* Reallocate more memory for the neighbors list */ - stl->neighbors_start = (stl_neighbors*) - realloc(stl->neighbors_start, stl->stats.number_of_facets * - sizeof(stl_neighbors)); - if(stl->facet_start == NULL) perror("stl_initialize"); -} - - -/* Reads the contents of the file pointed to by stl->fp into the stl structure, - starting at facet first_facet. The second argument says if it's our first - time running this for the stl and therefore we should reset our max and min stats. */ -void stl_read(stl_file *stl, int first_facet, bool first) { - stl_facet facet; - - if (stl->error) return; - - if(stl->stats.type == binary) { - fseek(stl->fp, HEADER_SIZE, SEEK_SET); - } else { - rewind(stl->fp); - } - - char normal_buf[3][32]; - for(uint32_t i = first_facet; i < stl->stats.number_of_facets; i++) { - if(stl->stats.type == binary) - /* Read a single facet from a binary .STL file */ - { - /* we assume little-endian architecture! */ - if (fread(&facet, 1, SIZEOF_STL_FACET, stl->fp) != SIZEOF_STL_FACET) { - stl->error = 1; - return; - } -#ifndef BOOST_LITTLE_ENDIAN - // Convert the loaded little endian data to big endian. - stl_internal_reverse_quads((char*)&facet, 48); -#endif /* BOOST_LITTLE_ENDIAN */ - } else - /* Read a single facet from an ASCII .STL file */ - { - // skip solid/endsolid - // (in this order, otherwise it won't work when they are paired in the middle of a file) - fscanf(stl->fp, "endsolid%*[^\n]\n"); - fscanf(stl->fp, "solid%*[^\n]\n"); // name might contain spaces so %*s doesn't work and it also can be empty (just "solid") - // Leading space in the fscanf format skips all leading white spaces including numerous new lines and tabs. - int res_normal = fscanf(stl->fp, " facet normal %31s %31s %31s", normal_buf[0], normal_buf[1], normal_buf[2]); - assert(res_normal == 3); - int res_outer_loop = fscanf(stl->fp, " outer loop"); - assert(res_outer_loop == 0); - int res_vertex1 = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[0](0), &facet.vertex[0](1), &facet.vertex[0](2)); - assert(res_vertex1 == 3); - int res_vertex2 = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[1](0), &facet.vertex[1](1), &facet.vertex[1](2)); - assert(res_vertex2 == 3); - int res_vertex3 = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[2](0), &facet.vertex[2](1), &facet.vertex[2](2)); - assert(res_vertex3 == 3); - int res_endloop = fscanf(stl->fp, " endloop"); - assert(res_endloop == 0); - // There is a leading and trailing white space around endfacet to eat up all leading and trailing white spaces including numerous tabs and new lines. - int res_endfacet = fscanf(stl->fp, " endfacet "); - if (res_normal != 3 || res_outer_loop != 0 || res_vertex1 != 3 || res_vertex2 != 3 || res_vertex3 != 3 || res_endloop != 0 || res_endfacet != 0) { - perror("Something is syntactically very wrong with this ASCII STL!"); - stl->error = 1; - return; - } - - // The facet normal has been parsed as a single string as to workaround for not a numbers in the normal definition. - if (sscanf(normal_buf[0], "%f", &facet.normal(0)) != 1 || - sscanf(normal_buf[1], "%f", &facet.normal(1)) != 1 || - sscanf(normal_buf[2], "%f", &facet.normal(2)) != 1) { - // Normal was mangled. Maybe denormals or "not a number" were stored? - // Just reset the normal and silently ignore it. - memset(&facet.normal, 0, sizeof(facet.normal)); - } - } - -#if 0 - // Report close to zero vertex coordinates. Due to the nature of the floating point numbers, - // close to zero values may be represented with singificantly higher precision than the rest of the vertices. - // It may be worth to round these numbers to zero during loading to reduce the number of errors reported - // during the STL import. - for (size_t j = 0; j < 3; ++ j) { - if (facet.vertex[j](0) > -1e-12f && facet.vertex[j](0) < 1e-12f) - printf("stl_read: facet %d(0) = %e\r\n", j, facet.vertex[j](0)); - if (facet.vertex[j](1) > -1e-12f && facet.vertex[j](1) < 1e-12f) - printf("stl_read: facet %d(1) = %e\r\n", j, facet.vertex[j](1)); - if (facet.vertex[j](2) > -1e-12f && facet.vertex[j](2) < 1e-12f) - printf("stl_read: facet %d(2) = %e\r\n", j, facet.vertex[j](2)); - } -#endif - - /* Write the facet into memory. */ - stl->facet_start[i] = facet; - stl_facet_stats(stl, facet, first); - } - stl->stats.size = stl->stats.max - stl->stats.min; - stl->stats.bounding_diameter = stl->stats.size.norm(); +void stl_reallocate(stl_file *stl) +{ + stl->facet_start.resize(stl->stats.number_of_facets); + stl->neighbors_start.resize(stl->stats.number_of_facets); } void stl_facet_stats(stl_file *stl, stl_facet facet, bool &first) { - if (stl->error) - return; + // While we are going through all of the facets, let's find the + // maximum and minimum values for x, y, and z - // While we are going through all of the facets, let's find the - // maximum and minimum values for x, y, and z + if (first) { + // Initialize the max and min values the first time through + stl->stats.min = facet.vertex[0]; + stl->stats.max = facet.vertex[0]; + stl_vertex diff = (facet.vertex[1] - facet.vertex[0]).cwiseAbs(); + stl->stats.shortest_edge = std::max(diff(0), std::max(diff(1), diff(2))); + first = false; + } - if (first) { - // Initialize the max and min values the first time through - stl->stats.min = facet.vertex[0]; - stl->stats.max = facet.vertex[0]; - stl_vertex diff = (facet.vertex[1] - facet.vertex[0]).cwiseAbs(); - stl->stats.shortest_edge = std::max(diff(0), std::max(diff(1), diff(2))); - first = false; - } - - // Now find the max and min values. - for (size_t i = 0; i < 3; ++ i) { - stl->stats.min = stl->stats.min.cwiseMin(facet.vertex[i]); - stl->stats.max = stl->stats.max.cwiseMax(facet.vertex[i]); - } -} - -void stl_close(stl_file *stl) -{ - assert(stl->fp == nullptr); - assert(stl->heads == nullptr); - assert(stl->tail == nullptr); - - if (stl->facet_start != NULL) - free(stl->facet_start); - if (stl->neighbors_start != NULL) - free(stl->neighbors_start); - if (stl->v_indices != NULL) - free(stl->v_indices); - if (stl->v_shared != NULL) - free(stl->v_shared); - memset(stl, 0, sizeof(stl_file)); + // Now find the max and min values. + for (size_t i = 0; i < 3; ++ i) { + stl->stats.min = stl->stats.min.cwiseMin(facet.vertex[i]); + stl->stats.max = stl->stats.max.cwiseMax(facet.vertex[i]); + } } diff --git a/src/admesh/util.cpp b/src/admesh/util.cpp index 305a58e22b..029e44a28e 100644 --- a/src/admesh/util.cpp +++ b/src/admesh/util.cpp @@ -25,435 +25,375 @@ #include #include +#include + #include "stl.h" -static void stl_rotate(float *x, float *y, const double c, const double s); -static float get_area(stl_facet *facet); -static float get_volume(stl_file *stl); +void stl_verify_neighbors(stl_file *stl) +{ + stl->stats.backwards_edges = 0; - -void -stl_verify_neighbors(stl_file *stl) { - int i; - int j; - stl_edge edge_a; - stl_edge edge_b; - int neighbor; - int vnot; - - if (stl->error) return; - - stl->stats.backwards_edges = 0; - - for(i = 0; i < stl->stats.number_of_facets; i++) { - for(j = 0; j < 3; j++) { - edge_a.p1 = stl->facet_start[i].vertex[j]; - edge_a.p2 = stl->facet_start[i].vertex[(j + 1) % 3]; - neighbor = stl->neighbors_start[i].neighbor[j]; - vnot = stl->neighbors_start[i].which_vertex_not[j]; - - if(neighbor == -1) - continue; /* this edge has no neighbor... Continue. */ - if(vnot < 3) { - edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3]; - edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3]; - } else { - stl->stats.backwards_edges += 1; - edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3]; - edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3]; - } - if (edge_a.p1 != edge_b.p1 || edge_a.p2 != edge_b.p2) { - /* These edges should match but they don't. Print results. */ - printf("edge %d of facet %d doesn't match edge %d of facet %d\n", - j, i, vnot + 1, neighbor); - stl_write_facet(stl, (char*)"first facet", i); - stl_write_facet(stl, (char*)"second facet", neighbor); - } - } - } + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { + for (int j = 0; j < 3; ++ j) { + struct stl_edge { + stl_vertex p1; + stl_vertex p2; + int facet_number; + }; + stl_edge edge_a; + edge_a.p1 = stl->facet_start[i].vertex[j]; + edge_a.p2 = stl->facet_start[i].vertex[(j + 1) % 3]; + int neighbor = stl->neighbors_start[i].neighbor[j]; + if (neighbor == -1) + continue; // this edge has no neighbor... Continue. + int vnot = stl->neighbors_start[i].which_vertex_not[j]; + stl_edge edge_b; + if (vnot < 3) { + edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3]; + edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3]; + } else { + stl->stats.backwards_edges += 1; + edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3]; + edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3]; + } + if (edge_a.p1 != edge_b.p1 || edge_a.p2 != edge_b.p2) { + // These edges should match but they don't. Print results. + BOOST_LOG_TRIVIAL(info) << "edge " << j << " of facet " << i << " doesn't match edge " << (vnot + 1) << " of facet " << neighbor; + stl_write_facet(stl, (char*)"first facet", i); + stl_write_facet(stl, (char*)"second facet", neighbor); + } + } + } } void stl_translate(stl_file *stl, float x, float y, float z) { - if (stl->error) - return; - - stl_vertex new_min(x, y, z); - stl_vertex shift = new_min - stl->stats.min; - for (int i = 0; i < stl->stats.number_of_facets; ++ i) - for (int j = 0; j < 3; ++ j) - stl->facet_start[i].vertex[j] += shift; - stl->stats.min = new_min; - stl->stats.max += shift; - stl_invalidate_shared_vertices(stl); + stl_vertex new_min(x, y, z); + stl_vertex shift = new_min - stl->stats.min; + for (int i = 0; i < stl->stats.number_of_facets; ++ i) + for (int j = 0; j < 3; ++ j) + stl->facet_start[i].vertex[j] += shift; + stl->stats.min = new_min; + stl->stats.max += shift; } /* Translates the stl by x,y,z, relatively from wherever it is currently */ void stl_translate_relative(stl_file *stl, float x, float y, float z) { - if (stl->error) - return; - - stl_vertex shift(x, y, z); - for (int i = 0; i < stl->stats.number_of_facets; ++ i) - for (int j = 0; j < 3; ++ j) - stl->facet_start[i].vertex[j] += shift; - stl->stats.min += shift; - stl->stats.max += shift; - stl_invalidate_shared_vertices(stl); + stl_vertex shift(x, y, z); + for (int i = 0; i < stl->stats.number_of_facets; ++ i) + for (int j = 0; j < 3; ++ j) + stl->facet_start[i].vertex[j] += shift; + stl->stats.min += shift; + stl->stats.max += shift; } void stl_scale_versor(stl_file *stl, const stl_vertex &versor) { - if (stl->error) - return; - - // Scale extents. - auto s = versor.array(); - stl->stats.min.array() *= s; - stl->stats.max.array() *= s; - // Scale size. - stl->stats.size.array() *= s; - // Scale volume. - if (stl->stats.volume > 0.0) - stl->stats.volume *= versor(0) * versor(1) * versor(2); - // Scale the mesh. - for (int i = 0; i < stl->stats.number_of_facets; ++ i) - for (int j = 0; j < 3; ++ j) - stl->facet_start[i].vertex[j].array() *= s; - stl_invalidate_shared_vertices(stl); + // Scale extents. + auto s = versor.array(); + stl->stats.min.array() *= s; + stl->stats.max.array() *= s; + // Scale size. + stl->stats.size.array() *= s; + // Scale volume. + if (stl->stats.volume > 0.0) + stl->stats.volume *= versor(0) * versor(1) * versor(2); + // Scale the mesh. + for (int i = 0; i < stl->stats.number_of_facets; ++ i) + for (int j = 0; j < 3; ++ j) + stl->facet_start[i].vertex[j].array() *= s; } static void calculate_normals(stl_file *stl) { - if (stl->error) - return; - - stl_normal normal; - for(uint32_t i = 0; i < stl->stats.number_of_facets; i++) { - stl_calculate_normal(normal, &stl->facet_start[i]); - stl_normalize_vector(normal); - stl->facet_start[i].normal = normal; - } + stl_normal normal; + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { + stl_calculate_normal(normal, &stl->facet_start[i]); + stl_normalize_vector(normal); + stl->facet_start[i].normal = normal; + } } -void -stl_rotate_x(stl_file *stl, float angle) { - int i; - int j; - double radian_angle = (angle / 180.0) * M_PI; - double c = cos(radian_angle); - double s = sin(radian_angle); - - if (stl->error) return; - - for(i = 0; i < stl->stats.number_of_facets; i++) { - for(j = 0; j < 3; j++) { - stl_rotate(&stl->facet_start[i].vertex[j](1), - &stl->facet_start[i].vertex[j](2), c, s); - } - } - stl_get_size(stl); - calculate_normals(stl); +static inline void rotate_point_2d(float &x, float &y, const double c, const double s) +{ + double xold = x; + double yold = y; + x = float(c * xold - s * yold); + y = float(s * xold + c * yold); } -void -stl_rotate_y(stl_file *stl, float angle) { - int i; - int j; - double radian_angle = (angle / 180.0) * M_PI; - double c = cos(radian_angle); - double s = sin(radian_angle); - - if (stl->error) return; - - for(i = 0; i < stl->stats.number_of_facets; i++) { - for(j = 0; j < 3; j++) { - stl_rotate(&stl->facet_start[i].vertex[j](2), - &stl->facet_start[i].vertex[j](0), c, s); - } - } - stl_get_size(stl); - calculate_normals(stl); +void stl_rotate_x(stl_file *stl, float angle) +{ + double radian_angle = (angle / 180.0) * M_PI; + double c = cos(radian_angle); + double s = sin(radian_angle); + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) + for (int j = 0; j < 3; ++ j) + rotate_point_2d(stl->facet_start[i].vertex[j](1), stl->facet_start[i].vertex[j](2), c, s); + stl_get_size(stl); + calculate_normals(stl); } -void -stl_rotate_z(stl_file *stl, float angle) { - int i; - int j; - double radian_angle = (angle / 180.0) * M_PI; - double c = cos(radian_angle); - double s = sin(radian_angle); - - if (stl->error) return; - - for(i = 0; i < stl->stats.number_of_facets; i++) { - for(j = 0; j < 3; j++) { - stl_rotate(&stl->facet_start[i].vertex[j](0), - &stl->facet_start[i].vertex[j](1), c, s); - } - } - stl_get_size(stl); - calculate_normals(stl); +void stl_rotate_y(stl_file *stl, float angle) +{ + double radian_angle = (angle / 180.0) * M_PI; + double c = cos(radian_angle); + double s = sin(radian_angle); + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) + for (int j = 0; j < 3; ++ j) + rotate_point_2d(stl->facet_start[i].vertex[j](2), stl->facet_start[i].vertex[j](0), c, s); + stl_get_size(stl); + calculate_normals(stl); } +void stl_rotate_z(stl_file *stl, float angle) +{ + double radian_angle = (angle / 180.0) * M_PI; + double c = cos(radian_angle); + double s = sin(radian_angle); + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) + for (int j = 0; j < 3; ++ j) + rotate_point_2d(stl->facet_start[i].vertex[j](0), stl->facet_start[i].vertex[j](1), c, s); + stl_get_size(stl); + calculate_normals(stl); +} +void its_rotate_x(indexed_triangle_set &its, float angle) +{ + double radian_angle = (angle / 180.0) * M_PI; + double c = cos(radian_angle); + double s = sin(radian_angle); + for (stl_vertex &v : its.vertices) + rotate_point_2d(v(1), v(2), c, s); +} -static void -stl_rotate(float *x, float *y, const double c, const double s) { - double xold = *x; - double yold = *y; - *x = float(c * xold - s * yold); - *y = float(s * xold + c * yold); +void its_rotate_y(indexed_triangle_set& its, float angle) +{ + double radian_angle = (angle / 180.0) * M_PI; + double c = cos(radian_angle); + double s = sin(radian_angle); + for (stl_vertex& v : its.vertices) + rotate_point_2d(v(2), v(0), c, s); +} + +void its_rotate_z(indexed_triangle_set& its, float angle) +{ + double radian_angle = (angle / 180.0) * M_PI; + double c = cos(radian_angle); + double s = sin(radian_angle); + for (stl_vertex& v : its.vertices) + rotate_point_2d(v(0), v(1), c, s); } void stl_get_size(stl_file *stl) { - if (stl->error || stl->stats.number_of_facets == 0) - return; - stl->stats.min = stl->facet_start[0].vertex[0]; - stl->stats.max = stl->stats.min; - for (int i = 0; i < stl->stats.number_of_facets; ++ i) { - const stl_facet &face = stl->facet_start[i]; - for (int j = 0; j < 3; ++ j) { - stl->stats.min = stl->stats.min.cwiseMin(face.vertex[j]); - stl->stats.max = stl->stats.max.cwiseMax(face.vertex[j]); - } - } - stl->stats.size = stl->stats.max - stl->stats.min; - stl->stats.bounding_diameter = stl->stats.size.norm(); + if (stl->stats.number_of_facets == 0) + return; + stl->stats.min = stl->facet_start[0].vertex[0]; + stl->stats.max = stl->stats.min; + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { + const stl_facet &face = stl->facet_start[i]; + for (int j = 0; j < 3; ++ j) { + stl->stats.min = stl->stats.min.cwiseMin(face.vertex[j]); + stl->stats.max = stl->stats.max.cwiseMax(face.vertex[j]); + } + } + stl->stats.size = stl->stats.max - stl->stats.min; + stl->stats.bounding_diameter = stl->stats.size.norm(); } void stl_mirror_xy(stl_file *stl) { - if (stl->error) - return; - - for(int i = 0; i < stl->stats.number_of_facets; i++) { - for(int j = 0; j < 3; j++) { - stl->facet_start[i].vertex[j](2) *= -1.0; - } - } - float temp_size = stl->stats.min(2); - stl->stats.min(2) = stl->stats.max(2); - stl->stats.max(2) = temp_size; - stl->stats.min(2) *= -1.0; - stl->stats.max(2) *= -1.0; - stl_reverse_all_facets(stl); - stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */ + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) + for (int j = 0; j < 3; ++ j) + stl->facet_start[i].vertex[j](2) *= -1.0; + float temp_size = stl->stats.min(2); + stl->stats.min(2) = stl->stats.max(2); + stl->stats.max(2) = temp_size; + stl->stats.min(2) *= -1.0; + stl->stats.max(2) *= -1.0; + stl_reverse_all_facets(stl); + stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */ } void stl_mirror_yz(stl_file *stl) { - if (stl->error) return; - - for (int i = 0; i < stl->stats.number_of_facets; i++) { - for (int j = 0; j < 3; j++) { - stl->facet_start[i].vertex[j](0) *= -1.0; - } - } - float temp_size = stl->stats.min(0); - stl->stats.min(0) = stl->stats.max(0); - stl->stats.max(0) = temp_size; - stl->stats.min(0) *= -1.0; - stl->stats.max(0) *= -1.0; - stl_reverse_all_facets(stl); - stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */ + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) + for (int j = 0; j < 3; j++) + stl->facet_start[i].vertex[j](0) *= -1.0; + float temp_size = stl->stats.min(0); + stl->stats.min(0) = stl->stats.max(0); + stl->stats.max(0) = temp_size; + stl->stats.min(0) *= -1.0; + stl->stats.max(0) *= -1.0; + stl_reverse_all_facets(stl); + stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */ } void stl_mirror_xz(stl_file *stl) { - if (stl->error) - return; - - for (int i = 0; i < stl->stats.number_of_facets; i++) { - for (int j = 0; j < 3; j++) { - stl->facet_start[i].vertex[j](1) *= -1.0; - } - } - float temp_size = stl->stats.min(1); - stl->stats.min(1) = stl->stats.max(1); - stl->stats.max(1) = temp_size; - stl->stats.min(1) *= -1.0; - stl->stats.max(1) *= -1.0; - stl_reverse_all_facets(stl); - stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */ -} - -static float get_volume(stl_file *stl) -{ - if (stl->error) - return 0; - - // Choose a point, any point as the reference. - stl_vertex p0 = stl->facet_start[0].vertex[0]; - float volume = 0.f; - for(uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { - // Do dot product to get distance from point to plane. - float height = stl->facet_start[i].normal.dot(stl->facet_start[i].vertex[0] - p0); - float area = get_area(&stl->facet_start[i]); - volume += (area * height) / 3.0f; - } - return volume; -} - -void stl_calculate_volume(stl_file *stl) -{ - if (stl->error) return; - stl->stats.volume = get_volume(stl); - if(stl->stats.volume < 0.0) { - stl_reverse_all_facets(stl); - stl->stats.volume = -stl->stats.volume; - } + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) + for (int j = 0; j < 3; ++ j) + stl->facet_start[i].vertex[j](1) *= -1.0; + float temp_size = stl->stats.min(1); + stl->stats.min(1) = stl->stats.max(1); + stl->stats.max(1) = temp_size; + stl->stats.min(1) *= -1.0; + stl->stats.max(1) *= -1.0; + stl_reverse_all_facets(stl); + stl->stats.facets_reversed -= stl->stats.number_of_facets; // for not altering stats } static float get_area(stl_facet *facet) { - /* cast to double before calculating cross product because large coordinates - can result in overflowing product - (bad area is responsible for bad volume and bad facets reversal) */ - double cross[3][3]; - for (int i = 0; i < 3; i++) { - cross[i][0]=(((double)facet->vertex[i](1) * (double)facet->vertex[(i + 1) % 3](2)) - - ((double)facet->vertex[i](2) * (double)facet->vertex[(i + 1) % 3](1))); - cross[i][1]=(((double)facet->vertex[i](2) * (double)facet->vertex[(i + 1) % 3](0)) - - ((double)facet->vertex[i](0) * (double)facet->vertex[(i + 1) % 3](2))); - cross[i][2]=(((double)facet->vertex[i](0) * (double)facet->vertex[(i + 1) % 3](1)) - - ((double)facet->vertex[i](1) * (double)facet->vertex[(i + 1) % 3](0))); - } + /* cast to double before calculating cross product because large coordinates + can result in overflowing product + (bad area is responsible for bad volume and bad facets reversal) */ + double cross[3][3]; + for (int i = 0; i < 3; i++) { + cross[i][0]=(((double)facet->vertex[i](1) * (double)facet->vertex[(i + 1) % 3](2)) - + ((double)facet->vertex[i](2) * (double)facet->vertex[(i + 1) % 3](1))); + cross[i][1]=(((double)facet->vertex[i](2) * (double)facet->vertex[(i + 1) % 3](0)) - + ((double)facet->vertex[i](0) * (double)facet->vertex[(i + 1) % 3](2))); + cross[i][2]=(((double)facet->vertex[i](0) * (double)facet->vertex[(i + 1) % 3](1)) - + ((double)facet->vertex[i](1) * (double)facet->vertex[(i + 1) % 3](0))); + } - stl_normal sum; - sum(0) = cross[0][0] + cross[1][0] + cross[2][0]; - sum(1) = cross[0][1] + cross[1][1] + cross[2][1]; - sum(2) = cross[0][2] + cross[1][2] + cross[2][2]; + stl_normal sum; + sum(0) = cross[0][0] + cross[1][0] + cross[2][0]; + sum(1) = cross[0][1] + cross[1][1] + cross[2][1]; + sum(2) = cross[0][2] + cross[1][2] + cross[2][2]; - // This should already be done. But just in case, let's do it again. - //FIXME this is questionable. the "sum" normal should be accurate, while the normal "n" may be calculated with a low accuracy. - stl_normal n; - stl_calculate_normal(n, facet); - stl_normalize_vector(n); - return 0.5f * n.dot(sum); + // This should already be done. But just in case, let's do it again. + //FIXME this is questionable. the "sum" normal should be accurate, while the normal "n" may be calculated with a low accuracy. + stl_normal n; + stl_calculate_normal(n, facet); + stl_normalize_vector(n); + return 0.5f * n.dot(sum); } -void stl_repair(stl_file *stl, - int fixall_flag, - int exact_flag, - int tolerance_flag, - float tolerance, - int increment_flag, - float increment, - int nearby_flag, - int iterations, - int remove_unconnected_flag, - int fill_holes_flag, - int normal_directions_flag, - int normal_values_flag, - int reverse_all_flag, - int verbose_flag) { - - int i; - int last_edges_fixed = 0; - - if (stl->error) return; - - if(exact_flag || fixall_flag || nearby_flag || remove_unconnected_flag - || fill_holes_flag || normal_directions_flag) { - if (verbose_flag) - printf("Checking exact...\n"); - exact_flag = 1; - stl_check_facets_exact(stl); - stl->stats.facets_w_1_bad_edge = - (stl->stats.connected_facets_2_edge - - stl->stats.connected_facets_3_edge); - stl->stats.facets_w_2_bad_edge = - (stl->stats.connected_facets_1_edge - - stl->stats.connected_facets_2_edge); - stl->stats.facets_w_3_bad_edge = - (stl->stats.number_of_facets - - stl->stats.connected_facets_1_edge); - } - - if(nearby_flag || fixall_flag) { - if(!tolerance_flag) { - tolerance = stl->stats.shortest_edge; - } - if(!increment_flag) { - increment = stl->stats.bounding_diameter / 10000.0; - } - - if(stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { - for(i = 0; i < iterations; i++) { - if(stl->stats.connected_facets_3_edge < - stl->stats.number_of_facets) { - if (verbose_flag) - printf("\ -Checking nearby. Tolerance= %f Iteration=%d of %d...", - tolerance, i + 1, iterations); - stl_check_facets_nearby(stl, tolerance); - if (verbose_flag) - printf(" Fixed %d edges.\n", - stl->stats.edges_fixed - last_edges_fixed); - last_edges_fixed = stl->stats.edges_fixed; - tolerance += increment; - } else { - if (verbose_flag) - printf("\ -All facets connected. No further nearby check necessary.\n"); - break; - } - } - } else { - if (verbose_flag) - printf("All facets connected. No nearby check necessary.\n"); - } - } - - if(remove_unconnected_flag || fixall_flag || fill_holes_flag) { - if(stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { - if (verbose_flag) - printf("Removing unconnected facets...\n"); - stl_remove_unconnected_facets(stl); - } else - if (verbose_flag) - printf("No unconnected need to be removed.\n"); - } - - if(fill_holes_flag || fixall_flag) { - if(stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { - if (verbose_flag) - printf("Filling holes...\n"); - stl_fill_holes(stl); - } else - if (verbose_flag) - printf("No holes need to be filled.\n"); - } - - if(reverse_all_flag) { - if (verbose_flag) - printf("Reversing all facets...\n"); - stl_reverse_all_facets(stl); - } - - if(normal_directions_flag || fixall_flag) { - if (verbose_flag) - printf("Checking normal directions...\n"); - stl_fix_normal_directions(stl); - } - - if(normal_values_flag || fixall_flag) { - if (verbose_flag) - printf("Checking normal values...\n"); - stl_fix_normal_values(stl); - } - - /* Always calculate the volume. It shouldn't take too long */ - if (verbose_flag) - printf("Calculating volume...\n"); - stl_calculate_volume(stl); - - if(exact_flag) { - if (verbose_flag) - printf("Verifying neighbors...\n"); - stl_verify_neighbors(stl); - } +static float get_volume(stl_file *stl) +{ + // Choose a point, any point as the reference. + stl_vertex p0 = stl->facet_start[0].vertex[0]; + float volume = 0.f; + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { + // Do dot product to get distance from point to plane. + float height = stl->facet_start[i].normal.dot(stl->facet_start[i].vertex[0] - p0); + float area = get_area(&stl->facet_start[i]); + volume += (area * height) / 3.0f; + } + return volume; +} + +void stl_calculate_volume(stl_file *stl) +{ + stl->stats.volume = get_volume(stl); + if (stl->stats.volume < 0.0) { + stl_reverse_all_facets(stl); + stl->stats.volume = -stl->stats.volume; + } +} + +void stl_repair( + stl_file *stl, + bool fixall_flag, + bool exact_flag, + bool tolerance_flag, + float tolerance, + bool increment_flag, + float increment, + bool nearby_flag, + int iterations, + bool remove_unconnected_flag, + bool fill_holes_flag, + bool normal_directions_flag, + bool normal_values_flag, + bool reverse_all_flag, + bool verbose_flag) +{ + if (exact_flag || fixall_flag || nearby_flag || remove_unconnected_flag || fill_holes_flag || normal_directions_flag) { + if (verbose_flag) + printf("Checking exact...\n"); + exact_flag = true; + stl_check_facets_exact(stl); + stl->stats.facets_w_1_bad_edge = (stl->stats.connected_facets_2_edge - stl->stats.connected_facets_3_edge); + stl->stats.facets_w_2_bad_edge = (stl->stats.connected_facets_1_edge - stl->stats.connected_facets_2_edge); + stl->stats.facets_w_3_bad_edge = (stl->stats.number_of_facets - stl->stats.connected_facets_1_edge); + } + + if (nearby_flag || fixall_flag) { + if (! tolerance_flag) + tolerance = stl->stats.shortest_edge; + if (! increment_flag) + increment = stl->stats.bounding_diameter / 10000.0; + } + + if (stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { + int last_edges_fixed = 0; + for (int i = 0; i < iterations; ++ i) { + if (stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { + if (verbose_flag) + printf("Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations); + stl_check_facets_nearby(stl, tolerance); + if (verbose_flag) + printf(" Fixed %d edges.\n", stl->stats.edges_fixed - last_edges_fixed); + last_edges_fixed = stl->stats.edges_fixed; + tolerance += increment; + } else { + if (verbose_flag) + printf("All facets connected. No further nearby check necessary.\n"); + break; + } + } + } else if (verbose_flag) + printf("All facets connected. No nearby check necessary.\n"); + + if (remove_unconnected_flag || fixall_flag || fill_holes_flag) { + if (stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { + if (verbose_flag) + printf("Removing unconnected facets...\n"); + stl_remove_unconnected_facets(stl); + } else if (verbose_flag) + printf("No unconnected need to be removed.\n"); + } + + if (fill_holes_flag || fixall_flag) { + if (stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { + if (verbose_flag) + printf("Filling holes...\n"); + stl_fill_holes(stl); + } else if (verbose_flag) + printf("No holes need to be filled.\n"); + } + + if (reverse_all_flag) { + if (verbose_flag) + printf("Reversing all facets...\n"); + stl_reverse_all_facets(stl); + } + + if (normal_directions_flag || fixall_flag) { + if (verbose_flag) + printf("Checking normal directions...\n"); + stl_fix_normal_directions(stl); + } + + if (normal_values_flag || fixall_flag) { + if (verbose_flag) + printf("Checking normal values...\n"); + stl_fix_normal_values(stl); + } + + // Always calculate the volume. It shouldn't take too long. + if (verbose_flag) + printf("Calculating volume...\n"); + stl_calculate_volume(stl); + + if (exact_flag) { + if (verbose_flag) + printf("Verifying neighbors...\n"); + stl_verify_neighbors(stl); + } } diff --git a/src/avrdude/CMakeLists.txt b/src/avrdude/CMakeLists.txt index 0e9b9e6d4d..8897200211 100644 --- a/src/avrdude/CMakeLists.txt +++ b/src/avrdude/CMakeLists.txt @@ -74,6 +74,10 @@ if (MSVC) windows/unistd.cpp windows/getopt.c ) +elseif (MINGW) + set(AVRDUDE_SOURCES ${AVRDUDE_SOURCES} + windows/utf8.c + ) endif() add_executable(avrdude-conf-gen conf-generate.cpp) @@ -81,13 +85,13 @@ add_executable(avrdude-conf-gen conf-generate.cpp) # Config file embedding add_custom_command( DEPENDS avrdude-conf-gen ${CMAKE_CURRENT_SOURCE_DIR}/avrdude-slic3r.conf - OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/avrdude-slic3r.conf.h - COMMAND $ avrdude-slic3r.conf avrdude_slic3r_conf > avrdude-slic3r.conf.h + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/avrdude-slic3r.conf.h + COMMAND $ avrdude-slic3r.conf avrdude_slic3r_conf ${CMAKE_CURRENT_BINARY_DIR}/avrdude-slic3r.conf.h WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) add_custom_target(gen_conf_h - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/avrdude-slic3r.conf.h + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/avrdude-slic3r.conf.h ) add_library(avrdude STATIC ${AVRDUDE_SOURCES}) @@ -96,7 +100,15 @@ add_dependencies(avrdude gen_conf_h) add_executable(avrdude-slic3r main-standalone.cpp) target_link_libraries(avrdude-slic3r avrdude) +encoding_check(avrdude) +encoding_check(avrdude-slic3r) + +# Make avrdude-slic3r.conf.h includable: +target_include_directories(avrdude SYSTEM PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + if (WIN32) target_compile_definitions(avrdude PRIVATE WIN32NATIVE=1) - target_include_directories(avrdude SYSTEM PRIVATE windows) # So that sources find the getopt.h windows drop-in + if(MSVC) + target_include_directories(avrdude SYSTEM PRIVATE windows) # So that sources find the getopt.h windows drop-in + endif(MSVC) endif() diff --git a/src/avrdude/arduino.c b/src/avrdude/arduino.c index 53e5ed8225..e6008adeb8 100644 --- a/src/avrdude/arduino.c +++ b/src/avrdude/arduino.c @@ -41,6 +41,7 @@ static int arduino_read_sig_bytes(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m) { unsigned char buf[32]; + (void)p; /* Signature byte reads are always 3 bytes. */ @@ -83,9 +84,9 @@ static int arduino_read_sig_bytes(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m) static int prusa_init_external_flash(PROGRAMMER * pgm) { // Note: send/receive as in _the firmare_ send & receives - const char entry_magic_send [] = "start\n"; - const char entry_magic_receive[] = "w25x20cl_enter\n"; - const char entry_magic_cfm [] = "w25x20cl_cfm\n"; + const char entry_magic_send[] = "start\n"; + const unsigned char entry_magic_receive[] = "w25x20cl_enter\n"; + const char entry_magic_cfm[] = "w25x20cl_cfm\n"; const size_t buffer_len = 32; // Should be large enough for the above messages int res; @@ -94,7 +95,7 @@ static int prusa_init_external_flash(PROGRAMMER * pgm) // 1. receive the "start" command recv_size = sizeof(entry_magic_send) - 1; - res = serial_recv(&pgm->fd, buffer, recv_size); + res = serial_recv(&pgm->fd, (unsigned char *)buffer, recv_size); if (res < 0) { avrdude_message(MSG_INFO, "%s: prusa_init_external_flash(): MK3 printer did not boot up on time or serial communication failed\n", progname); return -1; @@ -111,7 +112,7 @@ static int prusa_init_external_flash(PROGRAMMER * pgm) // 3. Receive the entry confirmation command recv_size = sizeof(entry_magic_cfm) - 1; - res = serial_recv(&pgm->fd, buffer, recv_size); + res = serial_recv(&pgm->fd, (unsigned char *)buffer, recv_size); if (res < 0) { avrdude_message(MSG_INFO, "%s: prusa_init_external_flash(): MK3 printer did not boot up on time or serial communication failed\n", progname); return -1; @@ -142,7 +143,7 @@ static int arduino_open(PROGRAMMER * pgm, char * port) // Sometimes there may be line noise generating input on the printer's USB-to-serial IC // Here we try to clean its input buffer with a sequence of newlines (a minimum of 9 is needed): - const char cleanup_newlines[] = "\n\n\n\n\n\n\n\n\n\n"; + const unsigned char cleanup_newlines[] = "\n\n\n\n\n\n\n\n\n\n"; if (serial_send(&pgm->fd, cleanup_newlines, sizeof(cleanup_newlines) - 1) < 0) { return -1; } diff --git a/src/avrdude/avr.c b/src/avrdude/avr.c index 73dcaf4ffe..defae75d50 100644 --- a/src/avrdude/avr.c +++ b/src/avrdude/avr.c @@ -341,7 +341,7 @@ int avr_read(PROGRAMMER * pgm, AVRPART * p, char * memtype, avr_tpi_setup_rw(pgm, mem, 0, TPI_NVMCMD_NO_OPERATION); /* load bytes */ - for (lastaddr = i = 0; i < mem->size; i++) { + for (lastaddr = i = 0; i < (unsigned)mem->size; i++) { RETURN_IF_CANCEL(); if (vmem == NULL || (vmem->tags[i] & TAG_ALLOCATED) != 0) @@ -374,7 +374,7 @@ int avr_read(PROGRAMMER * pgm, AVRPART * p, char * memtype, /* quickly scan number of pages to be written to first */ for (pageaddr = 0, npages = 0; - pageaddr < mem->size; + pageaddr < (unsigned)mem->size; pageaddr += mem->page_size) { /* check whether this page must be read */ for (i = pageaddr; @@ -391,7 +391,7 @@ int avr_read(PROGRAMMER * pgm, AVRPART * p, char * memtype, } for (pageaddr = 0, failure = 0, nread = 0; - !failure && pageaddr < mem->size; + !failure && pageaddr < (unsigned)mem->size; pageaddr += mem->page_size) { RETURN_IF_CANCEL(); /* check whether this page must be read */ @@ -437,7 +437,7 @@ int avr_read(PROGRAMMER * pgm, AVRPART * p, char * memtype, } } - for (i=0; i < mem->size; i++) { + for (i = 0; i < (unsigned)mem->size; i++) { RETURN_IF_CANCEL(); if (vmem == NULL || (vmem->tags[i] & TAG_ALLOCATED) != 0) @@ -634,18 +634,18 @@ int avr_write_byte_default(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, writeop = mem->op[AVR_OP_WRITE_HI]; else writeop = mem->op[AVR_OP_WRITE_LO]; - caddr = addr / 2; + caddr = (unsigned short)(addr / 2); } else if (mem->paged && mem->op[AVR_OP_LOADPAGE_LO]) { if (addr & 0x01) writeop = mem->op[AVR_OP_LOADPAGE_HI]; else writeop = mem->op[AVR_OP_LOADPAGE_LO]; - caddr = addr / 2; + caddr = (unsigned short)(addr / 2); } else { writeop = mem->op[AVR_OP_WRITE]; - caddr = addr; + caddr = (unsigned short)addr; } if (writeop == NULL) { @@ -723,7 +723,7 @@ int avr_write_byte_default(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, gettimeofday (&tv, NULL); prog_time = (tv.tv_sec * 1000000) + tv.tv_usec; } while ((r != data) && - ((prog_time-start_time) < mem->max_write_delay)); + ((prog_time - start_time) < (unsigned long)mem->max_write_delay)); } /* @@ -878,7 +878,7 @@ int avr_write(PROGRAMMER * pgm, AVRPART * p, char * memtype, int size, } /* write words, low byte first */ - for (lastaddr = i = 0; i < wsize; i += 2) { + for (lastaddr = i = 0; i < (unsigned)wsize; i += 2) { RETURN_IF_CANCEL(); if ((m->tags[i] & TAG_ALLOCATED) != 0 || (m->tags[i + 1] & TAG_ALLOCATED) != 0) { @@ -915,7 +915,7 @@ int avr_write(PROGRAMMER * pgm, AVRPART * p, char * memtype, int size, /* quickly scan number of pages to be written to first */ for (pageaddr = 0, npages = 0; - pageaddr < wsize; + pageaddr < (unsigned)wsize; pageaddr += m->page_size) { /* check whether this page must be written to */ for (i = pageaddr; @@ -928,7 +928,7 @@ int avr_write(PROGRAMMER * pgm, AVRPART * p, char * memtype, int size, } for (pageaddr = 0, failure = 0, nwritten = 0; - !failure && pageaddr < wsize; + !failure && pageaddr < (unsigned)wsize; pageaddr += m->page_size) { RETURN_IF_CANCEL(); /* check whether this page must be written to */ @@ -968,7 +968,7 @@ int avr_write(PROGRAMMER * pgm, AVRPART * p, char * memtype, int size, page_tainted = 0; flush_page = 0; - for (i=0; ibuf[i]; report_progress(i, wsize, NULL); diff --git a/src/avrdude/avr910.c b/src/avrdude/avr910.c index aa5cc07a9d..17a4ab69fd 100644 --- a/src/avrdude/avr910.c +++ b/src/avrdude/avr910.c @@ -676,7 +676,7 @@ static int avr910_paged_load(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, avr910_set_addr(pgm, addr / rd_size); while (addr < max_addr) { - if ((max_addr - addr) < blocksize) { + if ((max_addr - addr) < (unsigned)blocksize) { blocksize = max_addr - addr; } cmd[1] = (blocksize >> 8) & 0xff; diff --git a/src/avrdude/avrdude-slic3r.conf.h b/src/avrdude/avrdude-slic3r.conf.h deleted file mode 100644 index 7cc901336b..0000000000 --- a/src/avrdude/avrdude-slic3r.conf.h +++ /dev/null @@ -1,1188 +0,0 @@ -/* WARN: This file is auto-generated from `avrdude-slic3r.conf` */ -unsigned char avrdude_slic3r_conf[] = { - 0x0a, 0x23, 0x0a, 0x23, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, - 0x20, 0x61, 0x20, 0x62, 0x61, 0x73, 0x69, 0x63, 0x20, 0x6d, 0x69, 0x6e, - 0x69, 0x6d, 0x61, 0x6c, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x20, - 0x66, 0x69, 0x6c, 0x65, 0x20, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, - 0x64, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, - 0x76, 0x72, 0x64, 0x75, 0x64, 0x65, 0x2d, 0x73, 0x6c, 0x69, 0x63, 0x33, - 0x72, 0x20, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x0a, 0x23, 0x20, 0x73, - 0x6f, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x69, 0x74, 0x20, 0x63, 0x61, - 0x6e, 0x20, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, - 0x73, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x6c, 0x6f, 0x6e, 0x65, 0x20, 0x6d, - 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x0a, 0x23, 0x0a, 0x23, 0x20, 0x4f, - 0x6e, 0x6c, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x69, 0x74, 0x73, - 0x20, 0x75, 0x73, 0x65, 0x66, 0x75, 0x6c, 0x20, 0x66, 0x6f, 0x72, 0x20, - 0x50, 0x72, 0x75, 0x73, 0x61, 0x33, 0x44, 0x20, 0x64, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x73, 0x20, 0x77, 0x65, 0x72, 0x65, 0x20, 0x63, 0x6f, 0x70, - 0x69, 0x65, 0x64, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x66, 0x72, 0x6f, - 0x6d, 0x20, 0x61, 0x76, 0x72, 0x64, 0x75, 0x64, 0x65, 0x2e, 0x63, 0x6f, - 0x6e, 0x66, 0x0a, 0x23, 0x20, 0x49, 0x66, 0x20, 0x6e, 0x65, 0x65, 0x64, - 0x65, 0x64, 0x2c, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x63, - 0x61, 0x6e, 0x20, 0x73, 0x74, 0x69, 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x20, - 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x20, - 0x61, 0x76, 0x72, 0x64, 0x75, 0x64, 0x65, 0x2d, 0x73, 0x6c, 0x69, 0x63, - 0x33, 0x72, 0x0a, 0x23, 0x20, 0x76, 0x69, 0x61, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x2d, 0x43, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2d, - 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2e, - 0x0a, 0x23, 0x0a, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, - 0x6d, 0x65, 0x72, 0x0a, 0x20, 0x20, 0x69, 0x64, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x77, 0x69, 0x72, 0x69, 0x6e, 0x67, 0x22, 0x3b, 0x0a, - 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x57, - 0x69, 0x72, 0x69, 0x6e, 0x67, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x74, 0x79, - 0x70, 0x65, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x77, 0x69, 0x72, 0x69, 0x6e, - 0x67, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x20, 0x3d, 0x20, - 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3b, 0x0a, 0x3b, 0x0a, 0x0a, 0x70, - 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x6d, 0x65, 0x72, 0x0a, 0x20, 0x20, - 0x69, 0x64, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x61, 0x72, 0x64, - 0x75, 0x69, 0x6e, 0x6f, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x64, 0x65, 0x73, - 0x63, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x41, 0x72, 0x64, 0x75, 0x69, 0x6e, - 0x6f, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x22, 0x3b, - 0x0a, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x20, 0x3d, 0x20, 0x73, 0x65, 0x72, - 0x69, 0x61, 0x6c, 0x3b, 0x0a, 0x3b, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x67, - 0x72, 0x61, 0x6d, 0x6d, 0x65, 0x72, 0x0a, 0x20, 0x20, 0x69, 0x64, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x61, 0x76, 0x72, 0x31, 0x30, 0x39, - 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x20, 0x20, 0x3d, - 0x20, 0x22, 0x41, 0x74, 0x6d, 0x65, 0x6c, 0x20, 0x41, 0x70, 0x70, 0x4e, - 0x6f, 0x74, 0x65, 0x20, 0x41, 0x56, 0x52, 0x31, 0x30, 0x39, 0x20, 0x42, - 0x6f, 0x6f, 0x74, 0x20, 0x4c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x22, 0x3b, - 0x0a, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x20, 0x3d, 0x20, 0x22, - 0x62, 0x75, 0x74, 0x74, 0x65, 0x72, 0x66, 0x6c, 0x79, 0x22, 0x3b, 0x0a, - 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x20, 0x3d, 0x20, 0x73, 0x65, 0x72, 0x69, - 0x61, 0x6c, 0x3b, 0x0a, 0x3b, 0x0a, 0x0a, 0x0a, 0x23, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x23, 0x20, - 0x41, 0x54, 0x6d, 0x65, 0x67, 0x61, 0x32, 0x35, 0x36, 0x30, 0x0a, 0x23, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x74, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, - 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x6d, 0x32, 0x35, 0x36, 0x30, - 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x41, 0x54, 0x6d, 0x65, 0x67, 0x61, 0x32, 0x35, 0x36, - 0x30, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x67, 0x6e, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x30, 0x78, 0x31, 0x65, 0x20, 0x30, 0x78, 0x39, 0x38, - 0x20, 0x30, 0x78, 0x30, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, - 0x61, 0x73, 0x5f, 0x6a, 0x74, 0x61, 0x67, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x79, 0x65, 0x73, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x73, 0x74, 0x6b, 0x35, 0x30, 0x30, 0x5f, 0x64, 0x65, - 0x76, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, - 0x42, 0x32, 0x3b, 0x0a, 0x23, 0x20, 0x20, 0x20, 0x20, 0x61, 0x76, 0x72, - 0x39, 0x31, 0x30, 0x5f, 0x64, 0x65, 0x76, 0x63, 0x6f, 0x64, 0x65, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x34, 0x33, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x63, 0x68, 0x69, 0x70, 0x5f, 0x65, 0x72, 0x61, 0x73, 0x65, - 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, - 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x67, 0x65, 0x6c, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x30, 0x78, 0x44, 0x37, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x62, 0x73, 0x32, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x41, 0x30, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x73, 0x65, 0x74, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x64, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x70, 0x67, 0x6d, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, - 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, - 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, - 0x20, 0x31, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x31, 0x20, 0x31, 0x22, - 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x78, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x68, - 0x69, 0x70, 0x5f, 0x65, 0x72, 0x61, 0x73, 0x65, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, - 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x22, 0x3b, 0x0a, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x32, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x73, 0x74, 0x61, 0x62, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x31, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x63, 0x6d, 0x64, 0x65, 0x78, 0x65, 0x64, 0x65, 0x6c, 0x61, 0x79, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x32, 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x73, 0x79, 0x6e, 0x63, 0x68, 0x6c, 0x6f, 0x6f, 0x70, 0x73, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x33, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x62, 0x79, 0x74, 0x65, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, - 0x6c, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, 0x6c, 0x6c, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x35, - 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, 0x65, 0x64, 0x65, - 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, 0x73, 0x74, 0x64, 0x65, 0x6c, 0x61, - 0x79, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x70, 0x6f, 0x6c, 0x6c, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x73, - 0x74, 0x61, 0x63, 0x6b, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x78, 0x30, 0x45, 0x2c, - 0x20, 0x30, 0x78, 0x31, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x30, 0x46, 0x2c, - 0x20, 0x30, 0x78, 0x31, 0x46, 0x2c, 0x20, 0x30, 0x78, 0x32, 0x45, 0x2c, - 0x20, 0x30, 0x78, 0x33, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x32, 0x46, 0x2c, - 0x20, 0x30, 0x78, 0x33, 0x46, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x30, 0x78, 0x34, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x35, - 0x45, 0x2c, 0x20, 0x30, 0x78, 0x34, 0x46, 0x2c, 0x20, 0x30, 0x78, 0x35, - 0x46, 0x2c, 0x20, 0x30, 0x78, 0x36, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x37, - 0x45, 0x2c, 0x20, 0x30, 0x78, 0x36, 0x46, 0x2c, 0x20, 0x30, 0x78, 0x37, - 0x46, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, - 0x78, 0x36, 0x36, 0x2c, 0x20, 0x30, 0x78, 0x37, 0x36, 0x2c, 0x20, 0x30, - 0x78, 0x36, 0x37, 0x2c, 0x20, 0x30, 0x78, 0x37, 0x37, 0x2c, 0x20, 0x30, - 0x78, 0x36, 0x41, 0x2c, 0x20, 0x30, 0x78, 0x37, 0x41, 0x2c, 0x20, 0x30, - 0x78, 0x36, 0x42, 0x2c, 0x20, 0x30, 0x78, 0x37, 0x42, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x78, 0x42, 0x45, 0x2c, - 0x20, 0x30, 0x78, 0x46, 0x44, 0x2c, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2c, - 0x20, 0x30, 0x78, 0x30, 0x31, 0x2c, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2c, - 0x20, 0x30, 0x78, 0x30, 0x30, 0x2c, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2c, - 0x20, 0x30, 0x78, 0x30, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, - 0x76, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x74, 0x61, 0x62, 0x64, 0x65, - 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x30, 0x30, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x6d, 0x6f, - 0x64, 0x65, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, - 0x61, 0x74, 0x63, 0x68, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x73, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x35, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x74, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x76, 0x74, - 0x67, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, 0x77, - 0x65, 0x72, 0x6f, 0x66, 0x66, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x35, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x72, 0x65, 0x73, 0x65, 0x74, 0x64, 0x65, 0x6c, 0x61, - 0x79, 0x6d, 0x73, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x73, 0x65, - 0x74, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x75, 0x73, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x68, 0x76, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x74, 0x61, 0x62, - 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, - 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x68, 0x69, 0x70, 0x65, - 0x72, 0x61, 0x73, 0x65, 0x70, 0x75, 0x6c, 0x73, 0x65, 0x77, 0x69, 0x64, - 0x74, 0x68, 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x63, 0x68, 0x69, 0x70, 0x65, 0x72, 0x61, 0x73, 0x65, 0x70, 0x6f, 0x6c, - 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x20, 0x3d, 0x20, 0x31, - 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, - 0x61, 0x6d, 0x66, 0x75, 0x73, 0x65, 0x70, 0x75, 0x6c, 0x73, 0x65, 0x77, - 0x69, 0x64, 0x74, 0x68, 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x66, 0x75, 0x73, - 0x65, 0x70, 0x6f, 0x6c, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, - 0x20, 0x3d, 0x20, 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, - 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x6c, 0x6f, 0x63, 0x6b, 0x70, 0x75, 0x6c, - 0x73, 0x65, 0x77, 0x69, 0x64, 0x74, 0x68, 0x20, 0x3d, 0x20, 0x30, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, - 0x6c, 0x6f, 0x63, 0x6b, 0x70, 0x6f, 0x6c, 0x6c, 0x74, 0x69, 0x6d, 0x65, - 0x6f, 0x75, 0x74, 0x20, 0x3d, 0x20, 0x35, 0x3b, 0x0a, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x69, 0x64, 0x72, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x30, 0x78, 0x33, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x70, - 0x6d, 0x63, 0x72, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x35, 0x37, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x61, 0x6d, 0x70, 0x7a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x33, 0x62, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x66, 0x75, 0x6c, 0x6c, 0x70, - 0x61, 0x67, 0x65, 0x62, 0x69, 0x74, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x20, 0x3d, 0x20, 0x6e, 0x6f, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x6f, 0x63, 0x64, 0x72, 0x65, 0x76, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x34, 0x3b, - 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, - 0x20, 0x22, 0x65, 0x65, 0x70, 0x72, 0x6f, 0x6d, 0x22, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x67, 0x65, 0x64, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x6e, 0x6f, 0x3b, 0x20, 0x2f, 0x2a, 0x20, 0x6c, 0x65, 0x61, 0x76, 0x65, - 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x22, 0x6e, 0x6f, 0x22, 0x20, 0x2a, - 0x2f, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, - 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x38, 0x3b, 0x20, 0x20, 0x2f, 0x2a, 0x20, 0x66, - 0x6f, 0x72, 0x20, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x20, - 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x20, - 0x2a, 0x2f, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, - 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x34, 0x30, 0x39, 0x36, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, - 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, - 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, - 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, - 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, - 0x65, 0x61, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x70, 0x31, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x30, 0x30, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x62, - 0x61, 0x63, 0x6b, 0x5f, 0x70, 0x32, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x30, 0x78, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, - 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x61, 0x31, 0x31, - 0x20, 0x61, 0x31, 0x30, 0x20, 0x20, 0x61, 0x39, 0x20, 0x20, 0x61, 0x38, - 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, 0x61, - 0x36, 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x61, 0x33, 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, 0x31, - 0x20, 0x20, 0x61, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, - 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, - 0x6f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, - 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x72, 0x69, 0x74, - 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x20, 0x61, 0x31, 0x31, 0x20, 0x61, 0x31, 0x30, 0x20, 0x20, 0x61, - 0x39, 0x20, 0x20, 0x61, 0x38, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, - 0x61, 0x37, 0x20, 0x20, 0x61, 0x36, 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, - 0x61, 0x34, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x33, 0x20, 0x20, 0x61, - 0x32, 0x20, 0x20, 0x61, 0x31, 0x20, 0x20, 0x61, 0x30, 0x22, 0x2c, 0x20, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, - 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, - 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x61, 0x64, - 0x70, 0x61, 0x67, 0x65, 0x5f, 0x6c, 0x6f, 0x20, 0x3d, 0x20, 0x22, 0x20, - 0x20, 0x31, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x22, 0x2c, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, - 0x31, 0x20, 0x20, 0x61, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, - 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, - 0x20, 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x77, 0x72, 0x69, - 0x74, 0x65, 0x70, 0x61, 0x67, 0x65, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, - 0x31, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, - 0x20, 0x20, 0x20, 0x61, 0x31, 0x31, 0x20, 0x61, 0x31, 0x30, 0x20, 0x20, - 0x61, 0x39, 0x20, 0x20, 0x61, 0x38, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, 0x61, - 0x36, 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x61, 0x33, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x78, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x6d, 0x6f, 0x64, 0x65, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x34, 0x31, 0x3b, 0x0a, - 0x20, 0x20, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x31, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, - 0x69, 0x7a, 0x65, 0x20, 0x3d, 0x20, 0x38, 0x3b, 0x0a, 0x20, 0x20, 0x72, - 0x65, 0x61, 0x64, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x3d, 0x20, 0x32, - 0x35, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, - 0x22, 0x66, 0x6c, 0x61, 0x73, 0x68, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x67, 0x65, 0x64, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x79, 0x65, - 0x73, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, - 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x32, 0x36, 0x32, 0x31, 0x34, 0x34, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x67, - 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x32, 0x35, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x67, 0x65, - 0x73, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x30, - 0x32, 0x34, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, - 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x34, 0x35, 0x30, 0x30, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x5f, - 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, - 0x3d, 0x20, 0x34, 0x35, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x62, 0x61, 0x63, 0x6b, - 0x5f, 0x70, 0x31, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, - 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x72, 0x65, 0x61, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x70, 0x32, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x30, 0x30, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, - 0x5f, 0x6c, 0x6f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x31, 0x35, 0x20, 0x61, - 0x31, 0x34, 0x20, 0x61, 0x31, 0x33, 0x20, 0x61, 0x31, 0x32, 0x20, 0x20, - 0x20, 0x20, 0x61, 0x31, 0x31, 0x20, 0x61, 0x31, 0x30, 0x20, 0x20, 0x61, - 0x39, 0x20, 0x20, 0x61, 0x38, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, - 0x61, 0x37, 0x20, 0x20, 0x61, 0x36, 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, - 0x61, 0x34, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x33, 0x20, 0x20, 0x61, - 0x32, 0x20, 0x20, 0x61, 0x31, 0x20, 0x20, 0x61, 0x30, 0x22, 0x2c, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, - 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, - 0x6f, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x68, 0x69, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x61, 0x31, 0x35, 0x20, 0x61, 0x31, 0x34, 0x20, 0x61, 0x31, 0x33, 0x20, - 0x61, 0x31, 0x32, 0x20, 0x20, 0x20, 0x20, 0x61, 0x31, 0x31, 0x20, 0x61, - 0x31, 0x30, 0x20, 0x20, 0x61, 0x39, 0x20, 0x20, 0x61, 0x38, 0x22, 0x2c, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, 0x61, 0x36, 0x20, - 0x20, 0x61, 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x61, 0x33, 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, 0x31, 0x20, 0x20, - 0x61, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x6f, 0x20, - 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, - 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x61, 0x64, 0x70, 0x61, - 0x67, 0x65, 0x5f, 0x6c, 0x6f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, - 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, - 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, - 0x20, 0x20, 0x78, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, - 0x20, 0x20, 0x61, 0x36, 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, 0x61, 0x34, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x33, 0x20, 0x20, 0x61, 0x32, 0x20, - 0x20, 0x61, 0x31, 0x20, 0x20, 0x61, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, - 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x20, - 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x22, - 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, - 0x6f, 0x61, 0x64, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x68, 0x69, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, - 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x22, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, 0x61, 0x36, 0x20, 0x20, 0x61, - 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x33, - 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, 0x31, 0x20, 0x20, 0x61, 0x30, - 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, - 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, - 0x20, 0x20, 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x70, 0x61, 0x67, - 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, - 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x61, 0x31, 0x35, 0x20, 0x61, 0x31, 0x34, 0x20, 0x61, - 0x31, 0x33, 0x20, 0x61, 0x31, 0x32, 0x20, 0x20, 0x20, 0x20, 0x61, 0x31, - 0x31, 0x20, 0x61, 0x31, 0x30, 0x20, 0x20, 0x61, 0x39, 0x20, 0x20, 0x61, - 0x38, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x78, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x22, 0x3b, 0x0a, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x61, - 0x64, 0x5f, 0x65, 0x78, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x31, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x61, 0x31, 0x36, 0x22, 0x2c, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x6d, 0x6f, 0x64, 0x65, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x34, 0x31, 0x3b, 0x0a, - 0x20, 0x20, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x31, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, - 0x69, 0x7a, 0x65, 0x20, 0x3d, 0x20, 0x32, 0x35, 0x36, 0x3b, 0x0a, 0x20, - 0x20, 0x72, 0x65, 0x61, 0x64, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x3d, - 0x20, 0x32, 0x35, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, - 0x79, 0x20, 0x22, 0x6c, 0x66, 0x75, 0x73, 0x65, 0x22, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, - 0x72, 0x69, 0x74, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, - 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, - 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x20, - 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, - 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, - 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, - 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, - 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x68, 0x66, 0x75, - 0x73, 0x65, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, - 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, - 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, - 0x30, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x69, 0x20, - 0x69, 0x20, 0x69, 0x20, 0x69, 0x20, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, - 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x30, 0x20, 0x31, - 0x20, 0x30, 0x20, 0x31, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, - 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, - 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x22, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x69, - 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, - 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x72, - 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, - 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, - 0x79, 0x20, 0x22, 0x65, 0x66, 0x75, 0x73, 0x65, 0x22, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, - 0x72, 0x69, 0x74, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, - 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, - 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x31, - 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x20, 0x78, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, - 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, - 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, - 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, - 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x6c, 0x6f, 0x63, - 0x6b, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, - 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x30, - 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, - 0x6f, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, 0x20, - 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x31, 0x20, 0x78, - 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x22, 0x2c, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x20, 0x31, 0x20, - 0x31, 0x20, 0x69, 0x20, 0x69, 0x20, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, - 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, - 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, - 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, - 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, - 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x63, 0x61, 0x6c, 0x69, 0x62, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x30, 0x20, 0x30, 0x20, 0x31, 0x20, 0x31, 0x20, 0x20, - 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, - 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x22, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x73, 0x69, 0x67, - 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x33, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, - 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x22, 0x30, 0x20, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, - 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x78, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x78, 0x20, 0x61, 0x31, 0x20, 0x61, 0x30, 0x20, 0x20, - 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, - 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, - 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x20, - 0x20, 0x3b, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x23, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x23, 0x20, - 0x41, 0x54, 0x6d, 0x65, 0x67, 0x61, 0x33, 0x32, 0x75, 0x34, 0x0a, 0x23, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x74, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, - 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x6d, 0x33, 0x32, 0x75, 0x34, - 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x41, 0x54, 0x6d, 0x65, 0x67, 0x61, 0x33, 0x32, 0x55, - 0x34, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x67, 0x6e, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x30, 0x78, 0x31, 0x65, 0x20, 0x30, 0x78, 0x39, 0x35, - 0x20, 0x30, 0x78, 0x38, 0x37, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x75, - 0x73, 0x62, 0x70, 0x69, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x32, 0x66, 0x66, 0x34, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x61, 0x73, 0x5f, 0x6a, 0x74, - 0x61, 0x67, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x79, 0x65, 0x73, 0x3b, 0x0a, 0x23, 0x20, 0x20, 0x20, 0x20, 0x73, - 0x74, 0x6b, 0x35, 0x30, 0x30, 0x5f, 0x64, 0x65, 0x76, 0x63, 0x6f, 0x64, - 0x65, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x42, 0x32, 0x3b, 0x0a, - 0x23, 0x20, 0x20, 0x20, 0x20, 0x61, 0x76, 0x72, 0x39, 0x31, 0x30, 0x5f, - 0x64, 0x65, 0x76, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x30, 0x78, 0x34, 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x68, - 0x69, 0x70, 0x5f, 0x65, 0x72, 0x61, 0x73, 0x65, 0x5f, 0x64, 0x65, 0x6c, - 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x70, 0x61, 0x67, 0x65, 0x6c, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, - 0x44, 0x37, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x73, 0x32, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x30, 0x78, 0x41, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x72, 0x65, 0x73, 0x65, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x64, 0x65, 0x64, 0x69, - 0x63, 0x61, 0x74, 0x65, 0x64, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, - 0x67, 0x6d, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, 0x20, 0x30, 0x20, 0x31, - 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x31, 0x20, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x22, 0x3b, - 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x68, 0x69, 0x70, 0x5f, 0x65, - 0x72, 0x61, 0x73, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x22, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x31, - 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x31, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x78, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x32, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, - 0x61, 0x62, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x31, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6d, 0x64, - 0x65, 0x78, 0x65, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x32, 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x79, 0x6e, - 0x63, 0x68, 0x6c, 0x6f, 0x6f, 0x70, 0x73, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x33, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x79, 0x74, - 0x65, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, 0x6c, 0x6c, 0x69, 0x6e, - 0x64, 0x65, 0x78, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x33, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x70, 0x6f, 0x6c, 0x6c, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x35, 0x33, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x70, 0x72, 0x65, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x70, 0x6f, 0x73, 0x74, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, 0x6c, - 0x6c, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x31, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x70, 0x5f, - 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x73, 0x74, 0x61, 0x63, 0x6b, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x30, 0x78, 0x30, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x31, - 0x45, 0x2c, 0x20, 0x30, 0x78, 0x30, 0x46, 0x2c, 0x20, 0x30, 0x78, 0x31, - 0x46, 0x2c, 0x20, 0x30, 0x78, 0x32, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x33, - 0x45, 0x2c, 0x20, 0x30, 0x78, 0x32, 0x46, 0x2c, 0x20, 0x30, 0x78, 0x33, - 0x46, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, - 0x78, 0x34, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x35, 0x45, 0x2c, 0x20, 0x30, - 0x78, 0x34, 0x46, 0x2c, 0x20, 0x30, 0x78, 0x35, 0x46, 0x2c, 0x20, 0x30, - 0x78, 0x36, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x37, 0x45, 0x2c, 0x20, 0x30, - 0x78, 0x36, 0x46, 0x2c, 0x20, 0x30, 0x78, 0x37, 0x46, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x78, 0x36, 0x36, 0x2c, - 0x20, 0x30, 0x78, 0x37, 0x36, 0x2c, 0x20, 0x30, 0x78, 0x36, 0x37, 0x2c, - 0x20, 0x30, 0x78, 0x37, 0x37, 0x2c, 0x20, 0x30, 0x78, 0x36, 0x41, 0x2c, - 0x20, 0x30, 0x78, 0x37, 0x41, 0x2c, 0x20, 0x30, 0x78, 0x36, 0x42, 0x2c, - 0x20, 0x30, 0x78, 0x37, 0x42, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x30, 0x78, 0x42, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x46, - 0x44, 0x2c, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2c, 0x20, 0x30, 0x78, 0x30, - 0x31, 0x2c, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2c, 0x20, 0x30, 0x78, 0x30, - 0x30, 0x2c, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2c, 0x20, 0x30, 0x78, 0x30, - 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x76, 0x65, 0x6e, 0x74, - 0x65, 0x72, 0x73, 0x74, 0x61, 0x62, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x6d, 0x6f, 0x64, 0x65, 0x64, 0x65, - 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x61, 0x74, 0x63, 0x68, - 0x63, 0x79, 0x63, 0x6c, 0x65, 0x73, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x74, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x76, 0x74, 0x67, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x6f, 0x66, - 0x66, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x31, 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, - 0x65, 0x73, 0x65, 0x74, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x6d, 0x73, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x73, 0x65, 0x74, 0x64, 0x65, 0x6c, - 0x61, 0x79, 0x75, 0x73, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x76, 0x6c, - 0x65, 0x61, 0x76, 0x65, 0x73, 0x74, 0x61, 0x62, 0x64, 0x65, 0x6c, 0x61, - 0x79, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x35, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x63, 0x68, 0x69, 0x70, 0x65, 0x72, 0x61, 0x73, 0x65, - 0x70, 0x75, 0x6c, 0x73, 0x65, 0x77, 0x69, 0x64, 0x74, 0x68, 0x20, 0x3d, - 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x68, 0x69, 0x70, - 0x65, 0x72, 0x61, 0x73, 0x65, 0x70, 0x6f, 0x6c, 0x6c, 0x74, 0x69, 0x6d, - 0x65, 0x6f, 0x75, 0x74, 0x20, 0x3d, 0x20, 0x31, 0x30, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x66, 0x75, - 0x73, 0x65, 0x70, 0x75, 0x6c, 0x73, 0x65, 0x77, 0x69, 0x64, 0x74, 0x68, - 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, - 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x66, 0x75, 0x73, 0x65, 0x70, 0x6f, 0x6c, - 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x20, 0x3d, 0x20, 0x35, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, - 0x6d, 0x6c, 0x6f, 0x63, 0x6b, 0x70, 0x75, 0x6c, 0x73, 0x65, 0x77, 0x69, - 0x64, 0x74, 0x68, 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x6c, 0x6f, 0x63, 0x6b, - 0x70, 0x6f, 0x6c, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x20, - 0x3d, 0x20, 0x35, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x64, - 0x72, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x33, 0x31, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x70, 0x6d, 0x63, 0x72, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x35, 0x37, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x72, 0x61, 0x6d, 0x70, 0x7a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x30, 0x78, 0x33, 0x62, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x61, 0x6c, - 0x6c, 0x6f, 0x77, 0x66, 0x75, 0x6c, 0x6c, 0x70, 0x61, 0x67, 0x65, 0x62, - 0x69, 0x74, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x3d, 0x20, 0x6e, - 0x6f, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x63, 0x64, 0x72, - 0x65, 0x76, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x33, 0x3b, 0x0a, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x65, 0x65, - 0x70, 0x72, 0x6f, 0x6d, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x70, 0x61, 0x67, 0x65, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x6e, 0x6f, 0x3b, 0x20, - 0x2f, 0x2a, 0x20, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x20, 0x74, 0x68, 0x69, - 0x73, 0x20, 0x22, 0x6e, 0x6f, 0x22, 0x20, 0x2a, 0x2f, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, - 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x34, 0x3b, 0x20, 0x20, 0x2f, 0x2a, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x70, - 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x20, 0x70, 0x72, 0x6f, 0x67, - 0x72, 0x61, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x20, 0x2a, 0x2f, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x31, 0x30, 0x32, 0x34, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, - 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, - 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, - 0x61, 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, - 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x62, - 0x61, 0x63, 0x6b, 0x5f, 0x70, 0x31, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x30, 0x78, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x5f, - 0x70, 0x32, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x30, - 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, - 0x65, 0x61, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, 0x61, 0x31, 0x30, - 0x20, 0x20, 0x61, 0x39, 0x20, 0x20, 0x61, 0x38, 0x22, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, 0x61, 0x36, 0x20, 0x20, 0x61, - 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x33, - 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, 0x31, 0x20, 0x20, 0x61, 0x30, - 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, - 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, - 0x20, 0x20, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, - 0x20, 0x31, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x61, 0x31, 0x30, 0x20, 0x20, 0x61, 0x39, 0x20, 0x20, 0x61, - 0x38, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, - 0x61, 0x36, 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x61, 0x33, 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, - 0x31, 0x20, 0x20, 0x61, 0x30, 0x22, 0x2c, 0x20, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, - 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, - 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x22, 0x3b, - 0x0a, 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x61, 0x64, 0x70, 0x61, 0x67, 0x65, - 0x5f, 0x6c, 0x6f, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x31, 0x20, 0x20, - 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, 0x31, 0x20, 0x20, 0x61, - 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, - 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x20, - 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x22, - 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x70, 0x61, - 0x67, 0x65, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, - 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, - 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x61, 0x31, 0x30, 0x20, 0x20, 0x61, 0x39, 0x20, 0x20, - 0x61, 0x38, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, 0x61, 0x36, 0x20, 0x20, 0x61, - 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x33, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x22, 0x3b, - 0x0a, 0x0a, 0x20, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x30, 0x78, 0x34, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x64, 0x65, - 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x32, 0x30, 0x3b, 0x0a, - 0x20, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x69, 0x7a, 0x65, 0x20, - 0x3d, 0x20, 0x34, 0x3b, 0x0a, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x73, - 0x69, 0x7a, 0x65, 0x20, 0x20, 0x3d, 0x20, 0x32, 0x35, 0x36, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x66, 0x6c, 0x61, - 0x73, 0x68, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x70, 0x61, 0x67, 0x65, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x79, 0x65, 0x73, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x33, 0x32, 0x37, 0x36, 0x38, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, - 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x32, - 0x38, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6e, - 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x73, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x32, 0x35, 0x36, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, - 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, - 0x34, 0x35, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, - 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x34, 0x35, 0x30, 0x30, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, - 0x61, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x70, 0x31, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x62, 0x61, - 0x63, 0x6b, 0x5f, 0x70, 0x32, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x30, 0x78, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x6c, 0x6f, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x20, 0x20, 0x30, 0x20, 0x61, 0x31, 0x34, 0x20, 0x61, 0x31, 0x33, - 0x20, 0x61, 0x31, 0x32, 0x20, 0x20, 0x20, 0x20, 0x61, 0x31, 0x31, 0x20, - 0x61, 0x31, 0x30, 0x20, 0x20, 0x61, 0x39, 0x20, 0x20, 0x61, 0x38, 0x22, - 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, 0x61, 0x36, - 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x61, 0x33, 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, 0x31, 0x20, - 0x20, 0x61, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x6f, - 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, - 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x5f, - 0x68, 0x69, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x31, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x61, 0x31, - 0x34, 0x20, 0x61, 0x31, 0x33, 0x20, 0x61, 0x31, 0x32, 0x20, 0x20, 0x20, - 0x20, 0x61, 0x31, 0x31, 0x20, 0x61, 0x31, 0x30, 0x20, 0x20, 0x61, 0x39, - 0x20, 0x20, 0x61, 0x38, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x61, - 0x37, 0x20, 0x20, 0x61, 0x36, 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, 0x61, - 0x34, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x33, 0x20, 0x20, 0x61, 0x32, - 0x20, 0x20, 0x61, 0x31, 0x20, 0x20, 0x61, 0x30, 0x22, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, - 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, - 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, - 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x6c, 0x6f, 0x61, 0x64, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x6c, 0x6f, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x22, 0x2c, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x61, 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, - 0x33, 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, 0x31, 0x20, 0x20, 0x61, - 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x69, 0x20, 0x20, - 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, - 0x69, 0x20, 0x20, 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x61, 0x64, 0x70, 0x61, 0x67, - 0x65, 0x5f, 0x68, 0x69, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x78, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x61, 0x33, 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, - 0x61, 0x31, 0x20, 0x20, 0x61, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, - 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, - 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x22, 0x3b, - 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x72, - 0x69, 0x74, 0x65, 0x70, 0x61, 0x67, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x61, 0x31, - 0x35, 0x20, 0x61, 0x31, 0x34, 0x20, 0x61, 0x31, 0x33, 0x20, 0x61, 0x31, - 0x32, 0x20, 0x20, 0x20, 0x20, 0x61, 0x31, 0x31, 0x20, 0x61, 0x31, 0x30, - 0x20, 0x20, 0x61, 0x39, 0x20, 0x20, 0x61, 0x38, 0x22, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, 0x61, 0x36, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78, - 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, - 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, - 0x20, 0x20, 0x20, 0x78, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x6d, 0x6f, - 0x64, 0x65, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x34, 0x31, - 0x3b, 0x0a, 0x20, 0x20, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x73, 0x69, 0x7a, 0x65, 0x20, 0x3d, 0x20, 0x31, 0x32, 0x38, 0x3b, 0x0a, - 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, - 0x3d, 0x20, 0x32, 0x35, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, - 0x72, 0x79, 0x20, 0x22, 0x6c, 0x66, 0x75, 0x73, 0x65, 0x22, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, 0x20, 0x30, 0x20, 0x31, - 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, - 0x20, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x22, 0x3b, 0x0a, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, - 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x22, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, - 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x20, 0x6f, - 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, - 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, - 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, - 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x68, 0x66, - 0x75, 0x73, 0x65, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x22, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, - 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, - 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x22, - 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x69, - 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x20, 0x20, 0x69, 0x20, 0x69, 0x20, - 0x69, 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x30, 0x20, - 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, - 0x6f, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, - 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, - 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, - 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x5f, 0x77, - 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, - 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, - 0x72, 0x79, 0x20, 0x22, 0x65, 0x66, 0x75, 0x73, 0x65, 0x22, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, 0x20, 0x30, 0x20, 0x31, - 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, - 0x31, 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x22, 0x3b, 0x0a, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, - 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x22, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, - 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x20, 0x6f, - 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, - 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, - 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, - 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x6c, 0x6f, - 0x63, 0x6b, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, - 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x20, 0x31, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x22, - 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x6f, - 0x20, 0x6f, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, - 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x31, 0x20, - 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x22, 0x2c, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x20, 0x31, - 0x20, 0x31, 0x20, 0x69, 0x20, 0x69, 0x20, 0x20, 0x69, 0x20, 0x69, 0x20, - 0x69, 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, - 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, - 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, - 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, - 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x63, 0x61, 0x6c, 0x69, 0x62, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, - 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x22, 0x30, 0x20, 0x30, 0x20, 0x31, 0x20, 0x31, 0x20, - 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x78, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x30, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, - 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x22, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x73, 0x69, - 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x33, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, - 0x61, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x22, 0x30, 0x20, 0x20, 0x30, 0x20, 0x20, 0x31, - 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x78, 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x61, 0x31, 0x20, 0x61, 0x30, 0x20, - 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, - 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x20, - 0x6f, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, - 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x0a, - 0, 0 -}; -size_t avrdude_slic3r_conf_size = 14178; -size_t avrdude_slic3r_conf_size_yy = 14180; diff --git a/src/avrdude/avrdude-slic3r.cpp b/src/avrdude/avrdude-slic3r.cpp index 0140d93ed8..7eff436e2e 100644 --- a/src/avrdude/avrdude-slic3r.cpp +++ b/src/avrdude/avrdude-slic3r.cpp @@ -93,7 +93,7 @@ void AvrDude::priv::unset_handlers() int AvrDude::priv::run_one(const std::vector &args) { - std::vector c_args {{ const_cast(PACKAGE) }}; + std::vector c_args { const_cast(PACKAGE) }; std::string command_line { PACKAGE }; for (const auto &arg : args) { @@ -105,7 +105,7 @@ int AvrDude::priv::run_one(const std::vector &args) { HandlerGuard guard(*this); - message_fn(command_line.c_str(), command_line.size()); + message_fn(command_line.c_str(), (unsigned)command_line.size()); const auto res = ::avrdude_main(static_cast(c_args.size()), c_args.data()); @@ -200,7 +200,7 @@ AvrDude::Ptr AvrDude::run() auto &message_fn = self->p->message_fn; if (message_fn) { message_fn(msg, sizeof(msg)); - message_fn(what, std::strlen(what)); + message_fn(what, (unsigned)std::strlen(what)); message_fn("\n", 1); } diff --git a/src/avrdude/avrdude.h b/src/avrdude/avrdude.h index ff464f46f2..bc784cec6d 100644 --- a/src/avrdude/avrdude.h +++ b/src/avrdude/avrdude.h @@ -64,6 +64,8 @@ int avrdude_main(int argc, char * argv []); #include #include +#define strdup _strdup + #ifdef UNICODE #error "UNICODE should not be defined for avrdude bits on Windows" #endif diff --git a/src/avrdude/avrpart.c b/src/avrdude/avrpart.c index d0bb951ee8..1c7d6af001 100644 --- a/src/avrdude/avrpart.c +++ b/src/avrdude/avrpart.c @@ -358,7 +358,7 @@ AVRMEM * avr_locate_mem(AVRPART * p, char * desc) int matches; int l; - l = strlen(desc); + l = (int)strlen(desc); matches = 0; match = NULL; for (ln=lfirst(p->mem); ln; ln=lnext(ln)) { @@ -662,7 +662,7 @@ void avr_display(FILE * f, AVRPART * p, const char * prefix, int verbose) prefix); px = prefix; - i = strlen(prefix) + 5; + i = (int)strlen(prefix) + 5; buf = (char *)malloc(i); if (buf == NULL) { /* ugh, this is not important enough to bail, just ignore it */ diff --git a/src/avrdude/buspirate.c b/src/avrdude/buspirate.c index 5875d42833..dc8c68fbeb 100644 --- a/src/avrdude/buspirate.c +++ b/src/avrdude/buspirate.c @@ -128,7 +128,7 @@ static int buspirate_recv_bin(struct programmer_t *pgm, unsigned char *buf, size avrdude_message(MSG_DEBUG, "%s: buspirate_recv_bin():\n", progname); dump_mem(MSG_DEBUG, buf, len); - return len; + return (int)len; } static int buspirate_expect_bin(struct programmer_t *pgm, @@ -249,7 +249,7 @@ static int buspirate_send(struct programmer_t *pgm, const char *str) static int buspirate_is_prompt(const char *str) { - int strlen_str = strlen(str); + int strlen_str = (int)strlen(str); /* Prompt ends with '>' or '> ' * all other input probably ends with '\n' */ return (str[strlen_str - 1] == '>' || str[strlen_str - 2] == '>'); diff --git a/src/avrdude/butterfly.c b/src/avrdude/butterfly.c index beb5e04dea..8f582e72a0 100644 --- a/src/avrdude/butterfly.c +++ b/src/avrdude/butterfly.c @@ -675,7 +675,7 @@ static int butterfly_paged_load(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, butterfly_set_addr(pgm, addr / rd_size); } while (addr < max_addr) { - if ((max_addr - addr) < blocksize) { + if ((max_addr - addr) < (unsigned)blocksize) { blocksize = max_addr - addr; }; cmd[1] = (blocksize >> 8) & 0xff; diff --git a/src/avrdude/conf-generate.cpp b/src/avrdude/conf-generate.cpp index f2761ba223..e61f42eb4b 100644 --- a/src/avrdude/conf-generate.cpp +++ b/src/avrdude/conf-generate.cpp @@ -6,36 +6,42 @@ int main(int argc, char const *argv[]) { - if (argc != 3) { - std::cerr << "Usage: " << argv[0] << " " << std::endl; + if (argc != 4) { + std::cerr << "Usage: " << argv[0] << " " << std::endl; return -1; } - const char* filename = argv[1]; + const char* filename_in = argv[1]; const char* symbol = argv[2]; + const char* filename_out = argv[3]; size_t size = 0; - std::fstream file(filename); + std::fstream file(filename_in, std::ios::in | std::ios::binary); if (!file.good()) { - std::cerr << "Cannot read file: " << filename << std::endl; + std::cerr << "Cannot read file: " << filename_in << std::endl; } - std::cout << "/* WARN: This file is auto-generated from `" << filename << "` */" << std::endl; - std::cout << "unsigned char " << symbol << "[] = {"; + std::fstream output(filename_out, std::ios::out | std::ios::trunc); + if (!output.good()) { + std::cerr << "Cannot open output file: " << filename_out << std::endl; + } + + output << "/* WARN: This file is auto-generated from `" << filename_in << "` */" << std::endl; + output << "const unsigned char " << symbol << "[] = {"; char c; - std::cout << std::hex; - std::cout.fill('0'); + output << std::hex; + output.fill('0'); for (file.get(c); !file.eof(); size++, file.get(c)) { - if (size % 12 == 0) { std::cout << "\n "; } - std::cout << "0x" << std::setw(2) << (unsigned)c << ", "; + if (size % 12 == 0) { output << "\n "; } + output << "0x" << std::setw(2) << (unsigned)c << ", "; } - std::cout << "\n 0, 0\n};\n"; + output << "\n 0, 0\n};\n"; - std::cout << std::dec; - std::cout << "size_t " << symbol << "_size = " << size << ";" << std::endl; - std::cout << "size_t " << symbol << "_size_yy = " << size + 2 << ";" << std::endl; + output << std::dec; + output << "const size_t " << symbol << "_size = " << size << ";" << std::endl; + output << "const size_t " << symbol << "_size_yy = " << size + 2 << ";" << std::endl; return 0; } diff --git a/src/avrdude/config.c b/src/avrdude/config.c index 1c0ff55258..b82fb29cb7 100644 --- a/src/avrdude/config.c +++ b/src/avrdude/config.c @@ -240,7 +240,7 @@ TOKEN * string(char * text) return NULL; /* yyerror already called */ } - len = strlen(text); + len = (int)strlen(text); tkn->value.type = V_STR; tkn->value.string = (char *) malloc(len+1); @@ -351,7 +351,7 @@ int read_config(const char * file) } typedef struct yy_buffer_state *YY_BUFFER_STATE; -extern YY_BUFFER_STATE yy_scan_bytes(char *base, size_t size); +extern YY_BUFFER_STATE yy_scan_bytes(const char *base, size_t size); extern void yy_delete_buffer(YY_BUFFER_STATE b); int read_config_builtin() @@ -363,7 +363,7 @@ int read_config_builtin() // Note: Can't use yy_scan_buffer, it's buggy (?), leads to fread from a null FILE* // and so unfortunatelly we have to use the copying variant here - YY_BUFFER_STATE buffer = yy_scan_bytes(avrdude_slic3r_conf, avrdude_slic3r_conf_size); + YY_BUFFER_STATE buffer = yy_scan_bytes((const char *)avrdude_slic3r_conf, avrdude_slic3r_conf_size); if (buffer == NULL) { avrdude_message(MSG_INFO, "%s: read_config_builtin: Failed to initialize parsing buffer\n", progname); return -1; diff --git a/src/avrdude/config_gram.c b/src/avrdude/config_gram.c index c1a65b13ea..2d32fe7392 100644 --- a/src/avrdude/config_gram.c +++ b/src/avrdude/config_gram.c @@ -3640,7 +3640,7 @@ static int parse_cmdbits(OPCODE * op) break; } - len = strlen(s); + len = (int)strlen(s); if (len == 0) { yyerror("invalid bit specifier \"\""); diff --git a/src/avrdude/config_gram.y b/src/avrdude/config_gram.y index 0aa95a8e8c..6a062352b1 100644 --- a/src/avrdude/config_gram.y +++ b/src/avrdude/config_gram.y @@ -1493,7 +1493,7 @@ static int parse_cmdbits(OPCODE * op) break; } - len = strlen(s); + len = (int)strlen(s); if (len == 0) { yyerror("invalid bit specifier \"\""); diff --git a/src/avrdude/fileio.c b/src/avrdude/fileio.c index 7803497a0c..7f4c8edeeb 100644 --- a/src/avrdude/fileio.c +++ b/src/avrdude/fileio.c @@ -264,7 +264,7 @@ static int ihex_readrec(struct ihexrec * ihex, char * rec) unsigned char cksum; int rc; - len = strlen(rec); + len = (int)strlen(rec); offset = 1; cksum = 0; @@ -274,7 +274,7 @@ static int ihex_readrec(struct ihexrec * ihex, char * rec) for (i=0; i<2; i++) buf[i] = rec[offset++]; buf[i] = 0; - ihex->reclen = strtoul(buf, &e, 16); + ihex->reclen = (unsigned char)strtoul(buf, &e, 16); if (e == buf || *e != 0) return -1; @@ -294,7 +294,7 @@ static int ihex_readrec(struct ihexrec * ihex, char * rec) for (i=0; i<2; i++) buf[i] = rec[offset++]; buf[i] = 0; - ihex->rectyp = strtoul(buf, &e, 16); + ihex->rectyp = (unsigned char)strtoul(buf, &e, 16); if (e == buf || *e != 0) return -1; @@ -308,7 +308,7 @@ static int ihex_readrec(struct ihexrec * ihex, char * rec) for (i=0; i<2; i++) buf[i] = rec[offset++]; buf[i] = 0; - ihex->data[j] = strtoul(buf, &e, 16); + ihex->data[j] = (char)strtoul(buf, &e, 16); if (e == buf || *e != 0) return -1; cksum += ihex->data[j]; @@ -320,7 +320,7 @@ static int ihex_readrec(struct ihexrec * ihex, char * rec) for (i=0; i<2; i++) buf[i] = rec[offset++]; buf[i] = 0; - ihex->cksum = strtoul(buf, &e, 16); + ihex->cksum = (char)strtoul(buf, &e, 16); if (e == buf || *e != 0) return -1; @@ -361,7 +361,7 @@ static int ihex2b(char * infile, FILE * inf, while (fgets((char *)buffer,MAX_LINE_LEN,inf)!=NULL) { lineno++; - len = strlen(buffer); + len = (int)strlen(buffer); if (buffer[len-1] == '\n') buffer[--len] = 0; if (buffer[0] != ':') @@ -388,7 +388,7 @@ static int ihex2b(char * infile, FILE * inf, return -1; } nextaddr = ihex.loadofs + baseaddr - fileoffset; - if (nextaddr + ihex.reclen > bufsize) { + if (nextaddr + ihex.reclen > (unsigned)bufsize) { avrdude_message(MSG_INFO, "%s: ERROR: address 0x%04x out of range at line %d of %s\n", progname, nextaddr+ihex.reclen, lineno, infile); return -1; @@ -502,10 +502,11 @@ static int b2srec(unsigned char * inbuf, int bufsize, cksum += n + addr_width + 1; - for (i=addr_width; i>0; i--) + for (i = addr_width; i>0; i--) { cksum += (nextaddr >> (i-1) * 8) & 0xff; + } - for (i=nextaddr; ireclen = strtoul(buf, &e, 16); + srec->reclen = (char)strtoul(buf, &e, 16); cksum += srec->reclen; srec->reclen -= (addr_width+1); if (e == buf || *e != 0) @@ -594,7 +595,7 @@ static int srec_readrec(struct ihexrec * srec, char * rec) for (i=0; iloadofs = strtoull(buf, &e, 16); + srec->loadofs = strtoul(buf, &e, 16); if (e == buf || *e != 0) return -1; @@ -608,7 +609,7 @@ static int srec_readrec(struct ihexrec * srec, char * rec) for (i=0; i<2; i++) buf[i] = rec[offset++]; buf[i] = 0; - srec->data[j] = strtoul(buf, &e, 16); + srec->data[j] = (char)strtoul(buf, &e, 16); if (e == buf || *e != 0) return -1; cksum += srec->data[j]; @@ -620,7 +621,7 @@ static int srec_readrec(struct ihexrec * srec, char * rec) for (i=0; i<2; i++) buf[i] = rec[offset++]; buf[i] = 0; - srec->cksum = strtoul(buf, &e, 16); + srec->cksum = (char)strtoul(buf, &e, 16); if (e == buf || *e != 0) return -1; @@ -650,7 +651,7 @@ static int srec2b(char * infile, FILE * inf, while (fgets((char *)buffer,MAX_LINE_LEN,inf)!=NULL) { lineno++; - len = strlen(buffer); + len = (int)strlen(buffer); if (buffer[len-1] == '\n') buffer[--len] = 0; if (buffer[0] != 0x53) @@ -729,7 +730,7 @@ static int srec2b(char * infile, FILE * inf, return -1; } nextaddr -= fileoffset; - if (nextaddr + srec.reclen > bufsize) { + if (nextaddr + srec.reclen > (unsigned)bufsize) { avrdude_message(MSG_INFO, msg, progname, nextaddr+srec.reclen, "", lineno, infile); return -1; @@ -740,7 +741,7 @@ static int srec2b(char * infile, FILE * inf, } if (nextaddr+srec.reclen > maxaddr) maxaddr = nextaddr+srec.reclen; - reccount++; + reccount++; } } @@ -1143,12 +1144,12 @@ static int fileio_rbin(struct fioparms * fio, switch (fio->op) { case FIO_READ: - rc = fread(buf, 1, size, f); + rc = (int)fread(buf, 1, size, f); if (rc > 0) memset(mem->tags, TAG_ALLOCATED, rc); break; case FIO_WRITE: - rc = fwrite(buf, 1, size, f); + rc = (int)fwrite(buf, 1, size, f); break; default: avrdude_message(MSG_INFO, "%s: fileio: invalid operation=%d\n", @@ -1190,7 +1191,7 @@ static int fileio_imm(struct fioparms * fio, progname, p); return -1; } - mem->buf[loc] = b; + mem->buf[loc] = (char)b; mem->tags[loc++] = TAG_ALLOCATED; p = strtok(NULL, " ,"); rc = loc; @@ -1452,7 +1453,7 @@ static int fmt_autodetect(char * fname, unsigned section) } buf[MAX_LINE_LEN-1] = 0; - len = strlen((char *)buf); + len = (int)strlen((char *)buf); if (buf[len-1] == '\n') buf[--len] = 0; diff --git a/src/avrdude/lexer.c b/src/avrdude/lexer.c index f2d8adb4bc..46d88170f5 100644 --- a/src/avrdude/lexer.c +++ b/src/avrdude/lexer.c @@ -30,7 +30,7 @@ /* C99 systems have . Non-C99 systems may or may not. */ -#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#if (defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(_MSC_VER) /* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, * if you want the limit (max/min) macros for int types. diff --git a/src/avrdude/lists.c b/src/avrdude/lists.c index cab88364e7..063507ed32 100644 --- a/src/avrdude/lists.c +++ b/src/avrdude/lists.c @@ -444,7 +444,7 @@ lcreat ( void * liststruct, int elements ) l->poolsize = DEFAULT_POOLSIZE; } else { - l->poolsize = elements*sizeof(LISTNODE)+sizeof(NODEPOOL); + l->poolsize = (short)(elements*sizeof(LISTNODE)+sizeof(NODEPOOL)); } l->n_ln_pool = (l->poolsize-sizeof(NODEPOOL))/sizeof(LISTNODE); @@ -803,7 +803,7 @@ lget_n ( LISTID lid, unsigned int n ) CKLMAGIC(l); - if ((n<1)||(n>lsize(l))) { + if ((n < 1) || (n > (unsigned)lsize(l))) { return NULL; } @@ -844,7 +844,7 @@ lget_ln ( LISTID lid, unsigned int n ) CKLMAGIC(l); - if ((n<1)||(n>lsize(l))) { + if ((n < 1) || (n > (unsigned)lsize(l))) { return NULL; } @@ -941,7 +941,7 @@ insert_ln ( LIST * l, LISTNODE * ln, void * data_ptr ) | | Insert data before the nth item in the list. -----------------------------------------------------------------*/ -int +int lins_n ( LISTID lid, void * data_ptr, unsigned int n ) { int i; @@ -952,7 +952,7 @@ lins_n ( LISTID lid, void * data_ptr, unsigned int n ) CKLMAGIC(l); - if ((n<1)||(n>(l->num+1))) { + if ((n < 1) || (n > (unsigned)(l->num+1))) { return -1; } @@ -1193,7 +1193,7 @@ lrmv_n ( LISTID lid, unsigned int n ) CKLMAGIC(l); - if ((n<1)||(n>l->num)) { + if ((n < 1) || (n > (unsigned)l->num)) { return NULL; } diff --git a/src/avrdude/main-standalone.cpp b/src/avrdude/main-standalone.cpp index df6d79e136..f7cd6d1d4a 100644 --- a/src/avrdude/main-standalone.cpp +++ b/src/avrdude/main-standalone.cpp @@ -38,6 +38,10 @@ struct ArgvUtf8 : std::vector } }; +#endif + +#ifdef _MSC_VER + int wmain(int argc_w, wchar_t *argv_w[]) { ArgvUtf8 argv_utf8(argc_w, argv_w); diff --git a/src/avrdude/main.c b/src/avrdude/main.c index 8f90403495..60be7ec3a9 100644 --- a/src/avrdude/main.c +++ b/src/avrdude/main.c @@ -107,7 +107,7 @@ int avrdude_message(const int msglvl, const char *format, ...) if (rc > 0 && rc < MSGBUFFER_SIZE) { avrdude_message_handler(msgbuffer, rc, avrdude_message_handler_user_p); } else { - avrdude_message_handler(format_error, strlen(format_error), avrdude_message_handler_user_p); + avrdude_message_handler(format_error, (unsigned)strlen(format_error), avrdude_message_handler_user_p); } } @@ -567,7 +567,7 @@ int avrdude_main(int argc, char * argv []) // #endif - len = strlen(progname) + 2; + len = (int)strlen(progname) + 2; for (i=0; ifd.pfd = (void *)hComPort; @@ -326,8 +326,8 @@ static void serbb_close(PROGRAMMER *pgm) pgm->setpin(pgm, PIN_AVR_RESET, 1); CloseHandle (hComPort); } - avrdude_message(MSG_DEBUG, "%s: ser_close(): closed comm port handle 0x%x\n", - progname, (int)hComPort); + avrdude_message(MSG_DEBUG, "%s: ser_close(): closed comm port handle %p\n", + progname, (void *)hComPort); hComPort = INVALID_HANDLE_VALUE; } diff --git a/src/avrdude/stk500.c b/src/avrdude/stk500.c index efb7078bc9..256076cc6a 100644 --- a/src/avrdude/stk500.c +++ b/src/avrdude/stk500.c @@ -504,7 +504,7 @@ static int stk500_initialize(PROGRAMMER * pgm, AVRPART * p) } else { buf[9] = 0xff; - buf[10] = 0xff; + buf[10] = 0xff; buf[13] = 0; buf[14] = 0; buf[17] = 0; @@ -821,7 +821,7 @@ static int stk500_paged_write(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, break; } - for (prusa3d_semicolon_workaround_round = 0; prusa3d_semicolon_workaround_round < (has_semicolon ? 2 : 1); ++ prusa3d_semicolon_workaround_round) { + for (prusa3d_semicolon_workaround_round = 0; prusa3d_semicolon_workaround_round < (has_semicolon ? 2u : 1u); prusa3d_semicolon_workaround_round++) { /* build command block and avoid multiple send commands as it leads to a crash of the silabs usb serial driver on mac os x */ i = 0; @@ -834,7 +834,7 @@ static int stk500_paged_write(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, buf[i++] = block_size & 0x0f; buf[i++] = memtype; if (has_semicolon) { - for (j = 0; j < block_size; ++i, ++ j) { + for (j = 0; j < (unsigned)block_size; ++i, ++ j) { buf[i] = m->buf[addr + j]; if (buf[i] == ';') buf[i] |= (prusa3d_semicolon_workaround_round ? 0xf0 : 0x0f); @@ -1088,8 +1088,8 @@ static int stk500_set_sck_period(PROGRAMMER * pgm, double v) min = 8.0 / STK500_XTAL; max = 255 * min; - dur = v / min + 0.5; - + dur = (int)(v / min + 0.5); + if (v < min) { dur = 1; avrdude_message(MSG_INFO, "%s: stk500_set_sck_period(): p = %.1f us too small, using %.1f us\n", @@ -1099,7 +1099,7 @@ static int stk500_set_sck_period(PROGRAMMER * pgm, double v) avrdude_message(MSG_INFO, "%s: stk500_set_sck_period(): p = %.1f us too large, using %.1f us\n", progname, v / 1e-6, dur * min / 1e-6); } - + return stk500_setparm(pgm, Parm_STK_SCK_DURATION, dur); } diff --git a/src/avrdude/stk500v2.c b/src/avrdude/stk500v2.c index 9bc629ba41..33bdf8eeae 100644 --- a/src/avrdude/stk500v2.c +++ b/src/avrdude/stk500v2.c @@ -130,58 +130,58 @@ struct jtagispentry #define SZ_SPI_MULTI (USHRT_MAX - 1) }; -static const struct jtagispentry jtagispcmds[] = { - /* generic */ - { CMD_SET_PARAMETER, 2 }, - { CMD_GET_PARAMETER, 3 }, - { CMD_OSCCAL, 2 }, - { CMD_LOAD_ADDRESS, 2 }, - /* ISP mode */ - { CMD_ENTER_PROGMODE_ISP, 2 }, - { CMD_LEAVE_PROGMODE_ISP, 2 }, - { CMD_CHIP_ERASE_ISP, 2 }, - { CMD_PROGRAM_FLASH_ISP, 2 }, - { CMD_READ_FLASH_ISP, SZ_READ_FLASH_EE }, - { CMD_PROGRAM_EEPROM_ISP, 2 }, - { CMD_READ_EEPROM_ISP, SZ_READ_FLASH_EE }, - { CMD_PROGRAM_FUSE_ISP, 3 }, - { CMD_READ_FUSE_ISP, 4 }, - { CMD_PROGRAM_LOCK_ISP, 3 }, - { CMD_READ_LOCK_ISP, 4 }, - { CMD_READ_SIGNATURE_ISP, 4 }, - { CMD_READ_OSCCAL_ISP, 4 }, - { CMD_SPI_MULTI, SZ_SPI_MULTI }, - /* all HV modes */ - { CMD_SET_CONTROL_STACK, 2 }, - /* HVSP mode */ - { CMD_ENTER_PROGMODE_HVSP, 2 }, - { CMD_LEAVE_PROGMODE_HVSP, 2 }, - { CMD_CHIP_ERASE_HVSP, 2 }, - { CMD_PROGRAM_FLASH_HVSP, 2 }, - { CMD_READ_FLASH_HVSP, SZ_READ_FLASH_EE }, - { CMD_PROGRAM_EEPROM_HVSP, 2 }, - { CMD_READ_EEPROM_HVSP, SZ_READ_FLASH_EE }, - { CMD_PROGRAM_FUSE_HVSP, 2 }, - { CMD_READ_FUSE_HVSP, 3 }, - { CMD_PROGRAM_LOCK_HVSP, 2 }, - { CMD_READ_LOCK_HVSP, 3 }, - { CMD_READ_SIGNATURE_HVSP, 3 }, - { CMD_READ_OSCCAL_HVSP, 3 }, - /* PP mode */ - { CMD_ENTER_PROGMODE_PP, 2 }, - { CMD_LEAVE_PROGMODE_PP, 2 }, - { CMD_CHIP_ERASE_PP, 2 }, - { CMD_PROGRAM_FLASH_PP, 2 }, - { CMD_READ_FLASH_PP, SZ_READ_FLASH_EE }, - { CMD_PROGRAM_EEPROM_PP, 2 }, - { CMD_READ_EEPROM_PP, SZ_READ_FLASH_EE }, - { CMD_PROGRAM_FUSE_PP, 2 }, - { CMD_READ_FUSE_PP, 3 }, - { CMD_PROGRAM_LOCK_PP, 2 }, - { CMD_READ_LOCK_PP, 3 }, - { CMD_READ_SIGNATURE_PP, 3 }, - { CMD_READ_OSCCAL_PP, 3 }, -}; +// static const struct jtagispentry jtagispcmds[] = { +// /* generic */ +// { CMD_SET_PARAMETER, 2 }, +// { CMD_GET_PARAMETER, 3 }, +// { CMD_OSCCAL, 2 }, +// { CMD_LOAD_ADDRESS, 2 }, +// /* ISP mode */ +// { CMD_ENTER_PROGMODE_ISP, 2 }, +// { CMD_LEAVE_PROGMODE_ISP, 2 }, +// { CMD_CHIP_ERASE_ISP, 2 }, +// { CMD_PROGRAM_FLASH_ISP, 2 }, +// { CMD_READ_FLASH_ISP, SZ_READ_FLASH_EE }, +// { CMD_PROGRAM_EEPROM_ISP, 2 }, +// { CMD_READ_EEPROM_ISP, SZ_READ_FLASH_EE }, +// { CMD_PROGRAM_FUSE_ISP, 3 }, +// { CMD_READ_FUSE_ISP, 4 }, +// { CMD_PROGRAM_LOCK_ISP, 3 }, +// { CMD_READ_LOCK_ISP, 4 }, +// { CMD_READ_SIGNATURE_ISP, 4 }, +// { CMD_READ_OSCCAL_ISP, 4 }, +// { CMD_SPI_MULTI, SZ_SPI_MULTI }, +// /* all HV modes */ +// { CMD_SET_CONTROL_STACK, 2 }, +// /* HVSP mode */ +// { CMD_ENTER_PROGMODE_HVSP, 2 }, +// { CMD_LEAVE_PROGMODE_HVSP, 2 }, +// { CMD_CHIP_ERASE_HVSP, 2 }, +// { CMD_PROGRAM_FLASH_HVSP, 2 }, +// { CMD_READ_FLASH_HVSP, SZ_READ_FLASH_EE }, +// { CMD_PROGRAM_EEPROM_HVSP, 2 }, +// { CMD_READ_EEPROM_HVSP, SZ_READ_FLASH_EE }, +// { CMD_PROGRAM_FUSE_HVSP, 2 }, +// { CMD_READ_FUSE_HVSP, 3 }, +// { CMD_PROGRAM_LOCK_HVSP, 2 }, +// { CMD_READ_LOCK_HVSP, 3 }, +// { CMD_READ_SIGNATURE_HVSP, 3 }, +// { CMD_READ_OSCCAL_HVSP, 3 }, +// /* PP mode */ +// { CMD_ENTER_PROGMODE_PP, 2 }, +// { CMD_LEAVE_PROGMODE_PP, 2 }, +// { CMD_CHIP_ERASE_PP, 2 }, +// { CMD_PROGRAM_FLASH_PP, 2 }, +// { CMD_READ_FLASH_PP, SZ_READ_FLASH_EE }, +// { CMD_PROGRAM_EEPROM_PP, 2 }, +// { CMD_READ_EEPROM_PP, SZ_READ_FLASH_EE }, +// { CMD_PROGRAM_FUSE_PP, 2 }, +// { CMD_READ_FUSE_PP, 3 }, +// { CMD_PROGRAM_LOCK_PP, 2 }, +// { CMD_READ_LOCK_PP, 3 }, +// { CMD_READ_SIGNATURE_PP, 3 }, +// { CMD_READ_OSCCAL_PP, 3 }, +// }; /* * From XML file: @@ -379,15 +379,15 @@ static void stk500v2_jtag3_teardown(PROGRAMMER * pgm) } -static unsigned short -b2_to_u16(unsigned char *b) -{ - unsigned short l; - l = b[0]; - l += (unsigned)b[1] << 8; +// static unsigned short +// b2_to_u16(unsigned char *b) +// { +// unsigned short l; +// l = b[0]; +// l += (unsigned)b[1] << 8; - return l; -} +// return l; +// } static int stk500v2_send_mk2(PROGRAMMER * pgm, unsigned char * data, size_t len) { @@ -399,16 +399,16 @@ static int stk500v2_send_mk2(PROGRAMMER * pgm, unsigned char * data, size_t len) return 0; } -static unsigned short get_jtagisp_return_size(unsigned char cmd) -{ - int i; +// static unsigned short get_jtagisp_return_size(unsigned char cmd) +// { +// int i; - for (i = 0; i < sizeof jtagispcmds / sizeof jtagispcmds[0]; i++) - if (jtagispcmds[i].cmd == cmd) - return jtagispcmds[i].size; +// for (i = 0; i < sizeof jtagispcmds / sizeof jtagispcmds[0]; i++) +// if (jtagispcmds[i].cmd == cmd) +// return jtagispcmds[i].size; - return 0; -} +// return 0; +// } /* * Send the data as a JTAG ICE mkII encapsulated ISP packet. @@ -504,7 +504,7 @@ static int stk500v2_send(PROGRAMMER * pgm, unsigned char * data, size_t len) buf[0] = MESSAGE_START; buf[1] = PDATA(pgm)->command_sequence; - buf[2] = len / 256; + buf[2] = (char)(len / 256); buf[3] = len % 256; buf[4] = TOKEN; memcpy(buf+5, data, len); @@ -1128,7 +1128,8 @@ static int stk500v2_program_enable(PROGRAMMER * pgm, AVRPART * p) { unsigned char buf[16]; char msg[100]; /* see remarks above about size needed */ - int rv, tries; + int rv; + // int tries; PDATA(pgm)->lastpart = p; @@ -1143,7 +1144,7 @@ static int stk500v2_program_enable(PROGRAMMER * pgm, AVRPART * p) /* Activate AVR-style (low active) RESET */ stk500v2_setparm_real(pgm, PARAM_RESET_POLARITY, 0x01); - tries = 0; + // tries = 0; // retry: buf[0] = CMD_ENTER_PROGMODE_ISP; buf[1] = p->timeout; @@ -1882,7 +1883,7 @@ static int stk500hv_read_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, if (stk500v2_loadaddr(pgm, use_ext_addr | (paddr >> addrshift)) < 0) return -1; } else { - buf[1] = addr; + buf[1] = (char)addr; } avrdude_message(MSG_NOTICE2, "%s: stk500hv_read_byte(): Sending read memory command: ", @@ -2137,7 +2138,7 @@ static int stk500hv_write_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, if (stk500v2_loadaddr(pgm, use_ext_addr | (paddr >> addrshift)) < 0) return -1; } else { - buf[1] = addr; + buf[1] = (char)addr; buf[2] = data; if (mode == PPMODE) { buf[3] = pulsewidth; @@ -2298,7 +2299,7 @@ static int stk500v2_paged_write(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, unsigned int page_size, unsigned int addr, unsigned int n_bytes) { -static int page = 0; +// static int page = 0; unsigned int block_size, last_addr, addrshift, use_ext_addr; unsigned int maxaddr = addr + n_bytes; unsigned char commandbuf[10]; @@ -2833,10 +2834,10 @@ static int stk500v2_set_fosc(PROGRAMMER * pgm, double v) progname, v, unit, STK500V2_XTAL / 2e6); fosc = STK500V2_XTAL / 2; } else - fosc = (unsigned)v; + fosc = (int)v; for (idx = 0; idx < sizeof(ps) / sizeof(ps[0]); idx++) { - if (fosc >= STK500V2_XTAL / (256 * ps[idx] * 2)) { + if (fosc >= (int)(STK500V2_XTAL / (256 * ps[idx] * 2))) { /* this prescaler value can handle our frequency */ prescale = idx + 1; cmatch = (unsigned)(STK500V2_XTAL / (2 * fosc * ps[idx])) - 1; @@ -3065,8 +3066,8 @@ static int stk600_set_fosc(PROGRAMMER * pgm, double v) { unsigned int oct, dac; - oct = 1.443 * log(v / 1039.0); - dac = 2048 - (2078.0 * pow(2, (double)(10 + oct))) / v; + oct = (unsigned)(1.443 * log(v / 1039.0)); + dac = (unsigned)(2048.0 - (2078.0 * pow(2, (double)(10 + oct))) / v); return stk500v2_setparm2(pgm, PARAM2_CLOCK_CONF, (oct << 12) | (dac << 2)); } @@ -3075,7 +3076,7 @@ static int stk600_set_sck_period(PROGRAMMER * pgm, double v) { unsigned int sck; - sck = ceil((16e6 / (2 * 1.0 / v)) - 1); + sck = (unsigned)ceil((16e6 / (2 * 1.0 / v)) - 1); if (sck >= 4096) sck = 4095; @@ -3093,7 +3094,7 @@ static int stk500v2_jtag3_set_sck_period(PROGRAMMER * pgm, double v) else if (v > 1E-3) sck = 1; else - sck = 1.0 / (1000.0 * v); + sck = (unsigned)(1.0 / (1000.0 * v)); value[0] = CMD_SET_SCK; value[1] = sck & 0xff; @@ -3143,7 +3144,7 @@ static int stk500v2_setparm_real(PROGRAMMER * pgm, unsigned char parm, unsigned static int stk500v2_setparm(PROGRAMMER * pgm, unsigned char parm, unsigned char value) { - unsigned char current_value; + unsigned char current_value = 0; int res; res = stk500v2_getparm(pgm, parm, ¤t_value); @@ -3214,8 +3215,15 @@ static const char *stk600_get_cardname(const struct carddata *table, static void stk500v2_display(PROGRAMMER * pgm, const char * p) { - unsigned char maj, min, hdw, topcard, maj_s1, min_s1, maj_s2, min_s2; - unsigned int rev; + unsigned char maj = 0; + unsigned char min = 0; + unsigned char hdw = 0; + unsigned char topcard = 0; + unsigned char maj_s1 = 0; + unsigned char min_s1 = 0; + unsigned char maj_s2 = 0; + unsigned char min_s2 = 0; + unsigned int rev = 0; const char *topcard_name, *pgmname; switch (PDATA(pgm)->pgmtype) { @@ -3294,13 +3302,20 @@ f_to_kHz_MHz(double f, const char **unit) static void stk500v2_print_parms1(PROGRAMMER * pgm, const char * p) { - unsigned char vtarget, vadjust, osc_pscale, osc_cmatch, sck_duration =0; //XXX 0 is not correct, check caller - unsigned int sck_stk600, clock_conf, dac, oct, varef; - unsigned char vtarget_jtag[4]; + unsigned char vtarget = 0; + unsigned char vadjust = 0; + unsigned char sck_duration = 0; + unsigned char osc_pscale = 0; + unsigned char osc_cmatch = 0; + unsigned varef = 0; + unsigned sck_stk600 = 0; + unsigned clock_conf = 0; + unsigned dac, oct; + // unsigned char vtarget_jtag[4]; int prescale; double f; const char *unit; - void *mycookie; + // void *mycookie; if (PDATA(pgm)->pgmtype == PGMTYPE_JTAGICE_MKII) { return; @@ -3963,10 +3978,10 @@ static int stk600_xprog_write_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, b[0] = XPRG_CMD_WRITE_MEM; b[1] = memcode; b[2] = 0; /* pagemode: non-paged write */ - b[3] = addr >> 24; - b[4] = addr >> 16; - b[5] = addr >> 8; - b[6] = addr; + b[3] = (char)(addr >> 24); + b[4] = (char)(addr >> 16); + b[5] = (char)(addr >> 8); + b[6] = (char)addr; b[7] = 0; b[8] = write_size; b[9] = data; @@ -4011,10 +4026,10 @@ static int stk600_xprog_read_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, addr += mem->offset; b[0] = XPRG_CMD_READ_MEM; - b[2] = addr >> 24; - b[3] = addr >> 16; - b[4] = addr >> 8; - b[5] = addr; + b[2] = (char)(addr >> 24); + b[3] = (char)(addr >> 16); + b[4] = (char)(addr >> 8); + b[5] = (char)addr; b[6] = 0; b[7] = 1; if (stk600_xprog_command(pgm, b, 8, 3) < 0) { diff --git a/src/avrdude/term.c b/src/avrdude/term.c index 012f6f1a51..182367cf22 100644 --- a/src/avrdude/term.c +++ b/src/avrdude/term.c @@ -281,7 +281,7 @@ static int cmd_dump(PROGRAMMER * pgm, struct avrpart * p, maxsize = mem->size; - if (addr >= maxsize) { + if (addr >= (unsigned long)maxsize) { if (argc == 2) { /* wrap around */ addr = 0; @@ -294,7 +294,7 @@ static int cmd_dump(PROGRAMMER * pgm, struct avrpart * p, } /* trim len if nessary to not read past the end of memory */ - if ((addr + len) > maxsize) + if ((addr + len) > (unsigned long)maxsize) len = maxsize - addr; buf = malloc(len); @@ -303,7 +303,7 @@ static int cmd_dump(PROGRAMMER * pgm, struct avrpart * p, return -1; } - for (i=0; iread_byte(pgm, p, mem, addr+i, &buf[i]); if (rc != 0) { avrdude_message(MSG_INFO, "error reading %s address 0x%05lx of part %s\n", @@ -364,7 +364,7 @@ static int cmd_write(PROGRAMMER * pgm, struct avrpart * p, return -1; } - if (addr > maxsize) { + if (addr > (unsigned long)maxsize) { avrdude_message(MSG_INFO, "%s (write): address 0x%05lx is out of range for %s memory\n", progname, addr, memtype); return -1; @@ -373,7 +373,7 @@ static int cmd_write(PROGRAMMER * pgm, struct avrpart * p, /* number of bytes to write at the specified address */ len = argc - 3; - if ((addr + len) > maxsize) { + if ((addr + len) > (unsigned long)maxsize) { avrdude_message(MSG_INFO, "%s (write): selected address and # bytes exceed " "range for %s memory\n", progname, memtype); @@ -386,8 +386,8 @@ static int cmd_write(PROGRAMMER * pgm, struct avrpart * p, return -1; } - for (i=3; ierr_led(pgm, OFF); - for (werror=0, i=0; i +struct timezone; +struct timeval; +#else #ifndef __cplusplus /* should be in some equivalent to */ typedef __int8 int8_t; -typedef __int16 int16_t; +typedef __int16 int16_t; typedef __int32 int32_t; typedef __int64 int64_t; typedef unsigned __int8 uint8_t; @@ -74,6 +79,7 @@ typedef unsigned __int16 uint16_t; typedef unsigned __int32 uint32_t; typedef unsigned __int64 uint64_t; #endif +#endif int usleep(unsigned usec); diff --git a/src/boost/CMakeLists.txt b/src/boost/CMakeLists.txt index aae87340b3..12fe6b4e52 100644 --- a/src/boost/CMakeLists.txt +++ b/src/boost/CMakeLists.txt @@ -19,6 +19,6 @@ add_library(nowide STATIC nowide/windows.hpp ) -target_include_directories(nowide SYSTEM PUBLIC ${Boost_INCLUDE_DIRS}) +target_link_libraries(nowide PUBLIC boost_headeronly) diff --git a/src/boost/nowide/utf8_codecvt.hpp b/src/boost/nowide/utf8_codecvt.hpp index cc5046fc85..877c9f0e0d 100644 --- a/src/boost/nowide/utf8_codecvt.hpp +++ b/src/boost/nowide/utf8_codecvt.hpp @@ -102,7 +102,7 @@ protected: #ifndef BOOST_NOWIDE_DO_LENGTH_MBSTATE_CONST return from - save_from; #else - return save_max - max; + return int(save_max - max); #endif } diff --git a/src/build-utils/CMakeLists.txt b/src/build-utils/CMakeLists.txt new file mode 100644 index 0000000000..3b3961b562 --- /dev/null +++ b/src/build-utils/CMakeLists.txt @@ -0,0 +1,39 @@ + +add_executable(encoding-check encoding-check.cpp) + +# A global no-op target which depends on all encodings checks, +# and on which in turn all checked targets depend. +# This is done to make encoding checks the first thing to be +# performed before actually compiling any sources of the checked targets +# to make the check fail as early as possible. +add_custom_target(global-encoding-check + ALL + DEPENDS encoding-check +) + +# Function that adds source file encoding check to a target +# using the above encoding-check binary + +function(encoding_check TARGET) + # Obtain target source files + get_target_property(T_SOURCES ${TARGET} SOURCES) + + # Define top-level encoding check target for this ${TARGET} + add_custom_target(encoding-check-${TARGET} + DEPENDS encoding-check ${T_SOURCES} + COMMENT "Checking source files encodings for target ${TARGET}" + ) + + # Add checking of each source file as a subcommand of encoding-check-${TARGET} + foreach(file ${T_SOURCES}) + add_custom_command(TARGET encoding-check-${TARGET} + COMMAND $ ${TARGET} ${file} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + endforeach() + + # This adds dependency on encoding-check-${TARGET} to ${TARET} + # via the global-encoding-check + add_dependencies(global-encoding-check encoding-check-${TARGET}) + add_dependencies(${TARGET} global-encoding-check) +endfunction() diff --git a/src/build-utils/encoding-check.cpp b/src/build-utils/encoding-check.cpp new file mode 100644 index 0000000000..89f225572b --- /dev/null +++ b/src/build-utils/encoding-check.cpp @@ -0,0 +1,119 @@ +#include +#include +#include +#include + + +/* + * The utf8_check() function scans the '\0'-terminated string starting + * at s. It returns a pointer to the first byte of the first malformed + * or overlong UTF-8 sequence found, or NULL if the string contains + * only correct UTF-8. It also spots UTF-8 sequences that could cause + * trouble if converted to UTF-16, namely surrogate characters + * (U+D800..U+DFFF) and non-Unicode positions (U+FFFE..U+FFFF). This + * routine is very likely to find a malformed sequence if the input + * uses any other encoding than UTF-8. It therefore can be used as a + * very effective heuristic for distinguishing between UTF-8 and other + * encodings. + * + * I wrote this code mainly as a specification of functionality; there + * are no doubt performance optimizations possible for certain CPUs. + * + * Markus Kuhn -- 2005-03-30 + * License: http://www.cl.cam.ac.uk/~mgk25/short-license.html + */ + +unsigned char *utf8_check(unsigned char *s) +{ + while (*s) { + if (*s < 0x80) { + // 0xxxxxxx + s++; + } else if ((s[0] & 0xe0) == 0xc0) { + // 110xxxxx 10xxxxxx + if ((s[1] & 0xc0) != 0x80 || + (s[0] & 0xfe) == 0xc0) { // overlong? + return s; + } else { + s += 2; + } + } else if ((s[0] & 0xf0) == 0xe0) { + // 1110xxxx 10xxxxxx 10xxxxxx + if ((s[1] & 0xc0) != 0x80 || + (s[2] & 0xc0) != 0x80 || + (s[0] == 0xe0 && (s[1] & 0xe0) == 0x80) || // overlong? + (s[0] == 0xed && (s[1] & 0xe0) == 0xa0) || // surrogate? + (s[0] == 0xef && s[1] == 0xbf && + (s[2] & 0xfe) == 0xbe)) { // U+FFFE or U+FFFF? + return s; + } else { + s += 3; + } + } else if ((s[0] & 0xf8) == 0xf0) { + // 11110xxX 10xxxxxx 10xxxxxx 10xxxxxx + if ((s[1] & 0xc0) != 0x80 || + (s[2] & 0xc0) != 0x80 || + (s[3] & 0xc0) != 0x80 || + (s[0] == 0xf0 && (s[1] & 0xf0) == 0x80) || // overlong? + (s[0] == 0xf4 && s[1] > 0x8f) || s[0] > 0xf4) { // > U+10FFFF? + return s; + } else { + s += 4; + } + } else { + return s; + } + } + + return NULL; +} + + +int main(int argc, char const *argv[]) +{ + if (argc != 3) { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return -1; + } + + const char* target = argv[1]; + const char* filename = argv[2]; + + const auto error_exit = [=](const char* error) { + std::cerr << "\n\tError: " << error << ": " << filename << "\n" + << "\tTarget: " << target << "\n" + << std::endl; + std::exit(-2); + }; + + std::ifstream file(filename, std::ios::binary | std::ios::ate); + const auto size = file.tellg(); + + if (size == 0) { + return 0; + } + + file.seekg(0, std::ios::beg); + std::vector buffer(size); + + if (file.read(buffer.data(), size)) { + buffer.push_back('\0'); + + // Check UTF-8 validity + if (utf8_check(reinterpret_cast(buffer.data())) != nullptr) { + error_exit("Source file does not contain (valid) UTF-8"); + } + + // Check against a BOM mark + if (buffer.size() >= 3 + && buffer[0] == '\xef' + && buffer[1] == '\xbb' + && buffer[2] == '\xbf') { + error_exit("Source file is valid UTF-8 but contains a BOM mark"); + } + } else { + error_exit("Could not read source file"); + } + + return 0; +} diff --git a/src/igl/SortableRow.h b/src/igl/SortableRow.h deleted file mode 100644 index 5f172987bf..0000000000 --- a/src/igl/SortableRow.h +++ /dev/null @@ -1,70 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2013 Alec Jacobson -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#ifndef IGL_SORTABLE_ROW_H -#define IGL_SORTABLE_ROW_H - -// Simple class to contain a rowvector which allows rowwise sorting and -// reordering -#include - -namespace igl -{ - // Templates: - // T should be a matrix that implements .size(), and operator(int i) - template - class SortableRow - { - public: - T data; - public: - SortableRow():data(){}; - SortableRow(const T & data):data(data){}; - bool operator<(const SortableRow & that) const - { - // Get reference so that I can use parenthesis - const SortableRow & THIS = *this; - // Lexicographical - int minc = (THIS.data.size() < that.data.size()? - THIS.data.size() : that.data.size()); - // loop over columns - for(int i = 0;i & THIS = *this; - if(THIS.data.size() != that.data.size()) - { - return false; - } - for(int i = 0;i +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SORTABLE_ROW_H +#define IGL_SORTABLE_ROW_H + +// Simple class to contain a rowvector which allows rowwise sorting and +// reordering +#include + +namespace igl +{ +// Templates: +// T should be a matrix that implements .size(), and operator(int i) +template +class SortableRow +{ +public: + T data; +public: + SortableRow():data(){}; + SortableRow(const T & data):data(data){}; + bool operator<(const SortableRow & that) const + { + // Lexicographical + int minc = (this->data.size() < that.data.size()? + this->data.size() : that.data.size()); + // loop over columns + for(int i = 0;idata(i) == that.data(i)) + { + continue; + } + return this->data(i) < that.data(i); + } + // All characters the same, comes done to length + return this->data.size()data.size() != that.data.size()) + { + return false; + } + for(int i = 0;idata.size();i++) + { + if(this->data(i) != that.data(i)) + { + return false; + } + } + return true; + }; + bool operator!=(const SortableRow & that) const + { + return !(*this == that); + }; +}; +} + +#endif diff --git a/src/igl/Timer.h b/src/libigl/igl/Timer.h similarity index 100% rename from src/igl/Timer.h rename to src/libigl/igl/Timer.h diff --git a/src/igl/Viewport.h b/src/libigl/igl/Viewport.h similarity index 100% rename from src/igl/Viewport.h rename to src/libigl/igl/Viewport.h diff --git a/src/igl/WindingNumberAABB.h b/src/libigl/igl/WindingNumberAABB.h similarity index 100% rename from src/igl/WindingNumberAABB.h rename to src/libigl/igl/WindingNumberAABB.h diff --git a/src/igl/WindingNumberMethod.h b/src/libigl/igl/WindingNumberMethod.h similarity index 100% rename from src/igl/WindingNumberMethod.h rename to src/libigl/igl/WindingNumberMethod.h diff --git a/src/igl/WindingNumberTree.h b/src/libigl/igl/WindingNumberTree.h similarity index 100% rename from src/igl/WindingNumberTree.h rename to src/libigl/igl/WindingNumberTree.h diff --git a/src/igl/ZERO.h b/src/libigl/igl/ZERO.h similarity index 100% rename from src/igl/ZERO.h rename to src/libigl/igl/ZERO.h diff --git a/src/igl/active_set.cpp b/src/libigl/igl/active_set.cpp similarity index 100% rename from src/igl/active_set.cpp rename to src/libigl/igl/active_set.cpp diff --git a/src/igl/active_set.h b/src/libigl/igl/active_set.h similarity index 100% rename from src/igl/active_set.h rename to src/libigl/igl/active_set.h diff --git a/src/igl/adjacency_list.cpp b/src/libigl/igl/adjacency_list.cpp similarity index 100% rename from src/igl/adjacency_list.cpp rename to src/libigl/igl/adjacency_list.cpp diff --git a/src/igl/adjacency_list.h b/src/libigl/igl/adjacency_list.h similarity index 100% rename from src/igl/adjacency_list.h rename to src/libigl/igl/adjacency_list.h diff --git a/src/igl/adjacency_matrix.cpp b/src/libigl/igl/adjacency_matrix.cpp similarity index 100% rename from src/igl/adjacency_matrix.cpp rename to src/libigl/igl/adjacency_matrix.cpp diff --git a/src/igl/adjacency_matrix.h b/src/libigl/igl/adjacency_matrix.h similarity index 100% rename from src/igl/adjacency_matrix.h rename to src/libigl/igl/adjacency_matrix.h diff --git a/src/igl/all.cpp b/src/libigl/igl/all.cpp similarity index 100% rename from src/igl/all.cpp rename to src/libigl/igl/all.cpp diff --git a/src/igl/all.h b/src/libigl/igl/all.h similarity index 100% rename from src/igl/all.h rename to src/libigl/igl/all.h diff --git a/src/igl/all_edges.cpp b/src/libigl/igl/all_edges.cpp similarity index 100% rename from src/igl/all_edges.cpp rename to src/libigl/igl/all_edges.cpp diff --git a/src/igl/all_edges.h b/src/libigl/igl/all_edges.h similarity index 100% rename from src/igl/all_edges.h rename to src/libigl/igl/all_edges.h diff --git a/src/igl/all_pairs_distances.cpp b/src/libigl/igl/all_pairs_distances.cpp similarity index 100% rename from src/igl/all_pairs_distances.cpp rename to src/libigl/igl/all_pairs_distances.cpp diff --git a/src/igl/all_pairs_distances.h b/src/libigl/igl/all_pairs_distances.h similarity index 100% rename from src/igl/all_pairs_distances.h rename to src/libigl/igl/all_pairs_distances.h diff --git a/src/igl/ambient_occlusion.cpp b/src/libigl/igl/ambient_occlusion.cpp similarity index 100% rename from src/igl/ambient_occlusion.cpp rename to src/libigl/igl/ambient_occlusion.cpp diff --git a/src/igl/ambient_occlusion.h b/src/libigl/igl/ambient_occlusion.h similarity index 100% rename from src/igl/ambient_occlusion.h rename to src/libigl/igl/ambient_occlusion.h diff --git a/src/igl/angular_distance.cpp b/src/libigl/igl/angular_distance.cpp similarity index 100% rename from src/igl/angular_distance.cpp rename to src/libigl/igl/angular_distance.cpp diff --git a/src/igl/angular_distance.h b/src/libigl/igl/angular_distance.h similarity index 100% rename from src/igl/angular_distance.h rename to src/libigl/igl/angular_distance.h diff --git a/src/igl/anttweakbar/ReAntTweakBar.cpp b/src/libigl/igl/anttweakbar/ReAntTweakBar.cpp similarity index 100% rename from src/igl/anttweakbar/ReAntTweakBar.cpp rename to src/libigl/igl/anttweakbar/ReAntTweakBar.cpp diff --git a/src/igl/anttweakbar/ReAntTweakBar.h b/src/libigl/igl/anttweakbar/ReAntTweakBar.h similarity index 100% rename from src/igl/anttweakbar/ReAntTweakBar.h rename to src/libigl/igl/anttweakbar/ReAntTweakBar.h diff --git a/src/igl/anttweakbar/cocoa_key_to_anttweakbar_key.cpp b/src/libigl/igl/anttweakbar/cocoa_key_to_anttweakbar_key.cpp similarity index 100% rename from src/igl/anttweakbar/cocoa_key_to_anttweakbar_key.cpp rename to src/libigl/igl/anttweakbar/cocoa_key_to_anttweakbar_key.cpp diff --git a/src/igl/anttweakbar/cocoa_key_to_anttweakbar_key.h b/src/libigl/igl/anttweakbar/cocoa_key_to_anttweakbar_key.h similarity index 100% rename from src/igl/anttweakbar/cocoa_key_to_anttweakbar_key.h rename to src/libigl/igl/anttweakbar/cocoa_key_to_anttweakbar_key.h diff --git a/src/igl/any.cpp b/src/libigl/igl/any.cpp similarity index 100% rename from src/igl/any.cpp rename to src/libigl/igl/any.cpp diff --git a/src/igl/any.h b/src/libigl/igl/any.h similarity index 100% rename from src/igl/any.h rename to src/libigl/igl/any.h diff --git a/src/igl/any_of.cpp b/src/libigl/igl/any_of.cpp similarity index 100% rename from src/igl/any_of.cpp rename to src/libigl/igl/any_of.cpp diff --git a/src/igl/any_of.h b/src/libigl/igl/any_of.h similarity index 100% rename from src/igl/any_of.h rename to src/libigl/igl/any_of.h diff --git a/src/igl/arap.cpp b/src/libigl/igl/arap.cpp similarity index 100% rename from src/igl/arap.cpp rename to src/libigl/igl/arap.cpp diff --git a/src/igl/arap.h b/src/libigl/igl/arap.h similarity index 100% rename from src/igl/arap.h rename to src/libigl/igl/arap.h diff --git a/src/igl/arap_dof.cpp b/src/libigl/igl/arap_dof.cpp similarity index 100% rename from src/igl/arap_dof.cpp rename to src/libigl/igl/arap_dof.cpp diff --git a/src/igl/arap_dof.h b/src/libigl/igl/arap_dof.h similarity index 100% rename from src/igl/arap_dof.h rename to src/libigl/igl/arap_dof.h diff --git a/src/igl/arap_linear_block.cpp b/src/libigl/igl/arap_linear_block.cpp similarity index 100% rename from src/igl/arap_linear_block.cpp rename to src/libigl/igl/arap_linear_block.cpp diff --git a/src/igl/arap_linear_block.h b/src/libigl/igl/arap_linear_block.h similarity index 100% rename from src/igl/arap_linear_block.h rename to src/libigl/igl/arap_linear_block.h diff --git a/src/igl/arap_rhs.cpp b/src/libigl/igl/arap_rhs.cpp similarity index 100% rename from src/igl/arap_rhs.cpp rename to src/libigl/igl/arap_rhs.cpp diff --git a/src/igl/arap_rhs.h b/src/libigl/igl/arap_rhs.h similarity index 100% rename from src/igl/arap_rhs.h rename to src/libigl/igl/arap_rhs.h diff --git a/src/igl/average_onto_faces.cpp b/src/libigl/igl/average_onto_faces.cpp similarity index 100% rename from src/igl/average_onto_faces.cpp rename to src/libigl/igl/average_onto_faces.cpp diff --git a/src/igl/average_onto_faces.h b/src/libigl/igl/average_onto_faces.h similarity index 100% rename from src/igl/average_onto_faces.h rename to src/libigl/igl/average_onto_faces.h diff --git a/src/igl/average_onto_vertices.cpp b/src/libigl/igl/average_onto_vertices.cpp similarity index 100% rename from src/igl/average_onto_vertices.cpp rename to src/libigl/igl/average_onto_vertices.cpp diff --git a/src/igl/average_onto_vertices.h b/src/libigl/igl/average_onto_vertices.h similarity index 100% rename from src/igl/average_onto_vertices.h rename to src/libigl/igl/average_onto_vertices.h diff --git a/src/igl/avg_edge_length.cpp b/src/libigl/igl/avg_edge_length.cpp similarity index 100% rename from src/igl/avg_edge_length.cpp rename to src/libigl/igl/avg_edge_length.cpp diff --git a/src/igl/avg_edge_length.h b/src/libigl/igl/avg_edge_length.h similarity index 100% rename from src/igl/avg_edge_length.h rename to src/libigl/igl/avg_edge_length.h diff --git a/src/igl/axis_angle_to_quat.cpp b/src/libigl/igl/axis_angle_to_quat.cpp similarity index 100% rename from src/igl/axis_angle_to_quat.cpp rename to src/libigl/igl/axis_angle_to_quat.cpp diff --git a/src/igl/axis_angle_to_quat.h b/src/libigl/igl/axis_angle_to_quat.h similarity index 100% rename from src/igl/axis_angle_to_quat.h rename to src/libigl/igl/axis_angle_to_quat.h diff --git a/src/igl/barycenter.cpp b/src/libigl/igl/barycenter.cpp similarity index 100% rename from src/igl/barycenter.cpp rename to src/libigl/igl/barycenter.cpp diff --git a/src/igl/barycenter.h b/src/libigl/igl/barycenter.h similarity index 100% rename from src/igl/barycenter.h rename to src/libigl/igl/barycenter.h diff --git a/src/igl/barycentric_coordinates.cpp b/src/libigl/igl/barycentric_coordinates.cpp similarity index 100% rename from src/igl/barycentric_coordinates.cpp rename to src/libigl/igl/barycentric_coordinates.cpp diff --git a/src/igl/barycentric_coordinates.h b/src/libigl/igl/barycentric_coordinates.h similarity index 100% rename from src/igl/barycentric_coordinates.h rename to src/libigl/igl/barycentric_coordinates.h diff --git a/src/igl/barycentric_to_global.cpp b/src/libigl/igl/barycentric_to_global.cpp similarity index 100% rename from src/igl/barycentric_to_global.cpp rename to src/libigl/igl/barycentric_to_global.cpp diff --git a/src/igl/barycentric_to_global.h b/src/libigl/igl/barycentric_to_global.h similarity index 100% rename from src/igl/barycentric_to_global.h rename to src/libigl/igl/barycentric_to_global.h diff --git a/src/igl/basename.cpp b/src/libigl/igl/basename.cpp similarity index 100% rename from src/igl/basename.cpp rename to src/libigl/igl/basename.cpp diff --git a/src/igl/basename.h b/src/libigl/igl/basename.h similarity index 100% rename from src/igl/basename.h rename to src/libigl/igl/basename.h diff --git a/src/igl/bbw.cpp b/src/libigl/igl/bbw.cpp similarity index 100% rename from src/igl/bbw.cpp rename to src/libigl/igl/bbw.cpp diff --git a/src/igl/bbw.h b/src/libigl/igl/bbw.h similarity index 100% rename from src/igl/bbw.h rename to src/libigl/igl/bbw.h diff --git a/src/igl/bfs.cpp b/src/libigl/igl/bfs.cpp similarity index 100% rename from src/igl/bfs.cpp rename to src/libigl/igl/bfs.cpp diff --git a/src/igl/bfs.h b/src/libigl/igl/bfs.h similarity index 100% rename from src/igl/bfs.h rename to src/libigl/igl/bfs.h diff --git a/src/igl/bfs_orient.cpp b/src/libigl/igl/bfs_orient.cpp similarity index 100% rename from src/igl/bfs_orient.cpp rename to src/libigl/igl/bfs_orient.cpp diff --git a/src/igl/bfs_orient.h b/src/libigl/igl/bfs_orient.h similarity index 100% rename from src/igl/bfs_orient.h rename to src/libigl/igl/bfs_orient.h diff --git a/src/igl/biharmonic_coordinates.cpp b/src/libigl/igl/biharmonic_coordinates.cpp similarity index 100% rename from src/igl/biharmonic_coordinates.cpp rename to src/libigl/igl/biharmonic_coordinates.cpp diff --git a/src/igl/biharmonic_coordinates.h b/src/libigl/igl/biharmonic_coordinates.h similarity index 100% rename from src/igl/biharmonic_coordinates.h rename to src/libigl/igl/biharmonic_coordinates.h diff --git a/src/igl/bijective_composite_harmonic_mapping.cpp b/src/libigl/igl/bijective_composite_harmonic_mapping.cpp similarity index 100% rename from src/igl/bijective_composite_harmonic_mapping.cpp rename to src/libigl/igl/bijective_composite_harmonic_mapping.cpp diff --git a/src/igl/bijective_composite_harmonic_mapping.h b/src/libigl/igl/bijective_composite_harmonic_mapping.h similarity index 100% rename from src/igl/bijective_composite_harmonic_mapping.h rename to src/libigl/igl/bijective_composite_harmonic_mapping.h diff --git a/src/igl/bone_parents.cpp b/src/libigl/igl/bone_parents.cpp similarity index 100% rename from src/igl/bone_parents.cpp rename to src/libigl/igl/bone_parents.cpp diff --git a/src/igl/bone_parents.h b/src/libigl/igl/bone_parents.h similarity index 100% rename from src/igl/bone_parents.h rename to src/libigl/igl/bone_parents.h diff --git a/src/igl/boundary_conditions.cpp b/src/libigl/igl/boundary_conditions.cpp similarity index 100% rename from src/igl/boundary_conditions.cpp rename to src/libigl/igl/boundary_conditions.cpp diff --git a/src/igl/boundary_conditions.h b/src/libigl/igl/boundary_conditions.h similarity index 100% rename from src/igl/boundary_conditions.h rename to src/libigl/igl/boundary_conditions.h diff --git a/src/igl/boundary_facets.cpp b/src/libigl/igl/boundary_facets.cpp similarity index 100% rename from src/igl/boundary_facets.cpp rename to src/libigl/igl/boundary_facets.cpp diff --git a/src/igl/boundary_facets.h b/src/libigl/igl/boundary_facets.h similarity index 100% rename from src/igl/boundary_facets.h rename to src/libigl/igl/boundary_facets.h diff --git a/src/igl/boundary_loop.cpp b/src/libigl/igl/boundary_loop.cpp similarity index 100% rename from src/igl/boundary_loop.cpp rename to src/libigl/igl/boundary_loop.cpp diff --git a/src/igl/boundary_loop.h b/src/libigl/igl/boundary_loop.h similarity index 100% rename from src/igl/boundary_loop.h rename to src/libigl/igl/boundary_loop.h diff --git a/src/igl/bounding_box.cpp b/src/libigl/igl/bounding_box.cpp similarity index 100% rename from src/igl/bounding_box.cpp rename to src/libigl/igl/bounding_box.cpp diff --git a/src/igl/bounding_box.h b/src/libigl/igl/bounding_box.h similarity index 100% rename from src/igl/bounding_box.h rename to src/libigl/igl/bounding_box.h diff --git a/src/igl/bounding_box_diagonal.cpp b/src/libigl/igl/bounding_box_diagonal.cpp similarity index 100% rename from src/igl/bounding_box_diagonal.cpp rename to src/libigl/igl/bounding_box_diagonal.cpp diff --git a/src/igl/bounding_box_diagonal.h b/src/libigl/igl/bounding_box_diagonal.h similarity index 100% rename from src/igl/bounding_box_diagonal.h rename to src/libigl/igl/bounding_box_diagonal.h diff --git a/src/igl/canonical_quaternions.cpp b/src/libigl/igl/canonical_quaternions.cpp similarity index 100% rename from src/igl/canonical_quaternions.cpp rename to src/libigl/igl/canonical_quaternions.cpp diff --git a/src/igl/canonical_quaternions.h b/src/libigl/igl/canonical_quaternions.h similarity index 100% rename from src/igl/canonical_quaternions.h rename to src/libigl/igl/canonical_quaternions.h diff --git a/src/igl/cat.cpp b/src/libigl/igl/cat.cpp similarity index 100% rename from src/igl/cat.cpp rename to src/libigl/igl/cat.cpp diff --git a/src/igl/cat.h b/src/libigl/igl/cat.h similarity index 100% rename from src/igl/cat.h rename to src/libigl/igl/cat.h diff --git a/src/igl/ceil.cpp b/src/libigl/igl/ceil.cpp similarity index 100% rename from src/igl/ceil.cpp rename to src/libigl/igl/ceil.cpp diff --git a/src/igl/ceil.h b/src/libigl/igl/ceil.h similarity index 100% rename from src/igl/ceil.h rename to src/libigl/igl/ceil.h diff --git a/src/igl/centroid.cpp b/src/libigl/igl/centroid.cpp similarity index 100% rename from src/igl/centroid.cpp rename to src/libigl/igl/centroid.cpp diff --git a/src/igl/centroid.h b/src/libigl/igl/centroid.h similarity index 100% rename from src/igl/centroid.h rename to src/libigl/igl/centroid.h diff --git a/src/igl/circulation.cpp b/src/libigl/igl/circulation.cpp similarity index 100% rename from src/igl/circulation.cpp rename to src/libigl/igl/circulation.cpp diff --git a/src/igl/circulation.h b/src/libigl/igl/circulation.h similarity index 100% rename from src/igl/circulation.h rename to src/libigl/igl/circulation.h diff --git a/src/igl/circumradius.cpp b/src/libigl/igl/circumradius.cpp similarity index 100% rename from src/igl/circumradius.cpp rename to src/libigl/igl/circumradius.cpp diff --git a/src/igl/circumradius.h b/src/libigl/igl/circumradius.h similarity index 100% rename from src/igl/circumradius.h rename to src/libigl/igl/circumradius.h diff --git a/src/igl/collapse_edge.cpp b/src/libigl/igl/collapse_edge.cpp similarity index 100% rename from src/igl/collapse_edge.cpp rename to src/libigl/igl/collapse_edge.cpp diff --git a/src/igl/collapse_edge.h b/src/libigl/igl/collapse_edge.h similarity index 100% rename from src/igl/collapse_edge.h rename to src/libigl/igl/collapse_edge.h diff --git a/src/igl/collapse_small_triangles.cpp b/src/libigl/igl/collapse_small_triangles.cpp similarity index 100% rename from src/igl/collapse_small_triangles.cpp rename to src/libigl/igl/collapse_small_triangles.cpp diff --git a/src/igl/collapse_small_triangles.h b/src/libigl/igl/collapse_small_triangles.h similarity index 100% rename from src/igl/collapse_small_triangles.h rename to src/libigl/igl/collapse_small_triangles.h diff --git a/src/igl/colon.cpp b/src/libigl/igl/colon.cpp similarity index 100% rename from src/igl/colon.cpp rename to src/libigl/igl/colon.cpp diff --git a/src/igl/colon.h b/src/libigl/igl/colon.h similarity index 100% rename from src/igl/colon.h rename to src/libigl/igl/colon.h diff --git a/src/igl/colormap.cpp b/src/libigl/igl/colormap.cpp similarity index 100% rename from src/igl/colormap.cpp rename to src/libigl/igl/colormap.cpp diff --git a/src/igl/colormap.h b/src/libigl/igl/colormap.h similarity index 100% rename from src/igl/colormap.h rename to src/libigl/igl/colormap.h diff --git a/src/igl/column_to_quats.cpp b/src/libigl/igl/column_to_quats.cpp similarity index 100% rename from src/igl/column_to_quats.cpp rename to src/libigl/igl/column_to_quats.cpp diff --git a/src/igl/column_to_quats.h b/src/libigl/igl/column_to_quats.h similarity index 100% rename from src/igl/column_to_quats.h rename to src/libigl/igl/column_to_quats.h diff --git a/src/igl/columnize.cpp b/src/libigl/igl/columnize.cpp similarity index 100% rename from src/igl/columnize.cpp rename to src/libigl/igl/columnize.cpp diff --git a/src/igl/columnize.h b/src/libigl/igl/columnize.h similarity index 100% rename from src/igl/columnize.h rename to src/libigl/igl/columnize.h diff --git a/src/igl/comb_cross_field.cpp b/src/libigl/igl/comb_cross_field.cpp similarity index 100% rename from src/igl/comb_cross_field.cpp rename to src/libigl/igl/comb_cross_field.cpp diff --git a/src/igl/comb_cross_field.h b/src/libigl/igl/comb_cross_field.h similarity index 100% rename from src/igl/comb_cross_field.h rename to src/libigl/igl/comb_cross_field.h diff --git a/src/igl/comb_frame_field.cpp b/src/libigl/igl/comb_frame_field.cpp similarity index 100% rename from src/igl/comb_frame_field.cpp rename to src/libigl/igl/comb_frame_field.cpp diff --git a/src/igl/comb_frame_field.h b/src/libigl/igl/comb_frame_field.h similarity index 100% rename from src/igl/comb_frame_field.h rename to src/libigl/igl/comb_frame_field.h diff --git a/src/igl/comb_line_field.cpp b/src/libigl/igl/comb_line_field.cpp similarity index 100% rename from src/igl/comb_line_field.cpp rename to src/libigl/igl/comb_line_field.cpp diff --git a/src/igl/comb_line_field.h b/src/libigl/igl/comb_line_field.h similarity index 100% rename from src/igl/comb_line_field.h rename to src/libigl/igl/comb_line_field.h diff --git a/src/igl/combine.cpp b/src/libigl/igl/combine.cpp similarity index 100% rename from src/igl/combine.cpp rename to src/libigl/igl/combine.cpp diff --git a/src/igl/combine.h b/src/libigl/igl/combine.h similarity index 100% rename from src/igl/combine.h rename to src/libigl/igl/combine.h diff --git a/src/igl/components.cpp b/src/libigl/igl/components.cpp similarity index 100% rename from src/igl/components.cpp rename to src/libigl/igl/components.cpp diff --git a/src/igl/components.h b/src/libigl/igl/components.h similarity index 100% rename from src/igl/components.h rename to src/libigl/igl/components.h diff --git a/src/igl/compute_frame_field_bisectors.cpp b/src/libigl/igl/compute_frame_field_bisectors.cpp similarity index 100% rename from src/igl/compute_frame_field_bisectors.cpp rename to src/libigl/igl/compute_frame_field_bisectors.cpp diff --git a/src/igl/compute_frame_field_bisectors.h b/src/libigl/igl/compute_frame_field_bisectors.h similarity index 100% rename from src/igl/compute_frame_field_bisectors.h rename to src/libigl/igl/compute_frame_field_bisectors.h diff --git a/src/igl/connect_boundary_to_infinity.cpp b/src/libigl/igl/connect_boundary_to_infinity.cpp similarity index 100% rename from src/igl/connect_boundary_to_infinity.cpp rename to src/libigl/igl/connect_boundary_to_infinity.cpp diff --git a/src/igl/connect_boundary_to_infinity.h b/src/libigl/igl/connect_boundary_to_infinity.h similarity index 100% rename from src/igl/connect_boundary_to_infinity.h rename to src/libigl/igl/connect_boundary_to_infinity.h diff --git a/src/igl/copyleft/README.md b/src/libigl/igl/copyleft/README.md similarity index 100% rename from src/igl/copyleft/README.md rename to src/libigl/igl/copyleft/README.md diff --git a/src/igl/copyleft/cgal/BinaryWindingNumberOperations.h b/src/libigl/igl/copyleft/cgal/BinaryWindingNumberOperations.h similarity index 100% rename from src/igl/copyleft/cgal/BinaryWindingNumberOperations.h rename to src/libigl/igl/copyleft/cgal/BinaryWindingNumberOperations.h diff --git a/src/igl/copyleft/cgal/CGAL_includes.hpp b/src/libigl/igl/copyleft/cgal/CGAL_includes.hpp similarity index 100% rename from src/igl/copyleft/cgal/CGAL_includes.hpp rename to src/libigl/igl/copyleft/cgal/CGAL_includes.hpp diff --git a/src/igl/copyleft/cgal/CSGTree.h b/src/libigl/igl/copyleft/cgal/CSGTree.h similarity index 100% rename from src/igl/copyleft/cgal/CSGTree.h rename to src/libigl/igl/copyleft/cgal/CSGTree.h diff --git a/src/igl/copyleft/cgal/RemeshSelfIntersectionsParam.h b/src/libigl/igl/copyleft/cgal/RemeshSelfIntersectionsParam.h similarity index 100% rename from src/igl/copyleft/cgal/RemeshSelfIntersectionsParam.h rename to src/libigl/igl/copyleft/cgal/RemeshSelfIntersectionsParam.h diff --git a/src/igl/copyleft/cgal/SelfIntersectMesh.h b/src/libigl/igl/copyleft/cgal/SelfIntersectMesh.h similarity index 100% rename from src/igl/copyleft/cgal/SelfIntersectMesh.h rename to src/libigl/igl/copyleft/cgal/SelfIntersectMesh.h diff --git a/src/igl/copyleft/cgal/assign.cpp b/src/libigl/igl/copyleft/cgal/assign.cpp similarity index 100% rename from src/igl/copyleft/cgal/assign.cpp rename to src/libigl/igl/copyleft/cgal/assign.cpp diff --git a/src/igl/copyleft/cgal/assign.h b/src/libigl/igl/copyleft/cgal/assign.h similarity index 100% rename from src/igl/copyleft/cgal/assign.h rename to src/libigl/igl/copyleft/cgal/assign.h diff --git a/src/igl/copyleft/cgal/assign_scalar.cpp b/src/libigl/igl/copyleft/cgal/assign_scalar.cpp similarity index 100% rename from src/igl/copyleft/cgal/assign_scalar.cpp rename to src/libigl/igl/copyleft/cgal/assign_scalar.cpp diff --git a/src/igl/copyleft/cgal/assign_scalar.h b/src/libigl/igl/copyleft/cgal/assign_scalar.h similarity index 100% rename from src/igl/copyleft/cgal/assign_scalar.h rename to src/libigl/igl/copyleft/cgal/assign_scalar.h diff --git a/src/igl/copyleft/cgal/barycenter.cpp b/src/libigl/igl/copyleft/cgal/barycenter.cpp similarity index 100% rename from src/igl/copyleft/cgal/barycenter.cpp rename to src/libigl/igl/copyleft/cgal/barycenter.cpp diff --git a/src/igl/copyleft/cgal/cell_adjacency.cpp b/src/libigl/igl/copyleft/cgal/cell_adjacency.cpp similarity index 100% rename from src/igl/copyleft/cgal/cell_adjacency.cpp rename to src/libigl/igl/copyleft/cgal/cell_adjacency.cpp diff --git a/src/igl/copyleft/cgal/cell_adjacency.h b/src/libigl/igl/copyleft/cgal/cell_adjacency.h similarity index 100% rename from src/igl/copyleft/cgal/cell_adjacency.h rename to src/libigl/igl/copyleft/cgal/cell_adjacency.h diff --git a/src/igl/copyleft/cgal/closest_facet.cpp b/src/libigl/igl/copyleft/cgal/closest_facet.cpp similarity index 100% rename from src/igl/copyleft/cgal/closest_facet.cpp rename to src/libigl/igl/copyleft/cgal/closest_facet.cpp diff --git a/src/igl/copyleft/cgal/closest_facet.h b/src/libigl/igl/copyleft/cgal/closest_facet.h similarity index 100% rename from src/igl/copyleft/cgal/closest_facet.h rename to src/libigl/igl/copyleft/cgal/closest_facet.h diff --git a/src/igl/copyleft/cgal/complex_to_mesh.cpp b/src/libigl/igl/copyleft/cgal/complex_to_mesh.cpp similarity index 100% rename from src/igl/copyleft/cgal/complex_to_mesh.cpp rename to src/libigl/igl/copyleft/cgal/complex_to_mesh.cpp diff --git a/src/igl/copyleft/cgal/complex_to_mesh.h b/src/libigl/igl/copyleft/cgal/complex_to_mesh.h similarity index 100% rename from src/igl/copyleft/cgal/complex_to_mesh.h rename to src/libigl/igl/copyleft/cgal/complex_to_mesh.h diff --git a/src/igl/copyleft/cgal/component_inside_component.cpp b/src/libigl/igl/copyleft/cgal/component_inside_component.cpp similarity index 100% rename from src/igl/copyleft/cgal/component_inside_component.cpp rename to src/libigl/igl/copyleft/cgal/component_inside_component.cpp diff --git a/src/igl/copyleft/cgal/component_inside_component.h b/src/libigl/igl/copyleft/cgal/component_inside_component.h similarity index 100% rename from src/igl/copyleft/cgal/component_inside_component.h rename to src/libigl/igl/copyleft/cgal/component_inside_component.h diff --git a/src/igl/copyleft/cgal/convex_hull.cpp b/src/libigl/igl/copyleft/cgal/convex_hull.cpp similarity index 100% rename from src/igl/copyleft/cgal/convex_hull.cpp rename to src/libigl/igl/copyleft/cgal/convex_hull.cpp diff --git a/src/igl/copyleft/cgal/convex_hull.h b/src/libigl/igl/copyleft/cgal/convex_hull.h similarity index 100% rename from src/igl/copyleft/cgal/convex_hull.h rename to src/libigl/igl/copyleft/cgal/convex_hull.h diff --git a/src/igl/copyleft/cgal/delaunay_triangulation.cpp b/src/libigl/igl/copyleft/cgal/delaunay_triangulation.cpp similarity index 100% rename from src/igl/copyleft/cgal/delaunay_triangulation.cpp rename to src/libigl/igl/copyleft/cgal/delaunay_triangulation.cpp diff --git a/src/igl/copyleft/cgal/delaunay_triangulation.h b/src/libigl/igl/copyleft/cgal/delaunay_triangulation.h similarity index 100% rename from src/igl/copyleft/cgal/delaunay_triangulation.h rename to src/libigl/igl/copyleft/cgal/delaunay_triangulation.h diff --git a/src/igl/copyleft/cgal/extract_cells.cpp b/src/libigl/igl/copyleft/cgal/extract_cells.cpp similarity index 100% rename from src/igl/copyleft/cgal/extract_cells.cpp rename to src/libigl/igl/copyleft/cgal/extract_cells.cpp diff --git a/src/igl/copyleft/cgal/extract_cells.h b/src/libigl/igl/copyleft/cgal/extract_cells.h similarity index 100% rename from src/igl/copyleft/cgal/extract_cells.h rename to src/libigl/igl/copyleft/cgal/extract_cells.h diff --git a/src/igl/copyleft/cgal/extract_feature.cpp b/src/libigl/igl/copyleft/cgal/extract_feature.cpp similarity index 100% rename from src/igl/copyleft/cgal/extract_feature.cpp rename to src/libigl/igl/copyleft/cgal/extract_feature.cpp diff --git a/src/igl/copyleft/cgal/extract_feature.h b/src/libigl/igl/copyleft/cgal/extract_feature.h similarity index 100% rename from src/igl/copyleft/cgal/extract_feature.h rename to src/libigl/igl/copyleft/cgal/extract_feature.h diff --git a/src/igl/copyleft/cgal/fast_winding_number.cpp b/src/libigl/igl/copyleft/cgal/fast_winding_number.cpp similarity index 100% rename from src/igl/copyleft/cgal/fast_winding_number.cpp rename to src/libigl/igl/copyleft/cgal/fast_winding_number.cpp diff --git a/src/igl/copyleft/cgal/fast_winding_number.h b/src/libigl/igl/copyleft/cgal/fast_winding_number.h similarity index 100% rename from src/igl/copyleft/cgal/fast_winding_number.h rename to src/libigl/igl/copyleft/cgal/fast_winding_number.h diff --git a/src/igl/copyleft/cgal/half_space_box.cpp b/src/libigl/igl/copyleft/cgal/half_space_box.cpp similarity index 100% rename from src/igl/copyleft/cgal/half_space_box.cpp rename to src/libigl/igl/copyleft/cgal/half_space_box.cpp diff --git a/src/igl/copyleft/cgal/half_space_box.h b/src/libigl/igl/copyleft/cgal/half_space_box.h similarity index 100% rename from src/igl/copyleft/cgal/half_space_box.h rename to src/libigl/igl/copyleft/cgal/half_space_box.h diff --git a/src/igl/copyleft/cgal/hausdorff.cpp b/src/libigl/igl/copyleft/cgal/hausdorff.cpp similarity index 100% rename from src/igl/copyleft/cgal/hausdorff.cpp rename to src/libigl/igl/copyleft/cgal/hausdorff.cpp diff --git a/src/igl/copyleft/cgal/hausdorff.h b/src/libigl/igl/copyleft/cgal/hausdorff.h similarity index 100% rename from src/igl/copyleft/cgal/hausdorff.h rename to src/libigl/igl/copyleft/cgal/hausdorff.h diff --git a/src/igl/copyleft/cgal/incircle.cpp b/src/libigl/igl/copyleft/cgal/incircle.cpp similarity index 100% rename from src/igl/copyleft/cgal/incircle.cpp rename to src/libigl/igl/copyleft/cgal/incircle.cpp diff --git a/src/igl/copyleft/cgal/incircle.h b/src/libigl/igl/copyleft/cgal/incircle.h similarity index 100% rename from src/igl/copyleft/cgal/incircle.h rename to src/libigl/igl/copyleft/cgal/incircle.h diff --git a/src/igl/copyleft/cgal/insert_into_cdt.cpp b/src/libigl/igl/copyleft/cgal/insert_into_cdt.cpp similarity index 100% rename from src/igl/copyleft/cgal/insert_into_cdt.cpp rename to src/libigl/igl/copyleft/cgal/insert_into_cdt.cpp diff --git a/src/igl/copyleft/cgal/insert_into_cdt.h b/src/libigl/igl/copyleft/cgal/insert_into_cdt.h similarity index 100% rename from src/igl/copyleft/cgal/insert_into_cdt.h rename to src/libigl/igl/copyleft/cgal/insert_into_cdt.h diff --git a/src/igl/copyleft/cgal/insphere.cpp b/src/libigl/igl/copyleft/cgal/insphere.cpp similarity index 100% rename from src/igl/copyleft/cgal/insphere.cpp rename to src/libigl/igl/copyleft/cgal/insphere.cpp diff --git a/src/igl/copyleft/cgal/insphere.h b/src/libigl/igl/copyleft/cgal/insphere.h similarity index 100% rename from src/igl/copyleft/cgal/insphere.h rename to src/libigl/igl/copyleft/cgal/insphere.h diff --git a/src/igl/copyleft/cgal/intersect_other.cpp b/src/libigl/igl/copyleft/cgal/intersect_other.cpp similarity index 100% rename from src/igl/copyleft/cgal/intersect_other.cpp rename to src/libigl/igl/copyleft/cgal/intersect_other.cpp diff --git a/src/igl/copyleft/cgal/intersect_other.h b/src/libigl/igl/copyleft/cgal/intersect_other.h similarity index 100% rename from src/igl/copyleft/cgal/intersect_other.h rename to src/libigl/igl/copyleft/cgal/intersect_other.h diff --git a/src/igl/copyleft/cgal/intersect_with_half_space.cpp b/src/libigl/igl/copyleft/cgal/intersect_with_half_space.cpp similarity index 100% rename from src/igl/copyleft/cgal/intersect_with_half_space.cpp rename to src/libigl/igl/copyleft/cgal/intersect_with_half_space.cpp diff --git a/src/igl/copyleft/cgal/intersect_with_half_space.h b/src/libigl/igl/copyleft/cgal/intersect_with_half_space.h similarity index 100% rename from src/igl/copyleft/cgal/intersect_with_half_space.h rename to src/libigl/igl/copyleft/cgal/intersect_with_half_space.h diff --git a/src/igl/copyleft/cgal/lexicographic_triangulation.cpp b/src/libigl/igl/copyleft/cgal/lexicographic_triangulation.cpp similarity index 100% rename from src/igl/copyleft/cgal/lexicographic_triangulation.cpp rename to src/libigl/igl/copyleft/cgal/lexicographic_triangulation.cpp diff --git a/src/igl/copyleft/cgal/lexicographic_triangulation.h b/src/libigl/igl/copyleft/cgal/lexicographic_triangulation.h similarity index 100% rename from src/igl/copyleft/cgal/lexicographic_triangulation.h rename to src/libigl/igl/copyleft/cgal/lexicographic_triangulation.h diff --git a/src/igl/copyleft/cgal/list_to_matrix.cpp b/src/libigl/igl/copyleft/cgal/list_to_matrix.cpp similarity index 100% rename from src/igl/copyleft/cgal/list_to_matrix.cpp rename to src/libigl/igl/copyleft/cgal/list_to_matrix.cpp diff --git a/src/igl/copyleft/cgal/mesh_boolean.cpp b/src/libigl/igl/copyleft/cgal/mesh_boolean.cpp similarity index 100% rename from src/igl/copyleft/cgal/mesh_boolean.cpp rename to src/libigl/igl/copyleft/cgal/mesh_boolean.cpp diff --git a/src/igl/copyleft/cgal/mesh_boolean.h b/src/libigl/igl/copyleft/cgal/mesh_boolean.h similarity index 100% rename from src/igl/copyleft/cgal/mesh_boolean.h rename to src/libigl/igl/copyleft/cgal/mesh_boolean.h diff --git a/src/igl/copyleft/cgal/mesh_boolean_type_to_funcs.cpp b/src/libigl/igl/copyleft/cgal/mesh_boolean_type_to_funcs.cpp similarity index 100% rename from src/igl/copyleft/cgal/mesh_boolean_type_to_funcs.cpp rename to src/libigl/igl/copyleft/cgal/mesh_boolean_type_to_funcs.cpp diff --git a/src/igl/copyleft/cgal/mesh_boolean_type_to_funcs.h b/src/libigl/igl/copyleft/cgal/mesh_boolean_type_to_funcs.h similarity index 100% rename from src/igl/copyleft/cgal/mesh_boolean_type_to_funcs.h rename to src/libigl/igl/copyleft/cgal/mesh_boolean_type_to_funcs.h diff --git a/src/igl/copyleft/cgal/mesh_to_cgal_triangle_list.cpp b/src/libigl/igl/copyleft/cgal/mesh_to_cgal_triangle_list.cpp similarity index 100% rename from src/igl/copyleft/cgal/mesh_to_cgal_triangle_list.cpp rename to src/libigl/igl/copyleft/cgal/mesh_to_cgal_triangle_list.cpp diff --git a/src/igl/copyleft/cgal/mesh_to_cgal_triangle_list.h b/src/libigl/igl/copyleft/cgal/mesh_to_cgal_triangle_list.h similarity index 100% rename from src/igl/copyleft/cgal/mesh_to_cgal_triangle_list.h rename to src/libigl/igl/copyleft/cgal/mesh_to_cgal_triangle_list.h diff --git a/src/igl/copyleft/cgal/mesh_to_polyhedron.cpp b/src/libigl/igl/copyleft/cgal/mesh_to_polyhedron.cpp similarity index 100% rename from src/igl/copyleft/cgal/mesh_to_polyhedron.cpp rename to src/libigl/igl/copyleft/cgal/mesh_to_polyhedron.cpp diff --git a/src/igl/copyleft/cgal/mesh_to_polyhedron.h b/src/libigl/igl/copyleft/cgal/mesh_to_polyhedron.h similarity index 100% rename from src/igl/copyleft/cgal/mesh_to_polyhedron.h rename to src/libigl/igl/copyleft/cgal/mesh_to_polyhedron.h diff --git a/src/igl/copyleft/cgal/minkowski_sum.cpp b/src/libigl/igl/copyleft/cgal/minkowski_sum.cpp similarity index 100% rename from src/igl/copyleft/cgal/minkowski_sum.cpp rename to src/libigl/igl/copyleft/cgal/minkowski_sum.cpp diff --git a/src/igl/copyleft/cgal/minkowski_sum.h b/src/libigl/igl/copyleft/cgal/minkowski_sum.h similarity index 100% rename from src/igl/copyleft/cgal/minkowski_sum.h rename to src/libigl/igl/copyleft/cgal/minkowski_sum.h diff --git a/src/igl/copyleft/cgal/order_facets_around_edge.cpp b/src/libigl/igl/copyleft/cgal/order_facets_around_edge.cpp similarity index 100% rename from src/igl/copyleft/cgal/order_facets_around_edge.cpp rename to src/libigl/igl/copyleft/cgal/order_facets_around_edge.cpp diff --git a/src/igl/copyleft/cgal/order_facets_around_edge.h b/src/libigl/igl/copyleft/cgal/order_facets_around_edge.h similarity index 100% rename from src/igl/copyleft/cgal/order_facets_around_edge.h rename to src/libigl/igl/copyleft/cgal/order_facets_around_edge.h diff --git a/src/igl/copyleft/cgal/order_facets_around_edges.cpp b/src/libigl/igl/copyleft/cgal/order_facets_around_edges.cpp similarity index 100% rename from src/igl/copyleft/cgal/order_facets_around_edges.cpp rename to src/libigl/igl/copyleft/cgal/order_facets_around_edges.cpp diff --git a/src/igl/copyleft/cgal/order_facets_around_edges.h b/src/libigl/igl/copyleft/cgal/order_facets_around_edges.h similarity index 100% rename from src/igl/copyleft/cgal/order_facets_around_edges.h rename to src/libigl/igl/copyleft/cgal/order_facets_around_edges.h diff --git a/src/igl/copyleft/cgal/orient2D.cpp b/src/libigl/igl/copyleft/cgal/orient2D.cpp similarity index 100% rename from src/igl/copyleft/cgal/orient2D.cpp rename to src/libigl/igl/copyleft/cgal/orient2D.cpp diff --git a/src/igl/copyleft/cgal/orient2D.h b/src/libigl/igl/copyleft/cgal/orient2D.h similarity index 100% rename from src/igl/copyleft/cgal/orient2D.h rename to src/libigl/igl/copyleft/cgal/orient2D.h diff --git a/src/igl/copyleft/cgal/orient3D.cpp b/src/libigl/igl/copyleft/cgal/orient3D.cpp similarity index 100% rename from src/igl/copyleft/cgal/orient3D.cpp rename to src/libigl/igl/copyleft/cgal/orient3D.cpp diff --git a/src/igl/copyleft/cgal/orient3D.h b/src/libigl/igl/copyleft/cgal/orient3D.h similarity index 100% rename from src/igl/copyleft/cgal/orient3D.h rename to src/libigl/igl/copyleft/cgal/orient3D.h diff --git a/src/igl/copyleft/cgal/outer_element.cpp b/src/libigl/igl/copyleft/cgal/outer_element.cpp similarity index 100% rename from src/igl/copyleft/cgal/outer_element.cpp rename to src/libigl/igl/copyleft/cgal/outer_element.cpp diff --git a/src/igl/copyleft/cgal/outer_element.h b/src/libigl/igl/copyleft/cgal/outer_element.h similarity index 100% rename from src/igl/copyleft/cgal/outer_element.h rename to src/libigl/igl/copyleft/cgal/outer_element.h diff --git a/src/igl/copyleft/cgal/outer_facet.cpp b/src/libigl/igl/copyleft/cgal/outer_facet.cpp similarity index 100% rename from src/igl/copyleft/cgal/outer_facet.cpp rename to src/libigl/igl/copyleft/cgal/outer_facet.cpp diff --git a/src/igl/copyleft/cgal/outer_facet.h b/src/libigl/igl/copyleft/cgal/outer_facet.h similarity index 100% rename from src/igl/copyleft/cgal/outer_facet.h rename to src/libigl/igl/copyleft/cgal/outer_facet.h diff --git a/src/igl/copyleft/cgal/outer_hull.cpp b/src/libigl/igl/copyleft/cgal/outer_hull.cpp similarity index 100% rename from src/igl/copyleft/cgal/outer_hull.cpp rename to src/libigl/igl/copyleft/cgal/outer_hull.cpp diff --git a/src/igl/copyleft/cgal/outer_hull.h b/src/libigl/igl/copyleft/cgal/outer_hull.h similarity index 100% rename from src/igl/copyleft/cgal/outer_hull.h rename to src/libigl/igl/copyleft/cgal/outer_hull.h diff --git a/src/igl/copyleft/cgal/peel_outer_hull_layers.cpp b/src/libigl/igl/copyleft/cgal/peel_outer_hull_layers.cpp similarity index 100% rename from src/igl/copyleft/cgal/peel_outer_hull_layers.cpp rename to src/libigl/igl/copyleft/cgal/peel_outer_hull_layers.cpp diff --git a/src/igl/copyleft/cgal/peel_outer_hull_layers.h b/src/libigl/igl/copyleft/cgal/peel_outer_hull_layers.h similarity index 100% rename from src/igl/copyleft/cgal/peel_outer_hull_layers.h rename to src/libigl/igl/copyleft/cgal/peel_outer_hull_layers.h diff --git a/src/igl/copyleft/cgal/peel_winding_number_layers.cpp b/src/libigl/igl/copyleft/cgal/peel_winding_number_layers.cpp similarity index 100% rename from src/igl/copyleft/cgal/peel_winding_number_layers.cpp rename to src/libigl/igl/copyleft/cgal/peel_winding_number_layers.cpp diff --git a/src/igl/copyleft/cgal/peel_winding_number_layers.h b/src/libigl/igl/copyleft/cgal/peel_winding_number_layers.h similarity index 100% rename from src/igl/copyleft/cgal/peel_winding_number_layers.h rename to src/libigl/igl/copyleft/cgal/peel_winding_number_layers.h diff --git a/src/igl/copyleft/cgal/piecewise_constant_winding_number.cpp b/src/libigl/igl/copyleft/cgal/piecewise_constant_winding_number.cpp similarity index 100% rename from src/igl/copyleft/cgal/piecewise_constant_winding_number.cpp rename to src/libigl/igl/copyleft/cgal/piecewise_constant_winding_number.cpp diff --git a/src/igl/copyleft/cgal/piecewise_constant_winding_number.h b/src/libigl/igl/copyleft/cgal/piecewise_constant_winding_number.h similarity index 100% rename from src/igl/copyleft/cgal/piecewise_constant_winding_number.h rename to src/libigl/igl/copyleft/cgal/piecewise_constant_winding_number.h diff --git a/src/igl/copyleft/cgal/point_areas.cpp b/src/libigl/igl/copyleft/cgal/point_areas.cpp similarity index 100% rename from src/igl/copyleft/cgal/point_areas.cpp rename to src/libigl/igl/copyleft/cgal/point_areas.cpp diff --git a/src/igl/copyleft/cgal/point_areas.h b/src/libigl/igl/copyleft/cgal/point_areas.h similarity index 100% rename from src/igl/copyleft/cgal/point_areas.h rename to src/libigl/igl/copyleft/cgal/point_areas.h diff --git a/src/igl/copyleft/cgal/point_mesh_squared_distance.cpp b/src/libigl/igl/copyleft/cgal/point_mesh_squared_distance.cpp similarity index 100% rename from src/igl/copyleft/cgal/point_mesh_squared_distance.cpp rename to src/libigl/igl/copyleft/cgal/point_mesh_squared_distance.cpp diff --git a/src/igl/copyleft/cgal/point_mesh_squared_distance.h b/src/libigl/igl/copyleft/cgal/point_mesh_squared_distance.h similarity index 100% rename from src/igl/copyleft/cgal/point_mesh_squared_distance.h rename to src/libigl/igl/copyleft/cgal/point_mesh_squared_distance.h diff --git a/src/igl/copyleft/cgal/point_segment_squared_distance.cpp b/src/libigl/igl/copyleft/cgal/point_segment_squared_distance.cpp similarity index 100% rename from src/igl/copyleft/cgal/point_segment_squared_distance.cpp rename to src/libigl/igl/copyleft/cgal/point_segment_squared_distance.cpp diff --git a/src/igl/copyleft/cgal/point_segment_squared_distance.h b/src/libigl/igl/copyleft/cgal/point_segment_squared_distance.h similarity index 100% rename from src/igl/copyleft/cgal/point_segment_squared_distance.h rename to src/libigl/igl/copyleft/cgal/point_segment_squared_distance.h diff --git a/src/igl/copyleft/cgal/point_solid_signed_squared_distance.cpp b/src/libigl/igl/copyleft/cgal/point_solid_signed_squared_distance.cpp similarity index 100% rename from src/igl/copyleft/cgal/point_solid_signed_squared_distance.cpp rename to src/libigl/igl/copyleft/cgal/point_solid_signed_squared_distance.cpp diff --git a/src/igl/copyleft/cgal/point_solid_signed_squared_distance.h b/src/libigl/igl/copyleft/cgal/point_solid_signed_squared_distance.h similarity index 100% rename from src/igl/copyleft/cgal/point_solid_signed_squared_distance.h rename to src/libigl/igl/copyleft/cgal/point_solid_signed_squared_distance.h diff --git a/src/igl/copyleft/cgal/point_triangle_squared_distance.cpp b/src/libigl/igl/copyleft/cgal/point_triangle_squared_distance.cpp similarity index 100% rename from src/igl/copyleft/cgal/point_triangle_squared_distance.cpp rename to src/libigl/igl/copyleft/cgal/point_triangle_squared_distance.cpp diff --git a/src/igl/copyleft/cgal/point_triangle_squared_distance.h b/src/libigl/igl/copyleft/cgal/point_triangle_squared_distance.h similarity index 100% rename from src/igl/copyleft/cgal/point_triangle_squared_distance.h rename to src/libigl/igl/copyleft/cgal/point_triangle_squared_distance.h diff --git a/src/igl/copyleft/cgal/points_inside_component.cpp b/src/libigl/igl/copyleft/cgal/points_inside_component.cpp similarity index 100% rename from src/igl/copyleft/cgal/points_inside_component.cpp rename to src/libigl/igl/copyleft/cgal/points_inside_component.cpp diff --git a/src/igl/copyleft/cgal/points_inside_component.h b/src/libigl/igl/copyleft/cgal/points_inside_component.h similarity index 100% rename from src/igl/copyleft/cgal/points_inside_component.h rename to src/libigl/igl/copyleft/cgal/points_inside_component.h diff --git a/src/igl/copyleft/cgal/polyhedron_to_mesh.cpp b/src/libigl/igl/copyleft/cgal/polyhedron_to_mesh.cpp similarity index 100% rename from src/igl/copyleft/cgal/polyhedron_to_mesh.cpp rename to src/libigl/igl/copyleft/cgal/polyhedron_to_mesh.cpp diff --git a/src/igl/copyleft/cgal/polyhedron_to_mesh.h b/src/libigl/igl/copyleft/cgal/polyhedron_to_mesh.h similarity index 100% rename from src/igl/copyleft/cgal/polyhedron_to_mesh.h rename to src/libigl/igl/copyleft/cgal/polyhedron_to_mesh.h diff --git a/src/igl/copyleft/cgal/projected_cdt.cpp b/src/libigl/igl/copyleft/cgal/projected_cdt.cpp similarity index 100% rename from src/igl/copyleft/cgal/projected_cdt.cpp rename to src/libigl/igl/copyleft/cgal/projected_cdt.cpp diff --git a/src/igl/copyleft/cgal/projected_cdt.h b/src/libigl/igl/copyleft/cgal/projected_cdt.h similarity index 100% rename from src/igl/copyleft/cgal/projected_cdt.h rename to src/libigl/igl/copyleft/cgal/projected_cdt.h diff --git a/src/igl/copyleft/cgal/projected_delaunay.cpp b/src/libigl/igl/copyleft/cgal/projected_delaunay.cpp similarity index 100% rename from src/igl/copyleft/cgal/projected_delaunay.cpp rename to src/libigl/igl/copyleft/cgal/projected_delaunay.cpp diff --git a/src/igl/copyleft/cgal/projected_delaunay.h b/src/libigl/igl/copyleft/cgal/projected_delaunay.h similarity index 100% rename from src/igl/copyleft/cgal/projected_delaunay.h rename to src/libigl/igl/copyleft/cgal/projected_delaunay.h diff --git a/src/igl/copyleft/cgal/propagate_winding_numbers.cpp b/src/libigl/igl/copyleft/cgal/propagate_winding_numbers.cpp similarity index 100% rename from src/igl/copyleft/cgal/propagate_winding_numbers.cpp rename to src/libigl/igl/copyleft/cgal/propagate_winding_numbers.cpp diff --git a/src/igl/copyleft/cgal/propagate_winding_numbers.h b/src/libigl/igl/copyleft/cgal/propagate_winding_numbers.h similarity index 100% rename from src/igl/copyleft/cgal/propagate_winding_numbers.h rename to src/libigl/igl/copyleft/cgal/propagate_winding_numbers.h diff --git a/src/igl/copyleft/cgal/read_triangle_mesh.cpp b/src/libigl/igl/copyleft/cgal/read_triangle_mesh.cpp similarity index 100% rename from src/igl/copyleft/cgal/read_triangle_mesh.cpp rename to src/libigl/igl/copyleft/cgal/read_triangle_mesh.cpp diff --git a/src/igl/copyleft/cgal/read_triangle_mesh.h b/src/libigl/igl/copyleft/cgal/read_triangle_mesh.h similarity index 100% rename from src/igl/copyleft/cgal/read_triangle_mesh.h rename to src/libigl/igl/copyleft/cgal/read_triangle_mesh.h diff --git a/src/igl/copyleft/cgal/relabel_small_immersed_cells.cpp b/src/libigl/igl/copyleft/cgal/relabel_small_immersed_cells.cpp similarity index 100% rename from src/igl/copyleft/cgal/relabel_small_immersed_cells.cpp rename to src/libigl/igl/copyleft/cgal/relabel_small_immersed_cells.cpp diff --git a/src/igl/copyleft/cgal/relabel_small_immersed_cells.h b/src/libigl/igl/copyleft/cgal/relabel_small_immersed_cells.h similarity index 100% rename from src/igl/copyleft/cgal/relabel_small_immersed_cells.h rename to src/libigl/igl/copyleft/cgal/relabel_small_immersed_cells.h diff --git a/src/igl/copyleft/cgal/remesh_intersections.cpp b/src/libigl/igl/copyleft/cgal/remesh_intersections.cpp similarity index 100% rename from src/igl/copyleft/cgal/remesh_intersections.cpp rename to src/libigl/igl/copyleft/cgal/remesh_intersections.cpp diff --git a/src/igl/copyleft/cgal/remesh_intersections.h b/src/libigl/igl/copyleft/cgal/remesh_intersections.h similarity index 100% rename from src/igl/copyleft/cgal/remesh_intersections.h rename to src/libigl/igl/copyleft/cgal/remesh_intersections.h diff --git a/src/igl/copyleft/cgal/remesh_self_intersections.cpp b/src/libigl/igl/copyleft/cgal/remesh_self_intersections.cpp similarity index 100% rename from src/igl/copyleft/cgal/remesh_self_intersections.cpp rename to src/libigl/igl/copyleft/cgal/remesh_self_intersections.cpp diff --git a/src/igl/copyleft/cgal/remesh_self_intersections.h b/src/libigl/igl/copyleft/cgal/remesh_self_intersections.h similarity index 100% rename from src/igl/copyleft/cgal/remesh_self_intersections.h rename to src/libigl/igl/copyleft/cgal/remesh_self_intersections.h diff --git a/src/igl/copyleft/cgal/remove_unreferenced.cpp b/src/libigl/igl/copyleft/cgal/remove_unreferenced.cpp similarity index 100% rename from src/igl/copyleft/cgal/remove_unreferenced.cpp rename to src/libigl/igl/copyleft/cgal/remove_unreferenced.cpp diff --git a/src/igl/copyleft/cgal/resolve_intersections.cpp b/src/libigl/igl/copyleft/cgal/resolve_intersections.cpp similarity index 100% rename from src/igl/copyleft/cgal/resolve_intersections.cpp rename to src/libigl/igl/copyleft/cgal/resolve_intersections.cpp diff --git a/src/igl/copyleft/cgal/resolve_intersections.h b/src/libigl/igl/copyleft/cgal/resolve_intersections.h similarity index 100% rename from src/igl/copyleft/cgal/resolve_intersections.h rename to src/libigl/igl/copyleft/cgal/resolve_intersections.h diff --git a/src/igl/copyleft/cgal/row_to_point.cpp b/src/libigl/igl/copyleft/cgal/row_to_point.cpp similarity index 100% rename from src/igl/copyleft/cgal/row_to_point.cpp rename to src/libigl/igl/copyleft/cgal/row_to_point.cpp diff --git a/src/igl/copyleft/cgal/row_to_point.h b/src/libigl/igl/copyleft/cgal/row_to_point.h similarity index 100% rename from src/igl/copyleft/cgal/row_to_point.h rename to src/libigl/igl/copyleft/cgal/row_to_point.h diff --git a/src/igl/copyleft/cgal/segment_segment_squared_distance.cpp b/src/libigl/igl/copyleft/cgal/segment_segment_squared_distance.cpp similarity index 100% rename from src/igl/copyleft/cgal/segment_segment_squared_distance.cpp rename to src/libigl/igl/copyleft/cgal/segment_segment_squared_distance.cpp diff --git a/src/igl/copyleft/cgal/segment_segment_squared_distance.h b/src/libigl/igl/copyleft/cgal/segment_segment_squared_distance.h similarity index 100% rename from src/igl/copyleft/cgal/segment_segment_squared_distance.h rename to src/libigl/igl/copyleft/cgal/segment_segment_squared_distance.h diff --git a/src/igl/copyleft/cgal/signed_distance_isosurface.cpp b/src/libigl/igl/copyleft/cgal/signed_distance_isosurface.cpp similarity index 100% rename from src/igl/copyleft/cgal/signed_distance_isosurface.cpp rename to src/libigl/igl/copyleft/cgal/signed_distance_isosurface.cpp diff --git a/src/igl/copyleft/cgal/signed_distance_isosurface.h b/src/libigl/igl/copyleft/cgal/signed_distance_isosurface.h similarity index 100% rename from src/igl/copyleft/cgal/signed_distance_isosurface.h rename to src/libigl/igl/copyleft/cgal/signed_distance_isosurface.h diff --git a/src/igl/copyleft/cgal/slice.cpp b/src/libigl/igl/copyleft/cgal/slice.cpp similarity index 100% rename from src/igl/copyleft/cgal/slice.cpp rename to src/libigl/igl/copyleft/cgal/slice.cpp diff --git a/src/igl/copyleft/cgal/slice_mask.cpp b/src/libigl/igl/copyleft/cgal/slice_mask.cpp similarity index 100% rename from src/igl/copyleft/cgal/slice_mask.cpp rename to src/libigl/igl/copyleft/cgal/slice_mask.cpp diff --git a/src/igl/copyleft/cgal/snap_rounding.cpp b/src/libigl/igl/copyleft/cgal/snap_rounding.cpp similarity index 100% rename from src/igl/copyleft/cgal/snap_rounding.cpp rename to src/libigl/igl/copyleft/cgal/snap_rounding.cpp diff --git a/src/igl/copyleft/cgal/snap_rounding.h b/src/libigl/igl/copyleft/cgal/snap_rounding.h similarity index 100% rename from src/igl/copyleft/cgal/snap_rounding.h rename to src/libigl/igl/copyleft/cgal/snap_rounding.h diff --git a/src/igl/copyleft/cgal/string_to_mesh_boolean_type.cpp b/src/libigl/igl/copyleft/cgal/string_to_mesh_boolean_type.cpp similarity index 100% rename from src/igl/copyleft/cgal/string_to_mesh_boolean_type.cpp rename to src/libigl/igl/copyleft/cgal/string_to_mesh_boolean_type.cpp diff --git a/src/igl/copyleft/cgal/string_to_mesh_boolean_type.h b/src/libigl/igl/copyleft/cgal/string_to_mesh_boolean_type.h similarity index 100% rename from src/igl/copyleft/cgal/string_to_mesh_boolean_type.h rename to src/libigl/igl/copyleft/cgal/string_to_mesh_boolean_type.h diff --git a/src/igl/copyleft/cgal/subdivide_segments.cpp b/src/libigl/igl/copyleft/cgal/subdivide_segments.cpp similarity index 100% rename from src/igl/copyleft/cgal/subdivide_segments.cpp rename to src/libigl/igl/copyleft/cgal/subdivide_segments.cpp diff --git a/src/igl/copyleft/cgal/subdivide_segments.h b/src/libigl/igl/copyleft/cgal/subdivide_segments.h similarity index 100% rename from src/igl/copyleft/cgal/subdivide_segments.h rename to src/libigl/igl/copyleft/cgal/subdivide_segments.h diff --git a/src/igl/copyleft/cgal/submesh_aabb_tree.cpp b/src/libigl/igl/copyleft/cgal/submesh_aabb_tree.cpp similarity index 100% rename from src/igl/copyleft/cgal/submesh_aabb_tree.cpp rename to src/libigl/igl/copyleft/cgal/submesh_aabb_tree.cpp diff --git a/src/igl/copyleft/cgal/submesh_aabb_tree.h b/src/libigl/igl/copyleft/cgal/submesh_aabb_tree.h similarity index 100% rename from src/igl/copyleft/cgal/submesh_aabb_tree.h rename to src/libigl/igl/copyleft/cgal/submesh_aabb_tree.h diff --git a/src/igl/copyleft/cgal/triangle_triangle_squared_distance.cpp b/src/libigl/igl/copyleft/cgal/triangle_triangle_squared_distance.cpp similarity index 100% rename from src/igl/copyleft/cgal/triangle_triangle_squared_distance.cpp rename to src/libigl/igl/copyleft/cgal/triangle_triangle_squared_distance.cpp diff --git a/src/igl/copyleft/cgal/triangle_triangle_squared_distance.h b/src/libigl/igl/copyleft/cgal/triangle_triangle_squared_distance.h similarity index 100% rename from src/igl/copyleft/cgal/triangle_triangle_squared_distance.h rename to src/libigl/igl/copyleft/cgal/triangle_triangle_squared_distance.h diff --git a/src/igl/copyleft/cgal/trim_with_solid.cpp b/src/libigl/igl/copyleft/cgal/trim_with_solid.cpp similarity index 100% rename from src/igl/copyleft/cgal/trim_with_solid.cpp rename to src/libigl/igl/copyleft/cgal/trim_with_solid.cpp diff --git a/src/igl/copyleft/cgal/trim_with_solid.h b/src/libigl/igl/copyleft/cgal/trim_with_solid.h similarity index 100% rename from src/igl/copyleft/cgal/trim_with_solid.h rename to src/libigl/igl/copyleft/cgal/trim_with_solid.h diff --git a/src/igl/copyleft/cgal/unique.cpp b/src/libigl/igl/copyleft/cgal/unique.cpp similarity index 100% rename from src/igl/copyleft/cgal/unique.cpp rename to src/libigl/igl/copyleft/cgal/unique.cpp diff --git a/src/igl/copyleft/cgal/unique_rows.cpp b/src/libigl/igl/copyleft/cgal/unique_rows.cpp similarity index 100% rename from src/igl/copyleft/cgal/unique_rows.cpp rename to src/libigl/igl/copyleft/cgal/unique_rows.cpp diff --git a/src/igl/copyleft/cgal/wire_mesh.cpp b/src/libigl/igl/copyleft/cgal/wire_mesh.cpp similarity index 100% rename from src/igl/copyleft/cgal/wire_mesh.cpp rename to src/libigl/igl/copyleft/cgal/wire_mesh.cpp diff --git a/src/igl/copyleft/cgal/wire_mesh.h b/src/libigl/igl/copyleft/cgal/wire_mesh.h similarity index 100% rename from src/igl/copyleft/cgal/wire_mesh.h rename to src/libigl/igl/copyleft/cgal/wire_mesh.h diff --git a/src/igl/copyleft/comiso/frame_field.cpp b/src/libigl/igl/copyleft/comiso/frame_field.cpp similarity index 100% rename from src/igl/copyleft/comiso/frame_field.cpp rename to src/libigl/igl/copyleft/comiso/frame_field.cpp diff --git a/src/igl/copyleft/comiso/frame_field.h b/src/libigl/igl/copyleft/comiso/frame_field.h similarity index 100% rename from src/igl/copyleft/comiso/frame_field.h rename to src/libigl/igl/copyleft/comiso/frame_field.h diff --git a/src/igl/copyleft/comiso/miq.cpp b/src/libigl/igl/copyleft/comiso/miq.cpp similarity index 100% rename from src/igl/copyleft/comiso/miq.cpp rename to src/libigl/igl/copyleft/comiso/miq.cpp diff --git a/src/igl/copyleft/comiso/miq.h b/src/libigl/igl/copyleft/comiso/miq.h similarity index 100% rename from src/igl/copyleft/comiso/miq.h rename to src/libigl/igl/copyleft/comiso/miq.h diff --git a/src/igl/copyleft/comiso/nrosy.cpp b/src/libigl/igl/copyleft/comiso/nrosy.cpp similarity index 100% rename from src/igl/copyleft/comiso/nrosy.cpp rename to src/libigl/igl/copyleft/comiso/nrosy.cpp diff --git a/src/igl/copyleft/comiso/nrosy.h b/src/libigl/igl/copyleft/comiso/nrosy.h similarity index 100% rename from src/igl/copyleft/comiso/nrosy.h rename to src/libigl/igl/copyleft/comiso/nrosy.h diff --git a/src/igl/copyleft/cork/from_cork_mesh.cpp b/src/libigl/igl/copyleft/cork/from_cork_mesh.cpp similarity index 100% rename from src/igl/copyleft/cork/from_cork_mesh.cpp rename to src/libigl/igl/copyleft/cork/from_cork_mesh.cpp diff --git a/src/igl/copyleft/cork/from_cork_mesh.h b/src/libigl/igl/copyleft/cork/from_cork_mesh.h similarity index 100% rename from src/igl/copyleft/cork/from_cork_mesh.h rename to src/libigl/igl/copyleft/cork/from_cork_mesh.h diff --git a/src/igl/copyleft/cork/mesh_boolean.cpp b/src/libigl/igl/copyleft/cork/mesh_boolean.cpp similarity index 100% rename from src/igl/copyleft/cork/mesh_boolean.cpp rename to src/libigl/igl/copyleft/cork/mesh_boolean.cpp diff --git a/src/igl/copyleft/cork/mesh_boolean.h b/src/libigl/igl/copyleft/cork/mesh_boolean.h similarity index 100% rename from src/igl/copyleft/cork/mesh_boolean.h rename to src/libigl/igl/copyleft/cork/mesh_boolean.h diff --git a/src/igl/copyleft/cork/to_cork_mesh.cpp b/src/libigl/igl/copyleft/cork/to_cork_mesh.cpp similarity index 100% rename from src/igl/copyleft/cork/to_cork_mesh.cpp rename to src/libigl/igl/copyleft/cork/to_cork_mesh.cpp diff --git a/src/igl/copyleft/cork/to_cork_mesh.h b/src/libigl/igl/copyleft/cork/to_cork_mesh.h similarity index 100% rename from src/igl/copyleft/cork/to_cork_mesh.h rename to src/libigl/igl/copyleft/cork/to_cork_mesh.h diff --git a/src/igl/copyleft/marching_cubes.cpp b/src/libigl/igl/copyleft/marching_cubes.cpp similarity index 100% rename from src/igl/copyleft/marching_cubes.cpp rename to src/libigl/igl/copyleft/marching_cubes.cpp diff --git a/src/igl/copyleft/marching_cubes.h b/src/libigl/igl/copyleft/marching_cubes.h similarity index 100% rename from src/igl/copyleft/marching_cubes.h rename to src/libigl/igl/copyleft/marching_cubes.h diff --git a/src/igl/copyleft/marching_cubes_tables.h b/src/libigl/igl/copyleft/marching_cubes_tables.h similarity index 100% rename from src/igl/copyleft/marching_cubes_tables.h rename to src/libigl/igl/copyleft/marching_cubes_tables.h diff --git a/src/igl/copyleft/offset_surface.cpp b/src/libigl/igl/copyleft/offset_surface.cpp similarity index 100% rename from src/igl/copyleft/offset_surface.cpp rename to src/libigl/igl/copyleft/offset_surface.cpp diff --git a/src/igl/copyleft/offset_surface.h b/src/libigl/igl/copyleft/offset_surface.h similarity index 100% rename from src/igl/copyleft/offset_surface.h rename to src/libigl/igl/copyleft/offset_surface.h diff --git a/src/igl/copyleft/opengl2/render_to_tga.cpp b/src/libigl/igl/copyleft/opengl2/render_to_tga.cpp similarity index 100% rename from src/igl/copyleft/opengl2/render_to_tga.cpp rename to src/libigl/igl/copyleft/opengl2/render_to_tga.cpp diff --git a/src/igl/copyleft/opengl2/render_to_tga.h b/src/libigl/igl/copyleft/opengl2/render_to_tga.h similarity index 100% rename from src/igl/copyleft/opengl2/render_to_tga.h rename to src/libigl/igl/copyleft/opengl2/render_to_tga.h diff --git a/src/igl/copyleft/opengl2/texture_from_tga.cpp b/src/libigl/igl/copyleft/opengl2/texture_from_tga.cpp similarity index 100% rename from src/igl/copyleft/opengl2/texture_from_tga.cpp rename to src/libigl/igl/copyleft/opengl2/texture_from_tga.cpp diff --git a/src/igl/copyleft/opengl2/texture_from_tga.h b/src/libigl/igl/copyleft/opengl2/texture_from_tga.h similarity index 100% rename from src/igl/copyleft/opengl2/texture_from_tga.h rename to src/libigl/igl/copyleft/opengl2/texture_from_tga.h diff --git a/src/igl/copyleft/opengl2/tga.cpp b/src/libigl/igl/copyleft/opengl2/tga.cpp similarity index 100% rename from src/igl/copyleft/opengl2/tga.cpp rename to src/libigl/igl/copyleft/opengl2/tga.cpp diff --git a/src/igl/copyleft/opengl2/tga.h b/src/libigl/igl/copyleft/opengl2/tga.h similarity index 100% rename from src/igl/copyleft/opengl2/tga.h rename to src/libigl/igl/copyleft/opengl2/tga.h diff --git a/src/igl/copyleft/progressive_hulls.cpp b/src/libigl/igl/copyleft/progressive_hulls.cpp similarity index 100% rename from src/igl/copyleft/progressive_hulls.cpp rename to src/libigl/igl/copyleft/progressive_hulls.cpp diff --git a/src/igl/copyleft/progressive_hulls.h b/src/libigl/igl/copyleft/progressive_hulls.h similarity index 100% rename from src/igl/copyleft/progressive_hulls.h rename to src/libigl/igl/copyleft/progressive_hulls.h diff --git a/src/igl/copyleft/progressive_hulls_cost_and_placement.cpp b/src/libigl/igl/copyleft/progressive_hulls_cost_and_placement.cpp similarity index 100% rename from src/igl/copyleft/progressive_hulls_cost_and_placement.cpp rename to src/libigl/igl/copyleft/progressive_hulls_cost_and_placement.cpp diff --git a/src/igl/copyleft/progressive_hulls_cost_and_placement.h b/src/libigl/igl/copyleft/progressive_hulls_cost_and_placement.h similarity index 100% rename from src/igl/copyleft/progressive_hulls_cost_and_placement.h rename to src/libigl/igl/copyleft/progressive_hulls_cost_and_placement.h diff --git a/src/igl/copyleft/quadprog.cpp b/src/libigl/igl/copyleft/quadprog.cpp similarity index 100% rename from src/igl/copyleft/quadprog.cpp rename to src/libigl/igl/copyleft/quadprog.cpp diff --git a/src/igl/copyleft/quadprog.h b/src/libigl/igl/copyleft/quadprog.h similarity index 100% rename from src/igl/copyleft/quadprog.h rename to src/libigl/igl/copyleft/quadprog.h diff --git a/src/igl/copyleft/swept_volume.cpp b/src/libigl/igl/copyleft/swept_volume.cpp similarity index 100% rename from src/igl/copyleft/swept_volume.cpp rename to src/libigl/igl/copyleft/swept_volume.cpp diff --git a/src/igl/copyleft/swept_volume.h b/src/libigl/igl/copyleft/swept_volume.h similarity index 100% rename from src/igl/copyleft/swept_volume.h rename to src/libigl/igl/copyleft/swept_volume.h diff --git a/src/igl/copyleft/tetgen/README b/src/libigl/igl/copyleft/tetgen/README similarity index 100% rename from src/igl/copyleft/tetgen/README rename to src/libigl/igl/copyleft/tetgen/README diff --git a/src/igl/copyleft/tetgen/cdt.cpp b/src/libigl/igl/copyleft/tetgen/cdt.cpp similarity index 100% rename from src/igl/copyleft/tetgen/cdt.cpp rename to src/libigl/igl/copyleft/tetgen/cdt.cpp diff --git a/src/igl/copyleft/tetgen/cdt.h b/src/libigl/igl/copyleft/tetgen/cdt.h similarity index 100% rename from src/igl/copyleft/tetgen/cdt.h rename to src/libigl/igl/copyleft/tetgen/cdt.h diff --git a/src/igl/copyleft/tetgen/mesh_to_tetgenio.cpp b/src/libigl/igl/copyleft/tetgen/mesh_to_tetgenio.cpp similarity index 100% rename from src/igl/copyleft/tetgen/mesh_to_tetgenio.cpp rename to src/libigl/igl/copyleft/tetgen/mesh_to_tetgenio.cpp diff --git a/src/igl/copyleft/tetgen/mesh_to_tetgenio.h b/src/libigl/igl/copyleft/tetgen/mesh_to_tetgenio.h similarity index 100% rename from src/igl/copyleft/tetgen/mesh_to_tetgenio.h rename to src/libigl/igl/copyleft/tetgen/mesh_to_tetgenio.h diff --git a/src/igl/copyleft/tetgen/mesh_with_skeleton.cpp b/src/libigl/igl/copyleft/tetgen/mesh_with_skeleton.cpp similarity index 100% rename from src/igl/copyleft/tetgen/mesh_with_skeleton.cpp rename to src/libigl/igl/copyleft/tetgen/mesh_with_skeleton.cpp diff --git a/src/igl/copyleft/tetgen/mesh_with_skeleton.h b/src/libigl/igl/copyleft/tetgen/mesh_with_skeleton.h similarity index 100% rename from src/igl/copyleft/tetgen/mesh_with_skeleton.h rename to src/libigl/igl/copyleft/tetgen/mesh_with_skeleton.h diff --git a/src/igl/copyleft/tetgen/read_into_tetgenio.cpp b/src/libigl/igl/copyleft/tetgen/read_into_tetgenio.cpp similarity index 100% rename from src/igl/copyleft/tetgen/read_into_tetgenio.cpp rename to src/libigl/igl/copyleft/tetgen/read_into_tetgenio.cpp diff --git a/src/igl/copyleft/tetgen/read_into_tetgenio.h b/src/libigl/igl/copyleft/tetgen/read_into_tetgenio.h similarity index 100% rename from src/igl/copyleft/tetgen/read_into_tetgenio.h rename to src/libigl/igl/copyleft/tetgen/read_into_tetgenio.h diff --git a/src/igl/copyleft/tetgen/tetgenio_to_tetmesh.cpp b/src/libigl/igl/copyleft/tetgen/tetgenio_to_tetmesh.cpp similarity index 100% rename from src/igl/copyleft/tetgen/tetgenio_to_tetmesh.cpp rename to src/libigl/igl/copyleft/tetgen/tetgenio_to_tetmesh.cpp diff --git a/src/igl/copyleft/tetgen/tetgenio_to_tetmesh.h b/src/libigl/igl/copyleft/tetgen/tetgenio_to_tetmesh.h similarity index 100% rename from src/igl/copyleft/tetgen/tetgenio_to_tetmesh.h rename to src/libigl/igl/copyleft/tetgen/tetgenio_to_tetmesh.h diff --git a/src/igl/copyleft/tetgen/tetrahedralize.cpp b/src/libigl/igl/copyleft/tetgen/tetrahedralize.cpp similarity index 100% rename from src/igl/copyleft/tetgen/tetrahedralize.cpp rename to src/libigl/igl/copyleft/tetgen/tetrahedralize.cpp diff --git a/src/igl/copyleft/tetgen/tetrahedralize.h b/src/libigl/igl/copyleft/tetgen/tetrahedralize.h similarity index 100% rename from src/igl/copyleft/tetgen/tetrahedralize.h rename to src/libigl/igl/copyleft/tetgen/tetrahedralize.h diff --git a/src/igl/cotmatrix.cpp b/src/libigl/igl/cotmatrix.cpp similarity index 100% rename from src/igl/cotmatrix.cpp rename to src/libigl/igl/cotmatrix.cpp diff --git a/src/igl/cotmatrix.h b/src/libigl/igl/cotmatrix.h similarity index 100% rename from src/igl/cotmatrix.h rename to src/libigl/igl/cotmatrix.h diff --git a/src/igl/cotmatrix_entries.cpp b/src/libigl/igl/cotmatrix_entries.cpp similarity index 100% rename from src/igl/cotmatrix_entries.cpp rename to src/libigl/igl/cotmatrix_entries.cpp diff --git a/src/igl/cotmatrix_entries.h b/src/libigl/igl/cotmatrix_entries.h similarity index 100% rename from src/igl/cotmatrix_entries.h rename to src/libigl/igl/cotmatrix_entries.h diff --git a/src/igl/count.cpp b/src/libigl/igl/count.cpp similarity index 100% rename from src/igl/count.cpp rename to src/libigl/igl/count.cpp diff --git a/src/igl/count.h b/src/libigl/igl/count.h similarity index 100% rename from src/igl/count.h rename to src/libigl/igl/count.h diff --git a/src/igl/covariance_scatter_matrix.cpp b/src/libigl/igl/covariance_scatter_matrix.cpp similarity index 100% rename from src/igl/covariance_scatter_matrix.cpp rename to src/libigl/igl/covariance_scatter_matrix.cpp diff --git a/src/igl/covariance_scatter_matrix.h b/src/libigl/igl/covariance_scatter_matrix.h similarity index 100% rename from src/igl/covariance_scatter_matrix.h rename to src/libigl/igl/covariance_scatter_matrix.h diff --git a/src/igl/cross.cpp b/src/libigl/igl/cross.cpp similarity index 100% rename from src/igl/cross.cpp rename to src/libigl/igl/cross.cpp diff --git a/src/igl/cross.h b/src/libigl/igl/cross.h similarity index 100% rename from src/igl/cross.h rename to src/libigl/igl/cross.h diff --git a/src/igl/cross_field_missmatch.cpp b/src/libigl/igl/cross_field_missmatch.cpp similarity index 100% rename from src/igl/cross_field_missmatch.cpp rename to src/libigl/igl/cross_field_missmatch.cpp diff --git a/src/igl/cross_field_missmatch.h b/src/libigl/igl/cross_field_missmatch.h similarity index 100% rename from src/igl/cross_field_missmatch.h rename to src/libigl/igl/cross_field_missmatch.h diff --git a/src/igl/crouzeix_raviart_cotmatrix.cpp b/src/libigl/igl/crouzeix_raviart_cotmatrix.cpp similarity index 100% rename from src/igl/crouzeix_raviart_cotmatrix.cpp rename to src/libigl/igl/crouzeix_raviart_cotmatrix.cpp diff --git a/src/igl/crouzeix_raviart_cotmatrix.h b/src/libigl/igl/crouzeix_raviart_cotmatrix.h similarity index 100% rename from src/igl/crouzeix_raviart_cotmatrix.h rename to src/libigl/igl/crouzeix_raviart_cotmatrix.h diff --git a/src/igl/crouzeix_raviart_massmatrix.cpp b/src/libigl/igl/crouzeix_raviart_massmatrix.cpp similarity index 100% rename from src/igl/crouzeix_raviart_massmatrix.cpp rename to src/libigl/igl/crouzeix_raviart_massmatrix.cpp diff --git a/src/igl/crouzeix_raviart_massmatrix.h b/src/libigl/igl/crouzeix_raviart_massmatrix.h similarity index 100% rename from src/igl/crouzeix_raviart_massmatrix.h rename to src/libigl/igl/crouzeix_raviart_massmatrix.h diff --git a/src/igl/cumsum.cpp b/src/libigl/igl/cumsum.cpp similarity index 100% rename from src/igl/cumsum.cpp rename to src/libigl/igl/cumsum.cpp diff --git a/src/igl/cumsum.h b/src/libigl/igl/cumsum.h similarity index 100% rename from src/igl/cumsum.h rename to src/libigl/igl/cumsum.h diff --git a/src/igl/cut_mesh.cpp b/src/libigl/igl/cut_mesh.cpp similarity index 100% rename from src/igl/cut_mesh.cpp rename to src/libigl/igl/cut_mesh.cpp diff --git a/src/igl/cut_mesh.h b/src/libigl/igl/cut_mesh.h similarity index 100% rename from src/igl/cut_mesh.h rename to src/libigl/igl/cut_mesh.h diff --git a/src/igl/cut_mesh_from_singularities.cpp b/src/libigl/igl/cut_mesh_from_singularities.cpp similarity index 100% rename from src/igl/cut_mesh_from_singularities.cpp rename to src/libigl/igl/cut_mesh_from_singularities.cpp diff --git a/src/igl/cut_mesh_from_singularities.h b/src/libigl/igl/cut_mesh_from_singularities.h similarity index 100% rename from src/igl/cut_mesh_from_singularities.h rename to src/libigl/igl/cut_mesh_from_singularities.h diff --git a/src/igl/cylinder.cpp b/src/libigl/igl/cylinder.cpp similarity index 100% rename from src/igl/cylinder.cpp rename to src/libigl/igl/cylinder.cpp diff --git a/src/igl/cylinder.h b/src/libigl/igl/cylinder.h similarity index 100% rename from src/igl/cylinder.h rename to src/libigl/igl/cylinder.h diff --git a/src/igl/dated_copy.cpp b/src/libigl/igl/dated_copy.cpp similarity index 100% rename from src/igl/dated_copy.cpp rename to src/libigl/igl/dated_copy.cpp diff --git a/src/igl/dated_copy.h b/src/libigl/igl/dated_copy.h similarity index 100% rename from src/igl/dated_copy.h rename to src/libigl/igl/dated_copy.h diff --git a/src/igl/decimate.cpp b/src/libigl/igl/decimate.cpp similarity index 100% rename from src/igl/decimate.cpp rename to src/libigl/igl/decimate.cpp diff --git a/src/igl/decimate.h b/src/libigl/igl/decimate.h similarity index 100% rename from src/igl/decimate.h rename to src/libigl/igl/decimate.h diff --git a/src/igl/deform_skeleton.cpp b/src/libigl/igl/deform_skeleton.cpp similarity index 100% rename from src/igl/deform_skeleton.cpp rename to src/libigl/igl/deform_skeleton.cpp diff --git a/src/igl/deform_skeleton.h b/src/libigl/igl/deform_skeleton.h similarity index 100% rename from src/igl/deform_skeleton.h rename to src/libigl/igl/deform_skeleton.h diff --git a/src/igl/delaunay_triangulation.cpp b/src/libigl/igl/delaunay_triangulation.cpp similarity index 100% rename from src/igl/delaunay_triangulation.cpp rename to src/libigl/igl/delaunay_triangulation.cpp diff --git a/src/igl/delaunay_triangulation.h b/src/libigl/igl/delaunay_triangulation.h similarity index 100% rename from src/igl/delaunay_triangulation.h rename to src/libigl/igl/delaunay_triangulation.h diff --git a/src/igl/deprecated.h b/src/libigl/igl/deprecated.h similarity index 100% rename from src/igl/deprecated.h rename to src/libigl/igl/deprecated.h diff --git a/src/igl/dfs.cpp b/src/libigl/igl/dfs.cpp similarity index 100% rename from src/igl/dfs.cpp rename to src/libigl/igl/dfs.cpp diff --git a/src/igl/dfs.h b/src/libigl/igl/dfs.h similarity index 100% rename from src/igl/dfs.h rename to src/libigl/igl/dfs.h diff --git a/src/igl/diag.cpp b/src/libigl/igl/diag.cpp similarity index 100% rename from src/igl/diag.cpp rename to src/libigl/igl/diag.cpp diff --git a/src/igl/diag.h b/src/libigl/igl/diag.h similarity index 100% rename from src/igl/diag.h rename to src/libigl/igl/diag.h diff --git a/src/igl/dihedral_angles.cpp b/src/libigl/igl/dihedral_angles.cpp similarity index 100% rename from src/igl/dihedral_angles.cpp rename to src/libigl/igl/dihedral_angles.cpp diff --git a/src/igl/dihedral_angles.h b/src/libigl/igl/dihedral_angles.h similarity index 100% rename from src/igl/dihedral_angles.h rename to src/libigl/igl/dihedral_angles.h diff --git a/src/igl/dijkstra.cpp b/src/libigl/igl/dijkstra.cpp similarity index 100% rename from src/igl/dijkstra.cpp rename to src/libigl/igl/dijkstra.cpp diff --git a/src/igl/dijkstra.h b/src/libigl/igl/dijkstra.h similarity index 100% rename from src/igl/dijkstra.h rename to src/libigl/igl/dijkstra.h diff --git a/src/igl/directed_edge_orientations.cpp b/src/libigl/igl/directed_edge_orientations.cpp similarity index 100% rename from src/igl/directed_edge_orientations.cpp rename to src/libigl/igl/directed_edge_orientations.cpp diff --git a/src/igl/directed_edge_orientations.h b/src/libigl/igl/directed_edge_orientations.h similarity index 100% rename from src/igl/directed_edge_orientations.h rename to src/libigl/igl/directed_edge_orientations.h diff --git a/src/igl/directed_edge_parents.cpp b/src/libigl/igl/directed_edge_parents.cpp similarity index 100% rename from src/igl/directed_edge_parents.cpp rename to src/libigl/igl/directed_edge_parents.cpp diff --git a/src/igl/directed_edge_parents.h b/src/libigl/igl/directed_edge_parents.h similarity index 100% rename from src/igl/directed_edge_parents.h rename to src/libigl/igl/directed_edge_parents.h diff --git a/src/igl/dirname.cpp b/src/libigl/igl/dirname.cpp similarity index 100% rename from src/igl/dirname.cpp rename to src/libigl/igl/dirname.cpp diff --git a/src/igl/dirname.h b/src/libigl/igl/dirname.h similarity index 100% rename from src/igl/dirname.h rename to src/libigl/igl/dirname.h diff --git a/src/igl/dot.cpp b/src/libigl/igl/dot.cpp similarity index 100% rename from src/igl/dot.cpp rename to src/libigl/igl/dot.cpp diff --git a/src/igl/dot.h b/src/libigl/igl/dot.h similarity index 100% rename from src/igl/dot.h rename to src/libigl/igl/dot.h diff --git a/src/igl/dot_row.cpp b/src/libigl/igl/dot_row.cpp similarity index 100% rename from src/igl/dot_row.cpp rename to src/libigl/igl/dot_row.cpp diff --git a/src/igl/dot_row.h b/src/libigl/igl/dot_row.h similarity index 100% rename from src/igl/dot_row.h rename to src/libigl/igl/dot_row.h diff --git a/src/igl/doublearea.cpp b/src/libigl/igl/doublearea.cpp similarity index 100% rename from src/igl/doublearea.cpp rename to src/libigl/igl/doublearea.cpp diff --git a/src/igl/doublearea.h b/src/libigl/igl/doublearea.h similarity index 100% rename from src/igl/doublearea.h rename to src/libigl/igl/doublearea.h diff --git a/src/igl/dqs.cpp b/src/libigl/igl/dqs.cpp similarity index 100% rename from src/igl/dqs.cpp rename to src/libigl/igl/dqs.cpp diff --git a/src/igl/dqs.h b/src/libigl/igl/dqs.h similarity index 100% rename from src/igl/dqs.h rename to src/libigl/igl/dqs.h diff --git a/src/igl/ears.cpp b/src/libigl/igl/ears.cpp similarity index 100% rename from src/igl/ears.cpp rename to src/libigl/igl/ears.cpp diff --git a/src/igl/ears.h b/src/libigl/igl/ears.h similarity index 100% rename from src/igl/ears.h rename to src/libigl/igl/ears.h diff --git a/src/igl/edge_collapse_is_valid.cpp b/src/libigl/igl/edge_collapse_is_valid.cpp similarity index 100% rename from src/igl/edge_collapse_is_valid.cpp rename to src/libigl/igl/edge_collapse_is_valid.cpp diff --git a/src/igl/edge_collapse_is_valid.h b/src/libigl/igl/edge_collapse_is_valid.h similarity index 100% rename from src/igl/edge_collapse_is_valid.h rename to src/libigl/igl/edge_collapse_is_valid.h diff --git a/src/igl/edge_flaps.cpp b/src/libigl/igl/edge_flaps.cpp similarity index 100% rename from src/igl/edge_flaps.cpp rename to src/libigl/igl/edge_flaps.cpp diff --git a/src/igl/edge_flaps.h b/src/libigl/igl/edge_flaps.h similarity index 100% rename from src/igl/edge_flaps.h rename to src/libigl/igl/edge_flaps.h diff --git a/src/igl/edge_lengths.cpp b/src/libigl/igl/edge_lengths.cpp similarity index 100% rename from src/igl/edge_lengths.cpp rename to src/libigl/igl/edge_lengths.cpp diff --git a/src/igl/edge_lengths.h b/src/libigl/igl/edge_lengths.h similarity index 100% rename from src/igl/edge_lengths.h rename to src/libigl/igl/edge_lengths.h diff --git a/src/igl/edge_topology.cpp b/src/libigl/igl/edge_topology.cpp similarity index 100% rename from src/igl/edge_topology.cpp rename to src/libigl/igl/edge_topology.cpp diff --git a/src/igl/edge_topology.h b/src/libigl/igl/edge_topology.h similarity index 100% rename from src/igl/edge_topology.h rename to src/libigl/igl/edge_topology.h diff --git a/src/igl/edges.cpp b/src/libigl/igl/edges.cpp similarity index 100% rename from src/igl/edges.cpp rename to src/libigl/igl/edges.cpp diff --git a/src/igl/edges.h b/src/libigl/igl/edges.h similarity index 100% rename from src/igl/edges.h rename to src/libigl/igl/edges.h diff --git a/src/igl/edges_to_path.cpp b/src/libigl/igl/edges_to_path.cpp similarity index 100% rename from src/igl/edges_to_path.cpp rename to src/libigl/igl/edges_to_path.cpp diff --git a/src/igl/edges_to_path.h b/src/libigl/igl/edges_to_path.h similarity index 100% rename from src/igl/edges_to_path.h rename to src/libigl/igl/edges_to_path.h diff --git a/src/igl/eigs.cpp b/src/libigl/igl/eigs.cpp similarity index 100% rename from src/igl/eigs.cpp rename to src/libigl/igl/eigs.cpp diff --git a/src/igl/eigs.h b/src/libigl/igl/eigs.h similarity index 100% rename from src/igl/eigs.h rename to src/libigl/igl/eigs.h diff --git a/src/igl/embree/EmbreeIntersector.h b/src/libigl/igl/embree/EmbreeIntersector.h similarity index 100% rename from src/igl/embree/EmbreeIntersector.h rename to src/libigl/igl/embree/EmbreeIntersector.h diff --git a/src/igl/embree/Embree_convenience.h b/src/libigl/igl/embree/Embree_convenience.h similarity index 100% rename from src/igl/embree/Embree_convenience.h rename to src/libigl/igl/embree/Embree_convenience.h diff --git a/src/igl/embree/ambient_occlusion.cpp b/src/libigl/igl/embree/ambient_occlusion.cpp similarity index 100% rename from src/igl/embree/ambient_occlusion.cpp rename to src/libigl/igl/embree/ambient_occlusion.cpp diff --git a/src/igl/embree/ambient_occlusion.h b/src/libigl/igl/embree/ambient_occlusion.h similarity index 100% rename from src/igl/embree/ambient_occlusion.h rename to src/libigl/igl/embree/ambient_occlusion.h diff --git a/src/igl/embree/bone_heat.cpp b/src/libigl/igl/embree/bone_heat.cpp similarity index 100% rename from src/igl/embree/bone_heat.cpp rename to src/libigl/igl/embree/bone_heat.cpp diff --git a/src/igl/embree/bone_heat.h b/src/libigl/igl/embree/bone_heat.h similarity index 100% rename from src/igl/embree/bone_heat.h rename to src/libigl/igl/embree/bone_heat.h diff --git a/src/igl/embree/bone_visible.cpp b/src/libigl/igl/embree/bone_visible.cpp similarity index 100% rename from src/igl/embree/bone_visible.cpp rename to src/libigl/igl/embree/bone_visible.cpp diff --git a/src/igl/embree/bone_visible.h b/src/libigl/igl/embree/bone_visible.h similarity index 100% rename from src/igl/embree/bone_visible.h rename to src/libigl/igl/embree/bone_visible.h diff --git a/src/igl/embree/embree2/rtcore.h b/src/libigl/igl/embree/embree2/rtcore.h similarity index 100% rename from src/igl/embree/embree2/rtcore.h rename to src/libigl/igl/embree/embree2/rtcore.h diff --git a/src/igl/embree/embree2/rtcore.isph b/src/libigl/igl/embree/embree2/rtcore.isph similarity index 100% rename from src/igl/embree/embree2/rtcore.isph rename to src/libigl/igl/embree/embree2/rtcore.isph diff --git a/src/igl/embree/embree2/rtcore_geometry.h b/src/libigl/igl/embree/embree2/rtcore_geometry.h similarity index 100% rename from src/igl/embree/embree2/rtcore_geometry.h rename to src/libigl/igl/embree/embree2/rtcore_geometry.h diff --git a/src/igl/embree/embree2/rtcore_geometry.isph b/src/libigl/igl/embree/embree2/rtcore_geometry.isph similarity index 100% rename from src/igl/embree/embree2/rtcore_geometry.isph rename to src/libigl/igl/embree/embree2/rtcore_geometry.isph diff --git a/src/igl/embree/embree2/rtcore_geometry_user.h b/src/libigl/igl/embree/embree2/rtcore_geometry_user.h similarity index 100% rename from src/igl/embree/embree2/rtcore_geometry_user.h rename to src/libigl/igl/embree/embree2/rtcore_geometry_user.h diff --git a/src/igl/embree/embree2/rtcore_geometry_user.isph b/src/libigl/igl/embree/embree2/rtcore_geometry_user.isph similarity index 100% rename from src/igl/embree/embree2/rtcore_geometry_user.isph rename to src/libigl/igl/embree/embree2/rtcore_geometry_user.isph diff --git a/src/igl/embree/embree2/rtcore_ray.h b/src/libigl/igl/embree/embree2/rtcore_ray.h similarity index 100% rename from src/igl/embree/embree2/rtcore_ray.h rename to src/libigl/igl/embree/embree2/rtcore_ray.h diff --git a/src/igl/embree/embree2/rtcore_ray.isph b/src/libigl/igl/embree/embree2/rtcore_ray.isph similarity index 100% rename from src/igl/embree/embree2/rtcore_ray.isph rename to src/libigl/igl/embree/embree2/rtcore_ray.isph diff --git a/src/igl/embree/embree2/rtcore_scene.h b/src/libigl/igl/embree/embree2/rtcore_scene.h similarity index 100% rename from src/igl/embree/embree2/rtcore_scene.h rename to src/libigl/igl/embree/embree2/rtcore_scene.h diff --git a/src/igl/embree/embree2/rtcore_scene.isph b/src/libigl/igl/embree/embree2/rtcore_scene.isph similarity index 100% rename from src/igl/embree/embree2/rtcore_scene.isph rename to src/libigl/igl/embree/embree2/rtcore_scene.isph diff --git a/src/igl/embree/line_mesh_intersection.cpp b/src/libigl/igl/embree/line_mesh_intersection.cpp similarity index 100% rename from src/igl/embree/line_mesh_intersection.cpp rename to src/libigl/igl/embree/line_mesh_intersection.cpp diff --git a/src/igl/embree/line_mesh_intersection.h b/src/libigl/igl/embree/line_mesh_intersection.h similarity index 100% rename from src/igl/embree/line_mesh_intersection.h rename to src/libigl/igl/embree/line_mesh_intersection.h diff --git a/src/igl/embree/reorient_facets_raycast.cpp b/src/libigl/igl/embree/reorient_facets_raycast.cpp similarity index 100% rename from src/igl/embree/reorient_facets_raycast.cpp rename to src/libigl/igl/embree/reorient_facets_raycast.cpp diff --git a/src/igl/embree/reorient_facets_raycast.h b/src/libigl/igl/embree/reorient_facets_raycast.h similarity index 100% rename from src/igl/embree/reorient_facets_raycast.h rename to src/libigl/igl/embree/reorient_facets_raycast.h diff --git a/src/igl/embree/shape_diameter_function.cpp b/src/libigl/igl/embree/shape_diameter_function.cpp similarity index 100% rename from src/igl/embree/shape_diameter_function.cpp rename to src/libigl/igl/embree/shape_diameter_function.cpp diff --git a/src/igl/embree/shape_diameter_function.h b/src/libigl/igl/embree/shape_diameter_function.h similarity index 100% rename from src/igl/embree/shape_diameter_function.h rename to src/libigl/igl/embree/shape_diameter_function.h diff --git a/src/igl/embree/unproject_in_mesh.cpp b/src/libigl/igl/embree/unproject_in_mesh.cpp similarity index 100% rename from src/igl/embree/unproject_in_mesh.cpp rename to src/libigl/igl/embree/unproject_in_mesh.cpp diff --git a/src/igl/embree/unproject_in_mesh.h b/src/libigl/igl/embree/unproject_in_mesh.h similarity index 100% rename from src/igl/embree/unproject_in_mesh.h rename to src/libigl/igl/embree/unproject_in_mesh.h diff --git a/src/igl/embree/unproject_onto_mesh.cpp b/src/libigl/igl/embree/unproject_onto_mesh.cpp similarity index 100% rename from src/igl/embree/unproject_onto_mesh.cpp rename to src/libigl/igl/embree/unproject_onto_mesh.cpp diff --git a/src/igl/embree/unproject_onto_mesh.h b/src/libigl/igl/embree/unproject_onto_mesh.h similarity index 100% rename from src/igl/embree/unproject_onto_mesh.h rename to src/libigl/igl/embree/unproject_onto_mesh.h diff --git a/src/igl/euler_characteristic.cpp b/src/libigl/igl/euler_characteristic.cpp similarity index 100% rename from src/igl/euler_characteristic.cpp rename to src/libigl/igl/euler_characteristic.cpp diff --git a/src/igl/euler_characteristic.h b/src/libigl/igl/euler_characteristic.h similarity index 100% rename from src/igl/euler_characteristic.h rename to src/libigl/igl/euler_characteristic.h diff --git a/src/igl/exact_geodesic.cpp b/src/libigl/igl/exact_geodesic.cpp similarity index 100% rename from src/igl/exact_geodesic.cpp rename to src/libigl/igl/exact_geodesic.cpp diff --git a/src/igl/exact_geodesic.h b/src/libigl/igl/exact_geodesic.h similarity index 100% rename from src/igl/exact_geodesic.h rename to src/libigl/igl/exact_geodesic.h diff --git a/src/igl/example_fun.cpp b/src/libigl/igl/example_fun.cpp similarity index 100% rename from src/igl/example_fun.cpp rename to src/libigl/igl/example_fun.cpp diff --git a/src/igl/example_fun.h b/src/libigl/igl/example_fun.h similarity index 100% rename from src/igl/example_fun.h rename to src/libigl/igl/example_fun.h diff --git a/src/igl/exterior_edges.cpp b/src/libigl/igl/exterior_edges.cpp similarity index 100% rename from src/igl/exterior_edges.cpp rename to src/libigl/igl/exterior_edges.cpp diff --git a/src/igl/exterior_edges.h b/src/libigl/igl/exterior_edges.h similarity index 100% rename from src/igl/exterior_edges.h rename to src/libigl/igl/exterior_edges.h diff --git a/src/igl/extract_manifold_patches.cpp b/src/libigl/igl/extract_manifold_patches.cpp similarity index 100% rename from src/igl/extract_manifold_patches.cpp rename to src/libigl/igl/extract_manifold_patches.cpp diff --git a/src/igl/extract_manifold_patches.h b/src/libigl/igl/extract_manifold_patches.h similarity index 100% rename from src/igl/extract_manifold_patches.h rename to src/libigl/igl/extract_manifold_patches.h diff --git a/src/igl/extract_non_manifold_edge_curves.cpp b/src/libigl/igl/extract_non_manifold_edge_curves.cpp similarity index 100% rename from src/igl/extract_non_manifold_edge_curves.cpp rename to src/libigl/igl/extract_non_manifold_edge_curves.cpp diff --git a/src/igl/extract_non_manifold_edge_curves.h b/src/libigl/igl/extract_non_manifold_edge_curves.h similarity index 100% rename from src/igl/extract_non_manifold_edge_curves.h rename to src/libigl/igl/extract_non_manifold_edge_curves.h diff --git a/src/igl/face_areas.cpp b/src/libigl/igl/face_areas.cpp similarity index 100% rename from src/igl/face_areas.cpp rename to src/libigl/igl/face_areas.cpp diff --git a/src/igl/face_areas.h b/src/libigl/igl/face_areas.h similarity index 100% rename from src/igl/face_areas.h rename to src/libigl/igl/face_areas.h diff --git a/src/igl/face_occurrences.cpp b/src/libigl/igl/face_occurrences.cpp similarity index 100% rename from src/igl/face_occurrences.cpp rename to src/libigl/igl/face_occurrences.cpp diff --git a/src/igl/face_occurrences.h b/src/libigl/igl/face_occurrences.h similarity index 100% rename from src/igl/face_occurrences.h rename to src/libigl/igl/face_occurrences.h diff --git a/src/igl/faces_first.cpp b/src/libigl/igl/faces_first.cpp similarity index 100% rename from src/igl/faces_first.cpp rename to src/libigl/igl/faces_first.cpp diff --git a/src/igl/faces_first.h b/src/libigl/igl/faces_first.h similarity index 100% rename from src/igl/faces_first.h rename to src/libigl/igl/faces_first.h diff --git a/src/igl/facet_components.cpp b/src/libigl/igl/facet_components.cpp similarity index 100% rename from src/igl/facet_components.cpp rename to src/libigl/igl/facet_components.cpp diff --git a/src/igl/facet_components.h b/src/libigl/igl/facet_components.h similarity index 100% rename from src/igl/facet_components.h rename to src/libigl/igl/facet_components.h diff --git a/src/igl/false_barycentric_subdivision.cpp b/src/libigl/igl/false_barycentric_subdivision.cpp similarity index 100% rename from src/igl/false_barycentric_subdivision.cpp rename to src/libigl/igl/false_barycentric_subdivision.cpp diff --git a/src/igl/false_barycentric_subdivision.h b/src/libigl/igl/false_barycentric_subdivision.h similarity index 100% rename from src/igl/false_barycentric_subdivision.h rename to src/libigl/igl/false_barycentric_subdivision.h diff --git a/src/igl/fast_winding_number.cpp b/src/libigl/igl/fast_winding_number.cpp similarity index 100% rename from src/igl/fast_winding_number.cpp rename to src/libigl/igl/fast_winding_number.cpp diff --git a/src/igl/fast_winding_number.h b/src/libigl/igl/fast_winding_number.h similarity index 100% rename from src/igl/fast_winding_number.h rename to src/libigl/igl/fast_winding_number.h diff --git a/src/igl/file_contents_as_string.cpp b/src/libigl/igl/file_contents_as_string.cpp similarity index 100% rename from src/igl/file_contents_as_string.cpp rename to src/libigl/igl/file_contents_as_string.cpp diff --git a/src/igl/file_contents_as_string.h b/src/libigl/igl/file_contents_as_string.h similarity index 100% rename from src/igl/file_contents_as_string.h rename to src/libigl/igl/file_contents_as_string.h diff --git a/src/igl/file_dialog_open.cpp b/src/libigl/igl/file_dialog_open.cpp similarity index 100% rename from src/igl/file_dialog_open.cpp rename to src/libigl/igl/file_dialog_open.cpp diff --git a/src/igl/file_dialog_open.h b/src/libigl/igl/file_dialog_open.h similarity index 100% rename from src/igl/file_dialog_open.h rename to src/libigl/igl/file_dialog_open.h diff --git a/src/igl/file_dialog_save.cpp b/src/libigl/igl/file_dialog_save.cpp similarity index 100% rename from src/igl/file_dialog_save.cpp rename to src/libigl/igl/file_dialog_save.cpp diff --git a/src/igl/file_dialog_save.h b/src/libigl/igl/file_dialog_save.h similarity index 100% rename from src/igl/file_dialog_save.h rename to src/libigl/igl/file_dialog_save.h diff --git a/src/igl/file_exists.cpp b/src/libigl/igl/file_exists.cpp similarity index 100% rename from src/igl/file_exists.cpp rename to src/libigl/igl/file_exists.cpp diff --git a/src/igl/file_exists.h b/src/libigl/igl/file_exists.h similarity index 100% rename from src/igl/file_exists.h rename to src/libigl/igl/file_exists.h diff --git a/src/igl/find.cpp b/src/libigl/igl/find.cpp similarity index 100% rename from src/igl/find.cpp rename to src/libigl/igl/find.cpp diff --git a/src/igl/find.h b/src/libigl/igl/find.h similarity index 100% rename from src/igl/find.h rename to src/libigl/igl/find.h diff --git a/src/igl/find_cross_field_singularities.cpp b/src/libigl/igl/find_cross_field_singularities.cpp similarity index 100% rename from src/igl/find_cross_field_singularities.cpp rename to src/libigl/igl/find_cross_field_singularities.cpp diff --git a/src/igl/find_cross_field_singularities.h b/src/libigl/igl/find_cross_field_singularities.h similarity index 100% rename from src/igl/find_cross_field_singularities.h rename to src/libigl/igl/find_cross_field_singularities.h diff --git a/src/igl/find_zero.cpp b/src/libigl/igl/find_zero.cpp similarity index 100% rename from src/igl/find_zero.cpp rename to src/libigl/igl/find_zero.cpp diff --git a/src/igl/find_zero.h b/src/libigl/igl/find_zero.h similarity index 100% rename from src/igl/find_zero.h rename to src/libigl/igl/find_zero.h diff --git a/src/igl/fit_plane.cpp b/src/libigl/igl/fit_plane.cpp similarity index 100% rename from src/igl/fit_plane.cpp rename to src/libigl/igl/fit_plane.cpp diff --git a/src/igl/fit_plane.h b/src/libigl/igl/fit_plane.h similarity index 100% rename from src/igl/fit_plane.h rename to src/libigl/igl/fit_plane.h diff --git a/src/igl/fit_rotations.cpp b/src/libigl/igl/fit_rotations.cpp similarity index 100% rename from src/igl/fit_rotations.cpp rename to src/libigl/igl/fit_rotations.cpp diff --git a/src/igl/fit_rotations.h b/src/libigl/igl/fit_rotations.h similarity index 100% rename from src/igl/fit_rotations.h rename to src/libigl/igl/fit_rotations.h diff --git a/src/igl/flip_avoiding_line_search.cpp b/src/libigl/igl/flip_avoiding_line_search.cpp similarity index 100% rename from src/igl/flip_avoiding_line_search.cpp rename to src/libigl/igl/flip_avoiding_line_search.cpp diff --git a/src/igl/flip_avoiding_line_search.h b/src/libigl/igl/flip_avoiding_line_search.h similarity index 100% rename from src/igl/flip_avoiding_line_search.h rename to src/libigl/igl/flip_avoiding_line_search.h diff --git a/src/igl/flip_edge.cpp b/src/libigl/igl/flip_edge.cpp similarity index 100% rename from src/igl/flip_edge.cpp rename to src/libigl/igl/flip_edge.cpp diff --git a/src/igl/flip_edge.h b/src/libigl/igl/flip_edge.h similarity index 100% rename from src/igl/flip_edge.h rename to src/libigl/igl/flip_edge.h diff --git a/src/igl/flipped_triangles.cpp b/src/libigl/igl/flipped_triangles.cpp similarity index 100% rename from src/igl/flipped_triangles.cpp rename to src/libigl/igl/flipped_triangles.cpp diff --git a/src/igl/flipped_triangles.h b/src/libigl/igl/flipped_triangles.h similarity index 100% rename from src/igl/flipped_triangles.h rename to src/libigl/igl/flipped_triangles.h diff --git a/src/igl/flood_fill.cpp b/src/libigl/igl/flood_fill.cpp similarity index 100% rename from src/igl/flood_fill.cpp rename to src/libigl/igl/flood_fill.cpp diff --git a/src/igl/flood_fill.h b/src/libigl/igl/flood_fill.h similarity index 100% rename from src/igl/flood_fill.h rename to src/libigl/igl/flood_fill.h diff --git a/src/igl/floor.cpp b/src/libigl/igl/floor.cpp similarity index 100% rename from src/igl/floor.cpp rename to src/libigl/igl/floor.cpp diff --git a/src/igl/floor.h b/src/libigl/igl/floor.h similarity index 100% rename from src/igl/floor.h rename to src/libigl/igl/floor.h diff --git a/src/igl/for_each.h b/src/libigl/igl/for_each.h similarity index 100% rename from src/igl/for_each.h rename to src/libigl/igl/for_each.h diff --git a/src/igl/forward_kinematics.cpp b/src/libigl/igl/forward_kinematics.cpp similarity index 100% rename from src/igl/forward_kinematics.cpp rename to src/libigl/igl/forward_kinematics.cpp diff --git a/src/igl/forward_kinematics.h b/src/libigl/igl/forward_kinematics.h similarity index 100% rename from src/igl/forward_kinematics.h rename to src/libigl/igl/forward_kinematics.h diff --git a/src/igl/frame_field_deformer.cpp b/src/libigl/igl/frame_field_deformer.cpp similarity index 100% rename from src/igl/frame_field_deformer.cpp rename to src/libigl/igl/frame_field_deformer.cpp diff --git a/src/igl/frame_field_deformer.h b/src/libigl/igl/frame_field_deformer.h similarity index 100% rename from src/igl/frame_field_deformer.h rename to src/libigl/igl/frame_field_deformer.h diff --git a/src/igl/frame_to_cross_field.cpp b/src/libigl/igl/frame_to_cross_field.cpp similarity index 100% rename from src/igl/frame_to_cross_field.cpp rename to src/libigl/igl/frame_to_cross_field.cpp diff --git a/src/igl/frame_to_cross_field.h b/src/libigl/igl/frame_to_cross_field.h similarity index 100% rename from src/igl/frame_to_cross_field.h rename to src/libigl/igl/frame_to_cross_field.h diff --git a/src/igl/frustum.cpp b/src/libigl/igl/frustum.cpp similarity index 100% rename from src/igl/frustum.cpp rename to src/libigl/igl/frustum.cpp diff --git a/src/igl/frustum.h b/src/libigl/igl/frustum.h similarity index 100% rename from src/igl/frustum.h rename to src/libigl/igl/frustum.h diff --git a/src/igl/gaussian_curvature.cpp b/src/libigl/igl/gaussian_curvature.cpp similarity index 100% rename from src/igl/gaussian_curvature.cpp rename to src/libigl/igl/gaussian_curvature.cpp diff --git a/src/igl/gaussian_curvature.h b/src/libigl/igl/gaussian_curvature.h similarity index 100% rename from src/igl/gaussian_curvature.h rename to src/libigl/igl/gaussian_curvature.h diff --git a/src/igl/get_seconds.cpp b/src/libigl/igl/get_seconds.cpp similarity index 100% rename from src/igl/get_seconds.cpp rename to src/libigl/igl/get_seconds.cpp diff --git a/src/igl/get_seconds.h b/src/libigl/igl/get_seconds.h similarity index 100% rename from src/igl/get_seconds.h rename to src/libigl/igl/get_seconds.h diff --git a/src/igl/get_seconds_hires.cpp b/src/libigl/igl/get_seconds_hires.cpp similarity index 100% rename from src/igl/get_seconds_hires.cpp rename to src/libigl/igl/get_seconds_hires.cpp diff --git a/src/igl/get_seconds_hires.h b/src/libigl/igl/get_seconds_hires.h similarity index 100% rename from src/igl/get_seconds_hires.h rename to src/libigl/igl/get_seconds_hires.h diff --git a/src/igl/grad.cpp b/src/libigl/igl/grad.cpp similarity index 100% rename from src/igl/grad.cpp rename to src/libigl/igl/grad.cpp diff --git a/src/igl/grad.h b/src/libigl/igl/grad.h similarity index 100% rename from src/igl/grad.h rename to src/libigl/igl/grad.h diff --git a/src/igl/grid.cpp b/src/libigl/igl/grid.cpp similarity index 100% rename from src/igl/grid.cpp rename to src/libigl/igl/grid.cpp diff --git a/src/igl/grid.h b/src/libigl/igl/grid.h similarity index 100% rename from src/igl/grid.h rename to src/libigl/igl/grid.h diff --git a/src/igl/grid_search.cpp b/src/libigl/igl/grid_search.cpp similarity index 100% rename from src/igl/grid_search.cpp rename to src/libigl/igl/grid_search.cpp diff --git a/src/igl/grid_search.h b/src/libigl/igl/grid_search.h similarity index 100% rename from src/igl/grid_search.h rename to src/libigl/igl/grid_search.h diff --git a/src/igl/group_sum_matrix.cpp b/src/libigl/igl/group_sum_matrix.cpp similarity index 100% rename from src/igl/group_sum_matrix.cpp rename to src/libigl/igl/group_sum_matrix.cpp diff --git a/src/igl/group_sum_matrix.h b/src/libigl/igl/group_sum_matrix.h similarity index 100% rename from src/igl/group_sum_matrix.h rename to src/libigl/igl/group_sum_matrix.h diff --git a/src/igl/guess_extension.cpp b/src/libigl/igl/guess_extension.cpp similarity index 100% rename from src/igl/guess_extension.cpp rename to src/libigl/igl/guess_extension.cpp diff --git a/src/igl/guess_extension.h b/src/libigl/igl/guess_extension.h similarity index 100% rename from src/igl/guess_extension.h rename to src/libigl/igl/guess_extension.h diff --git a/src/igl/harmonic.cpp b/src/libigl/igl/harmonic.cpp similarity index 100% rename from src/igl/harmonic.cpp rename to src/libigl/igl/harmonic.cpp diff --git a/src/igl/harmonic.h b/src/libigl/igl/harmonic.h similarity index 100% rename from src/igl/harmonic.h rename to src/libigl/igl/harmonic.h diff --git a/src/igl/harwell_boeing.cpp b/src/libigl/igl/harwell_boeing.cpp similarity index 100% rename from src/igl/harwell_boeing.cpp rename to src/libigl/igl/harwell_boeing.cpp diff --git a/src/igl/harwell_boeing.h b/src/libigl/igl/harwell_boeing.h similarity index 100% rename from src/igl/harwell_boeing.h rename to src/libigl/igl/harwell_boeing.h diff --git a/src/igl/hausdorff.cpp b/src/libigl/igl/hausdorff.cpp similarity index 100% rename from src/igl/hausdorff.cpp rename to src/libigl/igl/hausdorff.cpp diff --git a/src/igl/hausdorff.h b/src/libigl/igl/hausdorff.h similarity index 100% rename from src/igl/hausdorff.h rename to src/libigl/igl/hausdorff.h diff --git a/src/igl/hessian.cpp b/src/libigl/igl/hessian.cpp similarity index 100% rename from src/igl/hessian.cpp rename to src/libigl/igl/hessian.cpp diff --git a/src/igl/hessian.h b/src/libigl/igl/hessian.h similarity index 100% rename from src/igl/hessian.h rename to src/libigl/igl/hessian.h diff --git a/src/igl/hessian_energy.cpp b/src/libigl/igl/hessian_energy.cpp similarity index 100% rename from src/igl/hessian_energy.cpp rename to src/libigl/igl/hessian_energy.cpp diff --git a/src/igl/hessian_energy.h b/src/libigl/igl/hessian_energy.h similarity index 100% rename from src/igl/hessian_energy.h rename to src/libigl/igl/hessian_energy.h diff --git a/src/igl/histc.cpp b/src/libigl/igl/histc.cpp similarity index 100% rename from src/igl/histc.cpp rename to src/libigl/igl/histc.cpp diff --git a/src/igl/histc.h b/src/libigl/igl/histc.h similarity index 100% rename from src/igl/histc.h rename to src/libigl/igl/histc.h diff --git a/src/igl/hsv_to_rgb.cpp b/src/libigl/igl/hsv_to_rgb.cpp similarity index 100% rename from src/igl/hsv_to_rgb.cpp rename to src/libigl/igl/hsv_to_rgb.cpp diff --git a/src/igl/hsv_to_rgb.h b/src/libigl/igl/hsv_to_rgb.h similarity index 100% rename from src/igl/hsv_to_rgb.h rename to src/libigl/igl/hsv_to_rgb.h diff --git a/src/igl/igl_inline.h b/src/libigl/igl/igl_inline.h similarity index 100% rename from src/igl/igl_inline.h rename to src/libigl/igl/igl_inline.h diff --git a/src/igl/in_element.cpp b/src/libigl/igl/in_element.cpp similarity index 100% rename from src/igl/in_element.cpp rename to src/libigl/igl/in_element.cpp diff --git a/src/igl/in_element.h b/src/libigl/igl/in_element.h similarity index 100% rename from src/igl/in_element.h rename to src/libigl/igl/in_element.h diff --git a/src/igl/infinite_cost_stopping_condition.cpp b/src/libigl/igl/infinite_cost_stopping_condition.cpp similarity index 100% rename from src/igl/infinite_cost_stopping_condition.cpp rename to src/libigl/igl/infinite_cost_stopping_condition.cpp diff --git a/src/igl/infinite_cost_stopping_condition.h b/src/libigl/igl/infinite_cost_stopping_condition.h similarity index 100% rename from src/igl/infinite_cost_stopping_condition.h rename to src/libigl/igl/infinite_cost_stopping_condition.h diff --git a/src/igl/inradius.cpp b/src/libigl/igl/inradius.cpp similarity index 100% rename from src/igl/inradius.cpp rename to src/libigl/igl/inradius.cpp diff --git a/src/igl/inradius.h b/src/libigl/igl/inradius.h similarity index 100% rename from src/igl/inradius.h rename to src/libigl/igl/inradius.h diff --git a/src/igl/internal_angles.cpp b/src/libigl/igl/internal_angles.cpp similarity index 100% rename from src/igl/internal_angles.cpp rename to src/libigl/igl/internal_angles.cpp diff --git a/src/igl/internal_angles.h b/src/libigl/igl/internal_angles.h similarity index 100% rename from src/igl/internal_angles.h rename to src/libigl/igl/internal_angles.h diff --git a/src/igl/intersect.cpp b/src/libigl/igl/intersect.cpp similarity index 100% rename from src/igl/intersect.cpp rename to src/libigl/igl/intersect.cpp diff --git a/src/igl/intersect.h b/src/libigl/igl/intersect.h similarity index 100% rename from src/igl/intersect.h rename to src/libigl/igl/intersect.h diff --git a/src/igl/invert_diag.cpp b/src/libigl/igl/invert_diag.cpp similarity index 100% rename from src/igl/invert_diag.cpp rename to src/libigl/igl/invert_diag.cpp diff --git a/src/igl/invert_diag.h b/src/libigl/igl/invert_diag.h similarity index 100% rename from src/igl/invert_diag.h rename to src/libigl/igl/invert_diag.h diff --git a/src/igl/is_border_vertex.cpp b/src/libigl/igl/is_border_vertex.cpp similarity index 100% rename from src/igl/is_border_vertex.cpp rename to src/libigl/igl/is_border_vertex.cpp diff --git a/src/igl/is_border_vertex.h b/src/libigl/igl/is_border_vertex.h similarity index 100% rename from src/igl/is_border_vertex.h rename to src/libigl/igl/is_border_vertex.h diff --git a/src/igl/is_boundary_edge.cpp b/src/libigl/igl/is_boundary_edge.cpp similarity index 100% rename from src/igl/is_boundary_edge.cpp rename to src/libigl/igl/is_boundary_edge.cpp diff --git a/src/igl/is_boundary_edge.h b/src/libigl/igl/is_boundary_edge.h similarity index 100% rename from src/igl/is_boundary_edge.h rename to src/libigl/igl/is_boundary_edge.h diff --git a/src/igl/is_dir.cpp b/src/libigl/igl/is_dir.cpp similarity index 100% rename from src/igl/is_dir.cpp rename to src/libigl/igl/is_dir.cpp diff --git a/src/igl/is_dir.h b/src/libigl/igl/is_dir.h similarity index 100% rename from src/igl/is_dir.h rename to src/libigl/igl/is_dir.h diff --git a/src/igl/is_edge_manifold.cpp b/src/libigl/igl/is_edge_manifold.cpp similarity index 100% rename from src/igl/is_edge_manifold.cpp rename to src/libigl/igl/is_edge_manifold.cpp diff --git a/src/igl/is_edge_manifold.h b/src/libigl/igl/is_edge_manifold.h similarity index 100% rename from src/igl/is_edge_manifold.h rename to src/libigl/igl/is_edge_manifold.h diff --git a/src/igl/is_file.cpp b/src/libigl/igl/is_file.cpp similarity index 100% rename from src/igl/is_file.cpp rename to src/libigl/igl/is_file.cpp diff --git a/src/igl/is_file.h b/src/libigl/igl/is_file.h similarity index 100% rename from src/igl/is_file.h rename to src/libigl/igl/is_file.h diff --git a/src/igl/is_irregular_vertex.cpp b/src/libigl/igl/is_irregular_vertex.cpp similarity index 100% rename from src/igl/is_irregular_vertex.cpp rename to src/libigl/igl/is_irregular_vertex.cpp diff --git a/src/igl/is_irregular_vertex.h b/src/libigl/igl/is_irregular_vertex.h similarity index 100% rename from src/igl/is_irregular_vertex.h rename to src/libigl/igl/is_irregular_vertex.h diff --git a/src/igl/is_planar.cpp b/src/libigl/igl/is_planar.cpp similarity index 100% rename from src/igl/is_planar.cpp rename to src/libigl/igl/is_planar.cpp diff --git a/src/igl/is_planar.h b/src/libigl/igl/is_planar.h similarity index 100% rename from src/igl/is_planar.h rename to src/libigl/igl/is_planar.h diff --git a/src/igl/is_readable.cpp b/src/libigl/igl/is_readable.cpp similarity index 100% rename from src/igl/is_readable.cpp rename to src/libigl/igl/is_readable.cpp diff --git a/src/igl/is_readable.h b/src/libigl/igl/is_readable.h similarity index 100% rename from src/igl/is_readable.h rename to src/libigl/igl/is_readable.h diff --git a/src/igl/is_sparse.cpp b/src/libigl/igl/is_sparse.cpp similarity index 100% rename from src/igl/is_sparse.cpp rename to src/libigl/igl/is_sparse.cpp diff --git a/src/igl/is_sparse.h b/src/libigl/igl/is_sparse.h similarity index 100% rename from src/igl/is_sparse.h rename to src/libigl/igl/is_sparse.h diff --git a/src/igl/is_stl.cpp b/src/libigl/igl/is_stl.cpp similarity index 100% rename from src/igl/is_stl.cpp rename to src/libigl/igl/is_stl.cpp diff --git a/src/igl/is_stl.h b/src/libigl/igl/is_stl.h similarity index 100% rename from src/igl/is_stl.h rename to src/libigl/igl/is_stl.h diff --git a/src/igl/is_symmetric.cpp b/src/libigl/igl/is_symmetric.cpp similarity index 100% rename from src/igl/is_symmetric.cpp rename to src/libigl/igl/is_symmetric.cpp diff --git a/src/igl/is_symmetric.h b/src/libigl/igl/is_symmetric.h similarity index 100% rename from src/igl/is_symmetric.h rename to src/libigl/igl/is_symmetric.h diff --git a/src/igl/is_vertex_manifold.cpp b/src/libigl/igl/is_vertex_manifold.cpp similarity index 100% rename from src/igl/is_vertex_manifold.cpp rename to src/libigl/igl/is_vertex_manifold.cpp diff --git a/src/igl/is_vertex_manifold.h b/src/libigl/igl/is_vertex_manifold.h similarity index 100% rename from src/igl/is_vertex_manifold.h rename to src/libigl/igl/is_vertex_manifold.h diff --git a/src/igl/is_writable.cpp b/src/libigl/igl/is_writable.cpp similarity index 100% rename from src/igl/is_writable.cpp rename to src/libigl/igl/is_writable.cpp diff --git a/src/igl/is_writable.h b/src/libigl/igl/is_writable.h similarity index 100% rename from src/igl/is_writable.h rename to src/libigl/igl/is_writable.h diff --git a/src/igl/isdiag.cpp b/src/libigl/igl/isdiag.cpp similarity index 100% rename from src/igl/isdiag.cpp rename to src/libigl/igl/isdiag.cpp diff --git a/src/igl/isdiag.h b/src/libigl/igl/isdiag.h similarity index 100% rename from src/igl/isdiag.h rename to src/libigl/igl/isdiag.h diff --git a/src/igl/ismember.cpp b/src/libigl/igl/ismember.cpp similarity index 100% rename from src/igl/ismember.cpp rename to src/libigl/igl/ismember.cpp diff --git a/src/igl/ismember.h b/src/libigl/igl/ismember.h similarity index 100% rename from src/igl/ismember.h rename to src/libigl/igl/ismember.h diff --git a/src/igl/isolines.cpp b/src/libigl/igl/isolines.cpp similarity index 100% rename from src/igl/isolines.cpp rename to src/libigl/igl/isolines.cpp diff --git a/src/igl/isolines.h b/src/libigl/igl/isolines.h similarity index 100% rename from src/igl/isolines.h rename to src/libigl/igl/isolines.h diff --git a/src/igl/jet.cpp b/src/libigl/igl/jet.cpp similarity index 100% rename from src/igl/jet.cpp rename to src/libigl/igl/jet.cpp diff --git a/src/igl/jet.h b/src/libigl/igl/jet.h similarity index 100% rename from src/igl/jet.h rename to src/libigl/igl/jet.h diff --git a/src/igl/knn.cpp b/src/libigl/igl/knn.cpp similarity index 100% rename from src/igl/knn.cpp rename to src/libigl/igl/knn.cpp diff --git a/src/igl/knn.h b/src/libigl/igl/knn.h similarity index 100% rename from src/igl/knn.h rename to src/libigl/igl/knn.h diff --git a/src/igl/launch_medit.cpp b/src/libigl/igl/launch_medit.cpp similarity index 100% rename from src/igl/launch_medit.cpp rename to src/libigl/igl/launch_medit.cpp diff --git a/src/igl/launch_medit.h b/src/libigl/igl/launch_medit.h similarity index 100% rename from src/igl/launch_medit.h rename to src/libigl/igl/launch_medit.h diff --git a/src/igl/lbs_matrix.cpp b/src/libigl/igl/lbs_matrix.cpp similarity index 100% rename from src/igl/lbs_matrix.cpp rename to src/libigl/igl/lbs_matrix.cpp diff --git a/src/igl/lbs_matrix.h b/src/libigl/igl/lbs_matrix.h similarity index 100% rename from src/igl/lbs_matrix.h rename to src/libigl/igl/lbs_matrix.h diff --git a/src/igl/lexicographic_triangulation.cpp b/src/libigl/igl/lexicographic_triangulation.cpp similarity index 100% rename from src/igl/lexicographic_triangulation.cpp rename to src/libigl/igl/lexicographic_triangulation.cpp diff --git a/src/igl/lexicographic_triangulation.h b/src/libigl/igl/lexicographic_triangulation.h similarity index 100% rename from src/igl/lexicographic_triangulation.h rename to src/libigl/igl/lexicographic_triangulation.h diff --git a/src/igl/lim/lim.cpp b/src/libigl/igl/lim/lim.cpp similarity index 100% rename from src/igl/lim/lim.cpp rename to src/libigl/igl/lim/lim.cpp diff --git a/src/igl/lim/lim.h b/src/libigl/igl/lim/lim.h similarity index 100% rename from src/igl/lim/lim.h rename to src/libigl/igl/lim/lim.h diff --git a/src/igl/limit_faces.cpp b/src/libigl/igl/limit_faces.cpp similarity index 100% rename from src/igl/limit_faces.cpp rename to src/libigl/igl/limit_faces.cpp diff --git a/src/igl/limit_faces.h b/src/libigl/igl/limit_faces.h similarity index 100% rename from src/igl/limit_faces.h rename to src/libigl/igl/limit_faces.h diff --git a/src/igl/line_field_missmatch.cpp b/src/libigl/igl/line_field_missmatch.cpp similarity index 100% rename from src/igl/line_field_missmatch.cpp rename to src/libigl/igl/line_field_missmatch.cpp diff --git a/src/igl/line_field_missmatch.h b/src/libigl/igl/line_field_missmatch.h similarity index 100% rename from src/igl/line_field_missmatch.h rename to src/libigl/igl/line_field_missmatch.h diff --git a/src/igl/line_search.cpp b/src/libigl/igl/line_search.cpp similarity index 100% rename from src/igl/line_search.cpp rename to src/libigl/igl/line_search.cpp diff --git a/src/igl/line_search.h b/src/libigl/igl/line_search.h similarity index 100% rename from src/igl/line_search.h rename to src/libigl/igl/line_search.h diff --git a/src/igl/line_segment_in_rectangle.cpp b/src/libigl/igl/line_segment_in_rectangle.cpp similarity index 100% rename from src/igl/line_segment_in_rectangle.cpp rename to src/libigl/igl/line_segment_in_rectangle.cpp diff --git a/src/igl/line_segment_in_rectangle.h b/src/libigl/igl/line_segment_in_rectangle.h similarity index 100% rename from src/igl/line_segment_in_rectangle.h rename to src/libigl/igl/line_segment_in_rectangle.h diff --git a/src/igl/linprog.cpp b/src/libigl/igl/linprog.cpp similarity index 100% rename from src/igl/linprog.cpp rename to src/libigl/igl/linprog.cpp diff --git a/src/igl/linprog.h b/src/libigl/igl/linprog.h similarity index 100% rename from src/igl/linprog.h rename to src/libigl/igl/linprog.h diff --git a/src/igl/list_to_matrix.cpp b/src/libigl/igl/list_to_matrix.cpp similarity index 100% rename from src/igl/list_to_matrix.cpp rename to src/libigl/igl/list_to_matrix.cpp diff --git a/src/igl/list_to_matrix.h b/src/libigl/igl/list_to_matrix.h similarity index 100% rename from src/igl/list_to_matrix.h rename to src/libigl/igl/list_to_matrix.h diff --git a/src/igl/local_basis.cpp b/src/libigl/igl/local_basis.cpp similarity index 100% rename from src/igl/local_basis.cpp rename to src/libigl/igl/local_basis.cpp diff --git a/src/igl/local_basis.h b/src/libigl/igl/local_basis.h similarity index 100% rename from src/igl/local_basis.h rename to src/libigl/igl/local_basis.h diff --git a/src/igl/look_at.cpp b/src/libigl/igl/look_at.cpp similarity index 100% rename from src/igl/look_at.cpp rename to src/libigl/igl/look_at.cpp diff --git a/src/igl/look_at.h b/src/libigl/igl/look_at.h similarity index 100% rename from src/igl/look_at.h rename to src/libigl/igl/look_at.h diff --git a/src/igl/loop.cpp b/src/libigl/igl/loop.cpp similarity index 100% rename from src/igl/loop.cpp rename to src/libigl/igl/loop.cpp diff --git a/src/igl/loop.h b/src/libigl/igl/loop.h similarity index 100% rename from src/igl/loop.h rename to src/libigl/igl/loop.h diff --git a/src/igl/lscm.cpp b/src/libigl/igl/lscm.cpp similarity index 100% rename from src/igl/lscm.cpp rename to src/libigl/igl/lscm.cpp diff --git a/src/igl/lscm.h b/src/libigl/igl/lscm.h similarity index 100% rename from src/igl/lscm.h rename to src/libigl/igl/lscm.h diff --git a/src/igl/map_vertices_to_circle.cpp b/src/libigl/igl/map_vertices_to_circle.cpp similarity index 100% rename from src/igl/map_vertices_to_circle.cpp rename to src/libigl/igl/map_vertices_to_circle.cpp diff --git a/src/igl/map_vertices_to_circle.h b/src/libigl/igl/map_vertices_to_circle.h similarity index 100% rename from src/igl/map_vertices_to_circle.h rename to src/libigl/igl/map_vertices_to_circle.h diff --git a/src/igl/massmatrix.cpp b/src/libigl/igl/massmatrix.cpp similarity index 100% rename from src/igl/massmatrix.cpp rename to src/libigl/igl/massmatrix.cpp diff --git a/src/igl/massmatrix.h b/src/libigl/igl/massmatrix.h similarity index 100% rename from src/igl/massmatrix.h rename to src/libigl/igl/massmatrix.h diff --git a/src/igl/mat_max.cpp b/src/libigl/igl/mat_max.cpp similarity index 100% rename from src/igl/mat_max.cpp rename to src/libigl/igl/mat_max.cpp diff --git a/src/igl/mat_max.h b/src/libigl/igl/mat_max.h similarity index 100% rename from src/igl/mat_max.h rename to src/libigl/igl/mat_max.h diff --git a/src/igl/mat_min.cpp b/src/libigl/igl/mat_min.cpp similarity index 100% rename from src/igl/mat_min.cpp rename to src/libigl/igl/mat_min.cpp diff --git a/src/igl/mat_min.h b/src/libigl/igl/mat_min.h similarity index 100% rename from src/igl/mat_min.h rename to src/libigl/igl/mat_min.h diff --git a/src/igl/mat_to_quat.cpp b/src/libigl/igl/mat_to_quat.cpp similarity index 100% rename from src/igl/mat_to_quat.cpp rename to src/libigl/igl/mat_to_quat.cpp diff --git a/src/igl/mat_to_quat.h b/src/libigl/igl/mat_to_quat.h similarity index 100% rename from src/igl/mat_to_quat.h rename to src/libigl/igl/mat_to_quat.h diff --git a/src/igl/material_colors.h b/src/libigl/igl/material_colors.h similarity index 100% rename from src/igl/material_colors.h rename to src/libigl/igl/material_colors.h diff --git a/src/igl/matlab/MatlabWorkspace.h b/src/libigl/igl/matlab/MatlabWorkspace.h similarity index 100% rename from src/igl/matlab/MatlabWorkspace.h rename to src/libigl/igl/matlab/MatlabWorkspace.h diff --git a/src/igl/matlab/MexStream.h b/src/libigl/igl/matlab/MexStream.h similarity index 100% rename from src/igl/matlab/MexStream.h rename to src/libigl/igl/matlab/MexStream.h diff --git a/src/igl/matlab/matlabinterface.cpp b/src/libigl/igl/matlab/matlabinterface.cpp similarity index 100% rename from src/igl/matlab/matlabinterface.cpp rename to src/libigl/igl/matlab/matlabinterface.cpp diff --git a/src/igl/matlab/matlabinterface.h b/src/libigl/igl/matlab/matlabinterface.h similarity index 100% rename from src/igl/matlab/matlabinterface.h rename to src/libigl/igl/matlab/matlabinterface.h diff --git a/src/igl/matlab/mexErrMsgTxt.cpp b/src/libigl/igl/matlab/mexErrMsgTxt.cpp similarity index 100% rename from src/igl/matlab/mexErrMsgTxt.cpp rename to src/libigl/igl/matlab/mexErrMsgTxt.cpp diff --git a/src/igl/matlab/mexErrMsgTxt.h b/src/libigl/igl/matlab/mexErrMsgTxt.h similarity index 100% rename from src/igl/matlab/mexErrMsgTxt.h rename to src/libigl/igl/matlab/mexErrMsgTxt.h diff --git a/src/igl/matlab/parse_rhs.cpp b/src/libigl/igl/matlab/parse_rhs.cpp similarity index 100% rename from src/igl/matlab/parse_rhs.cpp rename to src/libigl/igl/matlab/parse_rhs.cpp diff --git a/src/igl/matlab/parse_rhs.h b/src/libigl/igl/matlab/parse_rhs.h similarity index 100% rename from src/igl/matlab/parse_rhs.h rename to src/libigl/igl/matlab/parse_rhs.h diff --git a/src/igl/matlab/prepare_lhs.cpp b/src/libigl/igl/matlab/prepare_lhs.cpp similarity index 100% rename from src/igl/matlab/prepare_lhs.cpp rename to src/libigl/igl/matlab/prepare_lhs.cpp diff --git a/src/igl/matlab/prepare_lhs.h b/src/libigl/igl/matlab/prepare_lhs.h similarity index 100% rename from src/igl/matlab/prepare_lhs.h rename to src/libigl/igl/matlab/prepare_lhs.h diff --git a/src/igl/matlab/requires_arg.cpp b/src/libigl/igl/matlab/requires_arg.cpp similarity index 100% rename from src/igl/matlab/requires_arg.cpp rename to src/libigl/igl/matlab/requires_arg.cpp diff --git a/src/igl/matlab/requires_arg.h b/src/libigl/igl/matlab/requires_arg.h similarity index 100% rename from src/igl/matlab/requires_arg.h rename to src/libigl/igl/matlab/requires_arg.h diff --git a/src/igl/matlab/validate_arg.cpp b/src/libigl/igl/matlab/validate_arg.cpp similarity index 100% rename from src/igl/matlab/validate_arg.cpp rename to src/libigl/igl/matlab/validate_arg.cpp diff --git a/src/igl/matlab/validate_arg.h b/src/libigl/igl/matlab/validate_arg.h similarity index 100% rename from src/igl/matlab/validate_arg.h rename to src/libigl/igl/matlab/validate_arg.h diff --git a/src/igl/matlab_format.cpp b/src/libigl/igl/matlab_format.cpp similarity index 100% rename from src/igl/matlab_format.cpp rename to src/libigl/igl/matlab_format.cpp diff --git a/src/igl/matlab_format.h b/src/libigl/igl/matlab_format.h similarity index 100% rename from src/igl/matlab_format.h rename to src/libigl/igl/matlab_format.h diff --git a/src/igl/matrix_to_list.cpp b/src/libigl/igl/matrix_to_list.cpp similarity index 100% rename from src/igl/matrix_to_list.cpp rename to src/libigl/igl/matrix_to_list.cpp diff --git a/src/igl/matrix_to_list.h b/src/libigl/igl/matrix_to_list.h similarity index 100% rename from src/igl/matrix_to_list.h rename to src/libigl/igl/matrix_to_list.h diff --git a/src/igl/max.cpp b/src/libigl/igl/max.cpp similarity index 100% rename from src/igl/max.cpp rename to src/libigl/igl/max.cpp diff --git a/src/igl/max.h b/src/libigl/igl/max.h similarity index 100% rename from src/igl/max.h rename to src/libigl/igl/max.h diff --git a/src/igl/max_faces_stopping_condition.cpp b/src/libigl/igl/max_faces_stopping_condition.cpp similarity index 100% rename from src/igl/max_faces_stopping_condition.cpp rename to src/libigl/igl/max_faces_stopping_condition.cpp diff --git a/src/igl/max_faces_stopping_condition.h b/src/libigl/igl/max_faces_stopping_condition.h similarity index 100% rename from src/igl/max_faces_stopping_condition.h rename to src/libigl/igl/max_faces_stopping_condition.h diff --git a/src/igl/max_size.cpp b/src/libigl/igl/max_size.cpp similarity index 100% rename from src/igl/max_size.cpp rename to src/libigl/igl/max_size.cpp diff --git a/src/igl/max_size.h b/src/libigl/igl/max_size.h similarity index 100% rename from src/igl/max_size.h rename to src/libigl/igl/max_size.h diff --git a/src/igl/median.cpp b/src/libigl/igl/median.cpp similarity index 100% rename from src/igl/median.cpp rename to src/libigl/igl/median.cpp diff --git a/src/igl/median.h b/src/libigl/igl/median.h similarity index 100% rename from src/igl/median.h rename to src/libigl/igl/median.h diff --git a/src/igl/min.cpp b/src/libigl/igl/min.cpp similarity index 100% rename from src/igl/min.cpp rename to src/libigl/igl/min.cpp diff --git a/src/igl/min.h b/src/libigl/igl/min.h similarity index 100% rename from src/igl/min.h rename to src/libigl/igl/min.h diff --git a/src/igl/min_quad_dense.cpp b/src/libigl/igl/min_quad_dense.cpp similarity index 100% rename from src/igl/min_quad_dense.cpp rename to src/libigl/igl/min_quad_dense.cpp diff --git a/src/igl/min_quad_dense.h b/src/libigl/igl/min_quad_dense.h similarity index 100% rename from src/igl/min_quad_dense.h rename to src/libigl/igl/min_quad_dense.h diff --git a/src/igl/min_quad_with_fixed.cpp b/src/libigl/igl/min_quad_with_fixed.cpp similarity index 100% rename from src/igl/min_quad_with_fixed.cpp rename to src/libigl/igl/min_quad_with_fixed.cpp diff --git a/src/igl/min_quad_with_fixed.h b/src/libigl/igl/min_quad_with_fixed.h similarity index 100% rename from src/igl/min_quad_with_fixed.h rename to src/libigl/igl/min_quad_with_fixed.h diff --git a/src/igl/min_size.cpp b/src/libigl/igl/min_size.cpp similarity index 100% rename from src/igl/min_size.cpp rename to src/libigl/igl/min_size.cpp diff --git a/src/igl/min_size.h b/src/libigl/igl/min_size.h similarity index 100% rename from src/igl/min_size.h rename to src/libigl/igl/min_size.h diff --git a/src/igl/mod.cpp b/src/libigl/igl/mod.cpp similarity index 100% rename from src/igl/mod.cpp rename to src/libigl/igl/mod.cpp diff --git a/src/igl/mod.h b/src/libigl/igl/mod.h similarity index 100% rename from src/igl/mod.h rename to src/libigl/igl/mod.h diff --git a/src/igl/mode.cpp b/src/libigl/igl/mode.cpp similarity index 100% rename from src/igl/mode.cpp rename to src/libigl/igl/mode.cpp diff --git a/src/igl/mode.h b/src/libigl/igl/mode.h similarity index 100% rename from src/igl/mode.h rename to src/libigl/igl/mode.h diff --git a/src/igl/mosek/bbw.cpp b/src/libigl/igl/mosek/bbw.cpp similarity index 100% rename from src/igl/mosek/bbw.cpp rename to src/libigl/igl/mosek/bbw.cpp diff --git a/src/igl/mosek/bbw.h b/src/libigl/igl/mosek/bbw.h similarity index 100% rename from src/igl/mosek/bbw.h rename to src/libigl/igl/mosek/bbw.h diff --git a/src/igl/mosek/mosek_guarded.cpp b/src/libigl/igl/mosek/mosek_guarded.cpp similarity index 100% rename from src/igl/mosek/mosek_guarded.cpp rename to src/libigl/igl/mosek/mosek_guarded.cpp diff --git a/src/igl/mosek/mosek_guarded.h b/src/libigl/igl/mosek/mosek_guarded.h similarity index 100% rename from src/igl/mosek/mosek_guarded.h rename to src/libigl/igl/mosek/mosek_guarded.h diff --git a/src/igl/mosek/mosek_linprog.cpp b/src/libigl/igl/mosek/mosek_linprog.cpp similarity index 100% rename from src/igl/mosek/mosek_linprog.cpp rename to src/libigl/igl/mosek/mosek_linprog.cpp diff --git a/src/igl/mosek/mosek_linprog.h b/src/libigl/igl/mosek/mosek_linprog.h similarity index 100% rename from src/igl/mosek/mosek_linprog.h rename to src/libigl/igl/mosek/mosek_linprog.h diff --git a/src/igl/mosek/mosek_quadprog.cpp b/src/libigl/igl/mosek/mosek_quadprog.cpp similarity index 100% rename from src/igl/mosek/mosek_quadprog.cpp rename to src/libigl/igl/mosek/mosek_quadprog.cpp diff --git a/src/igl/mosek/mosek_quadprog.h b/src/libigl/igl/mosek/mosek_quadprog.h similarity index 100% rename from src/igl/mosek/mosek_quadprog.h rename to src/libigl/igl/mosek/mosek_quadprog.h diff --git a/src/igl/mvc.cpp b/src/libigl/igl/mvc.cpp similarity index 100% rename from src/igl/mvc.cpp rename to src/libigl/igl/mvc.cpp diff --git a/src/igl/mvc.h b/src/libigl/igl/mvc.h similarity index 100% rename from src/igl/mvc.h rename to src/libigl/igl/mvc.h diff --git a/src/igl/nchoosek.cpp b/src/libigl/igl/nchoosek.cpp similarity index 100% rename from src/igl/nchoosek.cpp rename to src/libigl/igl/nchoosek.cpp diff --git a/src/igl/nchoosek.h b/src/libigl/igl/nchoosek.h similarity index 100% rename from src/igl/nchoosek.h rename to src/libigl/igl/nchoosek.h diff --git a/src/igl/next_filename.cpp b/src/libigl/igl/next_filename.cpp similarity index 100% rename from src/igl/next_filename.cpp rename to src/libigl/igl/next_filename.cpp diff --git a/src/igl/next_filename.h b/src/libigl/igl/next_filename.h similarity index 100% rename from src/igl/next_filename.h rename to src/libigl/igl/next_filename.h diff --git a/src/igl/normal_derivative.cpp b/src/libigl/igl/normal_derivative.cpp similarity index 100% rename from src/igl/normal_derivative.cpp rename to src/libigl/igl/normal_derivative.cpp diff --git a/src/igl/normal_derivative.h b/src/libigl/igl/normal_derivative.h similarity index 100% rename from src/igl/normal_derivative.h rename to src/libigl/igl/normal_derivative.h diff --git a/src/igl/normalize_quat.cpp b/src/libigl/igl/normalize_quat.cpp similarity index 100% rename from src/igl/normalize_quat.cpp rename to src/libigl/igl/normalize_quat.cpp diff --git a/src/igl/normalize_quat.h b/src/libigl/igl/normalize_quat.h similarity index 100% rename from src/igl/normalize_quat.h rename to src/libigl/igl/normalize_quat.h diff --git a/src/igl/normalize_row_lengths.cpp b/src/libigl/igl/normalize_row_lengths.cpp similarity index 100% rename from src/igl/normalize_row_lengths.cpp rename to src/libigl/igl/normalize_row_lengths.cpp diff --git a/src/igl/normalize_row_lengths.h b/src/libigl/igl/normalize_row_lengths.h similarity index 100% rename from src/igl/normalize_row_lengths.h rename to src/libigl/igl/normalize_row_lengths.h diff --git a/src/igl/normalize_row_sums.cpp b/src/libigl/igl/normalize_row_sums.cpp similarity index 100% rename from src/igl/normalize_row_sums.cpp rename to src/libigl/igl/normalize_row_sums.cpp diff --git a/src/igl/normalize_row_sums.h b/src/libigl/igl/normalize_row_sums.h similarity index 100% rename from src/igl/normalize_row_sums.h rename to src/libigl/igl/normalize_row_sums.h diff --git a/src/igl/null.cpp b/src/libigl/igl/null.cpp similarity index 100% rename from src/igl/null.cpp rename to src/libigl/igl/null.cpp diff --git a/src/igl/null.h b/src/libigl/igl/null.h similarity index 100% rename from src/igl/null.h rename to src/libigl/igl/null.h diff --git a/src/igl/octree.cpp b/src/libigl/igl/octree.cpp similarity index 100% rename from src/igl/octree.cpp rename to src/libigl/igl/octree.cpp diff --git a/src/igl/octree.h b/src/libigl/igl/octree.h similarity index 100% rename from src/igl/octree.h rename to src/libigl/igl/octree.h diff --git a/src/igl/on_boundary.cpp b/src/libigl/igl/on_boundary.cpp similarity index 100% rename from src/igl/on_boundary.cpp rename to src/libigl/igl/on_boundary.cpp diff --git a/src/igl/on_boundary.h b/src/libigl/igl/on_boundary.h similarity index 100% rename from src/igl/on_boundary.h rename to src/libigl/igl/on_boundary.h diff --git a/src/igl/opengl/MeshGL.cpp b/src/libigl/igl/opengl/MeshGL.cpp similarity index 100% rename from src/igl/opengl/MeshGL.cpp rename to src/libigl/igl/opengl/MeshGL.cpp diff --git a/src/igl/opengl/MeshGL.h b/src/libigl/igl/opengl/MeshGL.h similarity index 100% rename from src/igl/opengl/MeshGL.h rename to src/libigl/igl/opengl/MeshGL.h diff --git a/src/igl/opengl/ViewerCore.cpp b/src/libigl/igl/opengl/ViewerCore.cpp similarity index 100% rename from src/igl/opengl/ViewerCore.cpp rename to src/libigl/igl/opengl/ViewerCore.cpp diff --git a/src/igl/opengl/ViewerCore.h b/src/libigl/igl/opengl/ViewerCore.h similarity index 100% rename from src/igl/opengl/ViewerCore.h rename to src/libigl/igl/opengl/ViewerCore.h diff --git a/src/igl/opengl/ViewerData.cpp b/src/libigl/igl/opengl/ViewerData.cpp similarity index 100% rename from src/igl/opengl/ViewerData.cpp rename to src/libigl/igl/opengl/ViewerData.cpp diff --git a/src/igl/opengl/ViewerData.h b/src/libigl/igl/opengl/ViewerData.h similarity index 100% rename from src/igl/opengl/ViewerData.h rename to src/libigl/igl/opengl/ViewerData.h diff --git a/src/igl/opengl/bind_vertex_attrib_array.cpp b/src/libigl/igl/opengl/bind_vertex_attrib_array.cpp similarity index 100% rename from src/igl/opengl/bind_vertex_attrib_array.cpp rename to src/libigl/igl/opengl/bind_vertex_attrib_array.cpp diff --git a/src/igl/opengl/bind_vertex_attrib_array.h b/src/libigl/igl/opengl/bind_vertex_attrib_array.h similarity index 100% rename from src/igl/opengl/bind_vertex_attrib_array.h rename to src/libigl/igl/opengl/bind_vertex_attrib_array.h diff --git a/src/igl/opengl/create_index_vbo.cpp b/src/libigl/igl/opengl/create_index_vbo.cpp similarity index 100% rename from src/igl/opengl/create_index_vbo.cpp rename to src/libigl/igl/opengl/create_index_vbo.cpp diff --git a/src/igl/opengl/create_index_vbo.h b/src/libigl/igl/opengl/create_index_vbo.h similarity index 100% rename from src/igl/opengl/create_index_vbo.h rename to src/libigl/igl/opengl/create_index_vbo.h diff --git a/src/igl/opengl/create_mesh_vbo.cpp b/src/libigl/igl/opengl/create_mesh_vbo.cpp similarity index 100% rename from src/igl/opengl/create_mesh_vbo.cpp rename to src/libigl/igl/opengl/create_mesh_vbo.cpp diff --git a/src/igl/opengl/create_mesh_vbo.h b/src/libigl/igl/opengl/create_mesh_vbo.h similarity index 100% rename from src/igl/opengl/create_mesh_vbo.h rename to src/libigl/igl/opengl/create_mesh_vbo.h diff --git a/src/igl/opengl/create_shader_program.cpp b/src/libigl/igl/opengl/create_shader_program.cpp similarity index 100% rename from src/igl/opengl/create_shader_program.cpp rename to src/libigl/igl/opengl/create_shader_program.cpp diff --git a/src/igl/opengl/create_shader_program.h b/src/libigl/igl/opengl/create_shader_program.h similarity index 100% rename from src/igl/opengl/create_shader_program.h rename to src/libigl/igl/opengl/create_shader_program.h diff --git a/src/igl/opengl/create_vector_vbo.cpp b/src/libigl/igl/opengl/create_vector_vbo.cpp similarity index 100% rename from src/igl/opengl/create_vector_vbo.cpp rename to src/libigl/igl/opengl/create_vector_vbo.cpp diff --git a/src/igl/opengl/create_vector_vbo.h b/src/libigl/igl/opengl/create_vector_vbo.h similarity index 100% rename from src/igl/opengl/create_vector_vbo.h rename to src/libigl/igl/opengl/create_vector_vbo.h diff --git a/src/igl/opengl/destroy_shader_program.cpp b/src/libigl/igl/opengl/destroy_shader_program.cpp similarity index 100% rename from src/igl/opengl/destroy_shader_program.cpp rename to src/libigl/igl/opengl/destroy_shader_program.cpp diff --git a/src/igl/opengl/destroy_shader_program.h b/src/libigl/igl/opengl/destroy_shader_program.h similarity index 100% rename from src/igl/opengl/destroy_shader_program.h rename to src/libigl/igl/opengl/destroy_shader_program.h diff --git a/src/igl/opengl/gl.h b/src/libigl/igl/opengl/gl.h similarity index 100% rename from src/igl/opengl/gl.h rename to src/libigl/igl/opengl/gl.h diff --git a/src/igl/opengl/gl_type_size.cpp b/src/libigl/igl/opengl/gl_type_size.cpp similarity index 100% rename from src/igl/opengl/gl_type_size.cpp rename to src/libigl/igl/opengl/gl_type_size.cpp diff --git a/src/igl/opengl/gl_type_size.h b/src/libigl/igl/opengl/gl_type_size.h similarity index 100% rename from src/igl/opengl/gl_type_size.h rename to src/libigl/igl/opengl/gl_type_size.h diff --git a/src/igl/opengl/glfw/Viewer.cpp b/src/libigl/igl/opengl/glfw/Viewer.cpp similarity index 100% rename from src/igl/opengl/glfw/Viewer.cpp rename to src/libigl/igl/opengl/glfw/Viewer.cpp diff --git a/src/igl/opengl/glfw/Viewer.h b/src/libigl/igl/opengl/glfw/Viewer.h similarity index 100% rename from src/igl/opengl/glfw/Viewer.h rename to src/libigl/igl/opengl/glfw/Viewer.h diff --git a/src/igl/opengl/glfw/ViewerPlugin.h b/src/libigl/igl/opengl/glfw/ViewerPlugin.h similarity index 100% rename from src/igl/opengl/glfw/ViewerPlugin.h rename to src/libigl/igl/opengl/glfw/ViewerPlugin.h diff --git a/src/igl/opengl/glfw/background_window.cpp b/src/libigl/igl/opengl/glfw/background_window.cpp similarity index 100% rename from src/igl/opengl/glfw/background_window.cpp rename to src/libigl/igl/opengl/glfw/background_window.cpp diff --git a/src/igl/opengl/glfw/background_window.h b/src/libigl/igl/opengl/glfw/background_window.h similarity index 100% rename from src/igl/opengl/glfw/background_window.h rename to src/libigl/igl/opengl/glfw/background_window.h diff --git a/src/igl/opengl/glfw/imgui/ImGuiHelpers.h b/src/libigl/igl/opengl/glfw/imgui/ImGuiHelpers.h similarity index 100% rename from src/igl/opengl/glfw/imgui/ImGuiHelpers.h rename to src/libigl/igl/opengl/glfw/imgui/ImGuiHelpers.h diff --git a/src/igl/opengl/glfw/imgui/ImGuiMenu.cpp b/src/libigl/igl/opengl/glfw/imgui/ImGuiMenu.cpp similarity index 100% rename from src/igl/opengl/glfw/imgui/ImGuiMenu.cpp rename to src/libigl/igl/opengl/glfw/imgui/ImGuiMenu.cpp diff --git a/src/igl/opengl/glfw/imgui/ImGuiMenu.h b/src/libigl/igl/opengl/glfw/imgui/ImGuiMenu.h similarity index 100% rename from src/igl/opengl/glfw/imgui/ImGuiMenu.h rename to src/libigl/igl/opengl/glfw/imgui/ImGuiMenu.h diff --git a/src/igl/opengl/glfw/map_texture.cpp b/src/libigl/igl/opengl/glfw/map_texture.cpp similarity index 100% rename from src/igl/opengl/glfw/map_texture.cpp rename to src/libigl/igl/opengl/glfw/map_texture.cpp diff --git a/src/igl/opengl/glfw/map_texture.h b/src/libigl/igl/opengl/glfw/map_texture.h similarity index 100% rename from src/igl/opengl/glfw/map_texture.h rename to src/libigl/igl/opengl/glfw/map_texture.h diff --git a/src/igl/opengl/init_render_to_texture.cpp b/src/libigl/igl/opengl/init_render_to_texture.cpp similarity index 100% rename from src/igl/opengl/init_render_to_texture.cpp rename to src/libigl/igl/opengl/init_render_to_texture.cpp diff --git a/src/igl/opengl/init_render_to_texture.h b/src/libigl/igl/opengl/init_render_to_texture.h similarity index 100% rename from src/igl/opengl/init_render_to_texture.h rename to src/libigl/igl/opengl/init_render_to_texture.h diff --git a/src/igl/opengl/load_shader.cpp b/src/libigl/igl/opengl/load_shader.cpp similarity index 100% rename from src/igl/opengl/load_shader.cpp rename to src/libigl/igl/opengl/load_shader.cpp diff --git a/src/igl/opengl/load_shader.h b/src/libigl/igl/opengl/load_shader.h similarity index 100% rename from src/igl/opengl/load_shader.h rename to src/libigl/igl/opengl/load_shader.h diff --git a/src/igl/opengl/print_program_info_log.cpp b/src/libigl/igl/opengl/print_program_info_log.cpp similarity index 100% rename from src/igl/opengl/print_program_info_log.cpp rename to src/libigl/igl/opengl/print_program_info_log.cpp diff --git a/src/igl/opengl/print_program_info_log.h b/src/libigl/igl/opengl/print_program_info_log.h similarity index 100% rename from src/igl/opengl/print_program_info_log.h rename to src/libigl/igl/opengl/print_program_info_log.h diff --git a/src/igl/opengl/print_shader_info_log.cpp b/src/libigl/igl/opengl/print_shader_info_log.cpp similarity index 100% rename from src/igl/opengl/print_shader_info_log.cpp rename to src/libigl/igl/opengl/print_shader_info_log.cpp diff --git a/src/igl/opengl/print_shader_info_log.h b/src/libigl/igl/opengl/print_shader_info_log.h similarity index 100% rename from src/igl/opengl/print_shader_info_log.h rename to src/libigl/igl/opengl/print_shader_info_log.h diff --git a/src/igl/opengl/report_gl_error.cpp b/src/libigl/igl/opengl/report_gl_error.cpp similarity index 100% rename from src/igl/opengl/report_gl_error.cpp rename to src/libigl/igl/opengl/report_gl_error.cpp diff --git a/src/igl/opengl/report_gl_error.h b/src/libigl/igl/opengl/report_gl_error.h similarity index 100% rename from src/igl/opengl/report_gl_error.h rename to src/libigl/igl/opengl/report_gl_error.h diff --git a/src/igl/opengl/uniform_type_to_string.cpp b/src/libigl/igl/opengl/uniform_type_to_string.cpp similarity index 100% rename from src/igl/opengl/uniform_type_to_string.cpp rename to src/libigl/igl/opengl/uniform_type_to_string.cpp diff --git a/src/igl/opengl/uniform_type_to_string.h b/src/libigl/igl/opengl/uniform_type_to_string.h similarity index 100% rename from src/igl/opengl/uniform_type_to_string.h rename to src/libigl/igl/opengl/uniform_type_to_string.h diff --git a/src/igl/opengl/vertex_array.cpp b/src/libigl/igl/opengl/vertex_array.cpp similarity index 100% rename from src/igl/opengl/vertex_array.cpp rename to src/libigl/igl/opengl/vertex_array.cpp diff --git a/src/igl/opengl/vertex_array.h b/src/libigl/igl/opengl/vertex_array.h similarity index 100% rename from src/igl/opengl/vertex_array.h rename to src/libigl/igl/opengl/vertex_array.h diff --git a/src/igl/opengl2/MouseController.h b/src/libigl/igl/opengl2/MouseController.h similarity index 100% rename from src/igl/opengl2/MouseController.h rename to src/libigl/igl/opengl2/MouseController.h diff --git a/src/igl/opengl2/RotateWidget.h b/src/libigl/igl/opengl2/RotateWidget.h similarity index 100% rename from src/igl/opengl2/RotateWidget.h rename to src/libigl/igl/opengl2/RotateWidget.h diff --git a/src/igl/opengl2/TranslateWidget.h b/src/libigl/igl/opengl2/TranslateWidget.h similarity index 100% rename from src/igl/opengl2/TranslateWidget.h rename to src/libigl/igl/opengl2/TranslateWidget.h diff --git a/src/igl/opengl2/draw_beach_ball.cpp b/src/libigl/igl/opengl2/draw_beach_ball.cpp similarity index 100% rename from src/igl/opengl2/draw_beach_ball.cpp rename to src/libigl/igl/opengl2/draw_beach_ball.cpp diff --git a/src/igl/opengl2/draw_beach_ball.h b/src/libigl/igl/opengl2/draw_beach_ball.h similarity index 100% rename from src/igl/opengl2/draw_beach_ball.h rename to src/libigl/igl/opengl2/draw_beach_ball.h diff --git a/src/igl/opengl2/draw_floor.cpp b/src/libigl/igl/opengl2/draw_floor.cpp similarity index 100% rename from src/igl/opengl2/draw_floor.cpp rename to src/libigl/igl/opengl2/draw_floor.cpp diff --git a/src/igl/opengl2/draw_floor.h b/src/libigl/igl/opengl2/draw_floor.h similarity index 100% rename from src/igl/opengl2/draw_floor.h rename to src/libigl/igl/opengl2/draw_floor.h diff --git a/src/igl/opengl2/draw_mesh.cpp b/src/libigl/igl/opengl2/draw_mesh.cpp similarity index 100% rename from src/igl/opengl2/draw_mesh.cpp rename to src/libigl/igl/opengl2/draw_mesh.cpp diff --git a/src/igl/opengl2/draw_mesh.h b/src/libigl/igl/opengl2/draw_mesh.h similarity index 100% rename from src/igl/opengl2/draw_mesh.h rename to src/libigl/igl/opengl2/draw_mesh.h diff --git a/src/igl/opengl2/draw_point.cpp b/src/libigl/igl/opengl2/draw_point.cpp similarity index 100% rename from src/igl/opengl2/draw_point.cpp rename to src/libigl/igl/opengl2/draw_point.cpp diff --git a/src/igl/opengl2/draw_point.h b/src/libigl/igl/opengl2/draw_point.h similarity index 100% rename from src/igl/opengl2/draw_point.h rename to src/libigl/igl/opengl2/draw_point.h diff --git a/src/igl/opengl2/draw_rectangular_marquee.cpp b/src/libigl/igl/opengl2/draw_rectangular_marquee.cpp similarity index 100% rename from src/igl/opengl2/draw_rectangular_marquee.cpp rename to src/libigl/igl/opengl2/draw_rectangular_marquee.cpp diff --git a/src/igl/opengl2/draw_rectangular_marquee.h b/src/libigl/igl/opengl2/draw_rectangular_marquee.h similarity index 100% rename from src/igl/opengl2/draw_rectangular_marquee.h rename to src/libigl/igl/opengl2/draw_rectangular_marquee.h diff --git a/src/igl/opengl2/draw_skeleton_3d.cpp b/src/libigl/igl/opengl2/draw_skeleton_3d.cpp similarity index 100% rename from src/igl/opengl2/draw_skeleton_3d.cpp rename to src/libigl/igl/opengl2/draw_skeleton_3d.cpp diff --git a/src/igl/opengl2/draw_skeleton_3d.h b/src/libigl/igl/opengl2/draw_skeleton_3d.h similarity index 100% rename from src/igl/opengl2/draw_skeleton_3d.h rename to src/libigl/igl/opengl2/draw_skeleton_3d.h diff --git a/src/igl/opengl2/draw_skeleton_vector_graphics.cpp b/src/libigl/igl/opengl2/draw_skeleton_vector_graphics.cpp similarity index 100% rename from src/igl/opengl2/draw_skeleton_vector_graphics.cpp rename to src/libigl/igl/opengl2/draw_skeleton_vector_graphics.cpp diff --git a/src/igl/opengl2/draw_skeleton_vector_graphics.h b/src/libigl/igl/opengl2/draw_skeleton_vector_graphics.h similarity index 100% rename from src/igl/opengl2/draw_skeleton_vector_graphics.h rename to src/libigl/igl/opengl2/draw_skeleton_vector_graphics.h diff --git a/src/igl/opengl2/flare_textures.h b/src/libigl/igl/opengl2/flare_textures.h similarity index 100% rename from src/igl/opengl2/flare_textures.h rename to src/libigl/igl/opengl2/flare_textures.h diff --git a/src/igl/opengl2/gl.h b/src/libigl/igl/opengl2/gl.h similarity index 100% rename from src/igl/opengl2/gl.h rename to src/libigl/igl/opengl2/gl.h diff --git a/src/igl/opengl2/glext.h b/src/libigl/igl/opengl2/glext.h similarity index 100% rename from src/igl/opengl2/glext.h rename to src/libigl/igl/opengl2/glext.h diff --git a/src/igl/opengl2/glu.h b/src/libigl/igl/opengl2/glu.h similarity index 100% rename from src/igl/opengl2/glu.h rename to src/libigl/igl/opengl2/glu.h diff --git a/src/igl/opengl2/lens_flare.cpp b/src/libigl/igl/opengl2/lens_flare.cpp similarity index 100% rename from src/igl/opengl2/lens_flare.cpp rename to src/libigl/igl/opengl2/lens_flare.cpp diff --git a/src/igl/opengl2/lens_flare.h b/src/libigl/igl/opengl2/lens_flare.h similarity index 100% rename from src/igl/opengl2/lens_flare.h rename to src/libigl/igl/opengl2/lens_flare.h diff --git a/src/igl/opengl2/model_proj_viewport.cpp b/src/libigl/igl/opengl2/model_proj_viewport.cpp similarity index 100% rename from src/igl/opengl2/model_proj_viewport.cpp rename to src/libigl/igl/opengl2/model_proj_viewport.cpp diff --git a/src/igl/opengl2/model_proj_viewport.h b/src/libigl/igl/opengl2/model_proj_viewport.h similarity index 100% rename from src/igl/opengl2/model_proj_viewport.h rename to src/libigl/igl/opengl2/model_proj_viewport.h diff --git a/src/igl/opengl2/print_gl_get.cpp b/src/libigl/igl/opengl2/print_gl_get.cpp similarity index 100% rename from src/igl/opengl2/print_gl_get.cpp rename to src/libigl/igl/opengl2/print_gl_get.cpp diff --git a/src/igl/opengl2/print_gl_get.h b/src/libigl/igl/opengl2/print_gl_get.h similarity index 100% rename from src/igl/opengl2/print_gl_get.h rename to src/libigl/igl/opengl2/print_gl_get.h diff --git a/src/igl/opengl2/project.cpp b/src/libigl/igl/opengl2/project.cpp similarity index 100% rename from src/igl/opengl2/project.cpp rename to src/libigl/igl/opengl2/project.cpp diff --git a/src/igl/opengl2/project.h b/src/libigl/igl/opengl2/project.h similarity index 100% rename from src/igl/opengl2/project.h rename to src/libigl/igl/opengl2/project.h diff --git a/src/igl/opengl2/right_axis.cpp b/src/libigl/igl/opengl2/right_axis.cpp similarity index 100% rename from src/igl/opengl2/right_axis.cpp rename to src/libigl/igl/opengl2/right_axis.cpp diff --git a/src/igl/opengl2/right_axis.h b/src/libigl/igl/opengl2/right_axis.h similarity index 100% rename from src/igl/opengl2/right_axis.h rename to src/libigl/igl/opengl2/right_axis.h diff --git a/src/igl/opengl2/shine_textures.h b/src/libigl/igl/opengl2/shine_textures.h similarity index 100% rename from src/igl/opengl2/shine_textures.h rename to src/libigl/igl/opengl2/shine_textures.h diff --git a/src/igl/opengl2/sort_triangles.cpp b/src/libigl/igl/opengl2/sort_triangles.cpp similarity index 100% rename from src/igl/opengl2/sort_triangles.cpp rename to src/libigl/igl/opengl2/sort_triangles.cpp diff --git a/src/igl/opengl2/sort_triangles.h b/src/libigl/igl/opengl2/sort_triangles.h similarity index 100% rename from src/igl/opengl2/sort_triangles.h rename to src/libigl/igl/opengl2/sort_triangles.h diff --git a/src/igl/opengl2/unproject.cpp b/src/libigl/igl/opengl2/unproject.cpp similarity index 100% rename from src/igl/opengl2/unproject.cpp rename to src/libigl/igl/opengl2/unproject.cpp diff --git a/src/igl/opengl2/unproject.h b/src/libigl/igl/opengl2/unproject.h similarity index 100% rename from src/igl/opengl2/unproject.h rename to src/libigl/igl/opengl2/unproject.h diff --git a/src/igl/opengl2/unproject_to_zero_plane.cpp b/src/libigl/igl/opengl2/unproject_to_zero_plane.cpp similarity index 100% rename from src/igl/opengl2/unproject_to_zero_plane.cpp rename to src/libigl/igl/opengl2/unproject_to_zero_plane.cpp diff --git a/src/igl/opengl2/unproject_to_zero_plane.h b/src/libigl/igl/opengl2/unproject_to_zero_plane.h similarity index 100% rename from src/igl/opengl2/unproject_to_zero_plane.h rename to src/libigl/igl/opengl2/unproject_to_zero_plane.h diff --git a/src/igl/opengl2/up_axis.cpp b/src/libigl/igl/opengl2/up_axis.cpp similarity index 100% rename from src/igl/opengl2/up_axis.cpp rename to src/libigl/igl/opengl2/up_axis.cpp diff --git a/src/igl/opengl2/up_axis.h b/src/libigl/igl/opengl2/up_axis.h similarity index 100% rename from src/igl/opengl2/up_axis.h rename to src/libigl/igl/opengl2/up_axis.h diff --git a/src/igl/opengl2/view_axis.cpp b/src/libigl/igl/opengl2/view_axis.cpp similarity index 100% rename from src/igl/opengl2/view_axis.cpp rename to src/libigl/igl/opengl2/view_axis.cpp diff --git a/src/igl/opengl2/view_axis.h b/src/libigl/igl/opengl2/view_axis.h similarity index 100% rename from src/igl/opengl2/view_axis.h rename to src/libigl/igl/opengl2/view_axis.h diff --git a/src/igl/orient_outward.cpp b/src/libigl/igl/orient_outward.cpp similarity index 100% rename from src/igl/orient_outward.cpp rename to src/libigl/igl/orient_outward.cpp diff --git a/src/igl/orient_outward.h b/src/libigl/igl/orient_outward.h similarity index 100% rename from src/igl/orient_outward.h rename to src/libigl/igl/orient_outward.h diff --git a/src/igl/orientable_patches.cpp b/src/libigl/igl/orientable_patches.cpp similarity index 100% rename from src/igl/orientable_patches.cpp rename to src/libigl/igl/orientable_patches.cpp diff --git a/src/igl/orientable_patches.h b/src/libigl/igl/orientable_patches.h similarity index 100% rename from src/igl/orientable_patches.h rename to src/libigl/igl/orientable_patches.h diff --git a/src/igl/oriented_facets.cpp b/src/libigl/igl/oriented_facets.cpp similarity index 100% rename from src/igl/oriented_facets.cpp rename to src/libigl/igl/oriented_facets.cpp diff --git a/src/igl/oriented_facets.h b/src/libigl/igl/oriented_facets.h similarity index 100% rename from src/igl/oriented_facets.h rename to src/libigl/igl/oriented_facets.h diff --git a/src/igl/orth.cpp b/src/libigl/igl/orth.cpp similarity index 100% rename from src/igl/orth.cpp rename to src/libigl/igl/orth.cpp diff --git a/src/igl/orth.h b/src/libigl/igl/orth.h similarity index 100% rename from src/igl/orth.h rename to src/libigl/igl/orth.h diff --git a/src/igl/ortho.cpp b/src/libigl/igl/ortho.cpp similarity index 100% rename from src/igl/ortho.cpp rename to src/libigl/igl/ortho.cpp diff --git a/src/igl/ortho.h b/src/libigl/igl/ortho.h similarity index 100% rename from src/igl/ortho.h rename to src/libigl/igl/ortho.h diff --git a/src/igl/outer_element.cpp b/src/libigl/igl/outer_element.cpp similarity index 100% rename from src/igl/outer_element.cpp rename to src/libigl/igl/outer_element.cpp diff --git a/src/igl/outer_element.h b/src/libigl/igl/outer_element.h similarity index 100% rename from src/igl/outer_element.h rename to src/libigl/igl/outer_element.h diff --git a/src/igl/parallel_for.h b/src/libigl/igl/parallel_for.h similarity index 100% rename from src/igl/parallel_for.h rename to src/libigl/igl/parallel_for.h diff --git a/src/igl/parallel_transport_angles.cpp b/src/libigl/igl/parallel_transport_angles.cpp similarity index 100% rename from src/igl/parallel_transport_angles.cpp rename to src/libigl/igl/parallel_transport_angles.cpp diff --git a/src/igl/parallel_transport_angles.h b/src/libigl/igl/parallel_transport_angles.h similarity index 100% rename from src/igl/parallel_transport_angles.h rename to src/libigl/igl/parallel_transport_angles.h diff --git a/src/igl/partition.cpp b/src/libigl/igl/partition.cpp similarity index 100% rename from src/igl/partition.cpp rename to src/libigl/igl/partition.cpp diff --git a/src/igl/partition.h b/src/libigl/igl/partition.h similarity index 100% rename from src/igl/partition.h rename to src/libigl/igl/partition.h diff --git a/src/igl/parula.cpp b/src/libigl/igl/parula.cpp similarity index 100% rename from src/igl/parula.cpp rename to src/libigl/igl/parula.cpp diff --git a/src/igl/parula.h b/src/libigl/igl/parula.h similarity index 100% rename from src/igl/parula.h rename to src/libigl/igl/parula.h diff --git a/src/igl/path_to_executable.cpp b/src/libigl/igl/path_to_executable.cpp similarity index 100% rename from src/igl/path_to_executable.cpp rename to src/libigl/igl/path_to_executable.cpp diff --git a/src/igl/path_to_executable.h b/src/libigl/igl/path_to_executable.h similarity index 100% rename from src/igl/path_to_executable.h rename to src/libigl/igl/path_to_executable.h diff --git a/src/igl/pathinfo.cpp b/src/libigl/igl/pathinfo.cpp similarity index 100% rename from src/igl/pathinfo.cpp rename to src/libigl/igl/pathinfo.cpp diff --git a/src/igl/pathinfo.h b/src/libigl/igl/pathinfo.h similarity index 100% rename from src/igl/pathinfo.h rename to src/libigl/igl/pathinfo.h diff --git a/src/igl/per_corner_normals.cpp b/src/libigl/igl/per_corner_normals.cpp similarity index 100% rename from src/igl/per_corner_normals.cpp rename to src/libigl/igl/per_corner_normals.cpp diff --git a/src/igl/per_corner_normals.h b/src/libigl/igl/per_corner_normals.h similarity index 100% rename from src/igl/per_corner_normals.h rename to src/libigl/igl/per_corner_normals.h diff --git a/src/igl/per_edge_normals.cpp b/src/libigl/igl/per_edge_normals.cpp similarity index 100% rename from src/igl/per_edge_normals.cpp rename to src/libigl/igl/per_edge_normals.cpp diff --git a/src/igl/per_edge_normals.h b/src/libigl/igl/per_edge_normals.h similarity index 100% rename from src/igl/per_edge_normals.h rename to src/libigl/igl/per_edge_normals.h diff --git a/src/igl/per_face_normals.cpp b/src/libigl/igl/per_face_normals.cpp similarity index 100% rename from src/igl/per_face_normals.cpp rename to src/libigl/igl/per_face_normals.cpp diff --git a/src/igl/per_face_normals.h b/src/libigl/igl/per_face_normals.h similarity index 100% rename from src/igl/per_face_normals.h rename to src/libigl/igl/per_face_normals.h diff --git a/src/igl/per_vertex_attribute_smoothing.cpp b/src/libigl/igl/per_vertex_attribute_smoothing.cpp similarity index 100% rename from src/igl/per_vertex_attribute_smoothing.cpp rename to src/libigl/igl/per_vertex_attribute_smoothing.cpp diff --git a/src/igl/per_vertex_attribute_smoothing.h b/src/libigl/igl/per_vertex_attribute_smoothing.h similarity index 100% rename from src/igl/per_vertex_attribute_smoothing.h rename to src/libigl/igl/per_vertex_attribute_smoothing.h diff --git a/src/igl/per_vertex_normals.cpp b/src/libigl/igl/per_vertex_normals.cpp similarity index 100% rename from src/igl/per_vertex_normals.cpp rename to src/libigl/igl/per_vertex_normals.cpp diff --git a/src/igl/per_vertex_normals.h b/src/libigl/igl/per_vertex_normals.h similarity index 100% rename from src/igl/per_vertex_normals.h rename to src/libigl/igl/per_vertex_normals.h diff --git a/src/igl/per_vertex_point_to_plane_quadrics.cpp b/src/libigl/igl/per_vertex_point_to_plane_quadrics.cpp similarity index 100% rename from src/igl/per_vertex_point_to_plane_quadrics.cpp rename to src/libigl/igl/per_vertex_point_to_plane_quadrics.cpp diff --git a/src/igl/per_vertex_point_to_plane_quadrics.h b/src/libigl/igl/per_vertex_point_to_plane_quadrics.h similarity index 100% rename from src/igl/per_vertex_point_to_plane_quadrics.h rename to src/libigl/igl/per_vertex_point_to_plane_quadrics.h diff --git a/src/igl/piecewise_constant_winding_number.cpp b/src/libigl/igl/piecewise_constant_winding_number.cpp similarity index 100% rename from src/igl/piecewise_constant_winding_number.cpp rename to src/libigl/igl/piecewise_constant_winding_number.cpp diff --git a/src/igl/piecewise_constant_winding_number.h b/src/libigl/igl/piecewise_constant_winding_number.h similarity index 100% rename from src/igl/piecewise_constant_winding_number.h rename to src/libigl/igl/piecewise_constant_winding_number.h diff --git a/src/igl/pinv.cpp b/src/libigl/igl/pinv.cpp similarity index 100% rename from src/igl/pinv.cpp rename to src/libigl/igl/pinv.cpp diff --git a/src/igl/pinv.h b/src/libigl/igl/pinv.h similarity index 100% rename from src/igl/pinv.h rename to src/libigl/igl/pinv.h diff --git a/src/igl/planarize_quad_mesh.cpp b/src/libigl/igl/planarize_quad_mesh.cpp similarity index 100% rename from src/igl/planarize_quad_mesh.cpp rename to src/libigl/igl/planarize_quad_mesh.cpp diff --git a/src/igl/planarize_quad_mesh.h b/src/libigl/igl/planarize_quad_mesh.h similarity index 100% rename from src/igl/planarize_quad_mesh.h rename to src/libigl/igl/planarize_quad_mesh.h diff --git a/src/igl/ply.h b/src/libigl/igl/ply.h similarity index 100% rename from src/igl/ply.h rename to src/libigl/igl/ply.h diff --git a/src/igl/png/readPNG.cpp b/src/libigl/igl/png/readPNG.cpp similarity index 100% rename from src/igl/png/readPNG.cpp rename to src/libigl/igl/png/readPNG.cpp diff --git a/src/igl/png/readPNG.h b/src/libigl/igl/png/readPNG.h similarity index 100% rename from src/igl/png/readPNG.h rename to src/libigl/igl/png/readPNG.h diff --git a/src/igl/png/render_to_png.cpp b/src/libigl/igl/png/render_to_png.cpp similarity index 100% rename from src/igl/png/render_to_png.cpp rename to src/libigl/igl/png/render_to_png.cpp diff --git a/src/igl/png/render_to_png.h b/src/libigl/igl/png/render_to_png.h similarity index 100% rename from src/igl/png/render_to_png.h rename to src/libigl/igl/png/render_to_png.h diff --git a/src/igl/png/render_to_png_async.cpp b/src/libigl/igl/png/render_to_png_async.cpp similarity index 100% rename from src/igl/png/render_to_png_async.cpp rename to src/libigl/igl/png/render_to_png_async.cpp diff --git a/src/igl/png/render_to_png_async.h b/src/libigl/igl/png/render_to_png_async.h similarity index 100% rename from src/igl/png/render_to_png_async.h rename to src/libigl/igl/png/render_to_png_async.h diff --git a/src/igl/png/texture_from_file.cpp b/src/libigl/igl/png/texture_from_file.cpp similarity index 100% rename from src/igl/png/texture_from_file.cpp rename to src/libigl/igl/png/texture_from_file.cpp diff --git a/src/igl/png/texture_from_file.h b/src/libigl/igl/png/texture_from_file.h similarity index 100% rename from src/igl/png/texture_from_file.h rename to src/libigl/igl/png/texture_from_file.h diff --git a/src/igl/png/texture_from_png.cpp b/src/libigl/igl/png/texture_from_png.cpp similarity index 100% rename from src/igl/png/texture_from_png.cpp rename to src/libigl/igl/png/texture_from_png.cpp diff --git a/src/igl/png/texture_from_png.h b/src/libigl/igl/png/texture_from_png.h similarity index 100% rename from src/igl/png/texture_from_png.h rename to src/libigl/igl/png/texture_from_png.h diff --git a/src/igl/png/writePNG.cpp b/src/libigl/igl/png/writePNG.cpp similarity index 100% rename from src/igl/png/writePNG.cpp rename to src/libigl/igl/png/writePNG.cpp diff --git a/src/igl/png/writePNG.h b/src/libigl/igl/png/writePNG.h similarity index 100% rename from src/igl/png/writePNG.h rename to src/libigl/igl/png/writePNG.h diff --git a/src/igl/point_in_circle.cpp b/src/libigl/igl/point_in_circle.cpp similarity index 100% rename from src/igl/point_in_circle.cpp rename to src/libigl/igl/point_in_circle.cpp diff --git a/src/igl/point_in_circle.h b/src/libigl/igl/point_in_circle.h similarity index 100% rename from src/igl/point_in_circle.h rename to src/libigl/igl/point_in_circle.h diff --git a/src/igl/point_in_poly.cpp b/src/libigl/igl/point_in_poly.cpp similarity index 100% rename from src/igl/point_in_poly.cpp rename to src/libigl/igl/point_in_poly.cpp diff --git a/src/igl/point_in_poly.h b/src/libigl/igl/point_in_poly.h similarity index 100% rename from src/igl/point_in_poly.h rename to src/libigl/igl/point_in_poly.h diff --git a/src/igl/point_mesh_squared_distance.cpp b/src/libigl/igl/point_mesh_squared_distance.cpp similarity index 100% rename from src/igl/point_mesh_squared_distance.cpp rename to src/libigl/igl/point_mesh_squared_distance.cpp diff --git a/src/igl/point_mesh_squared_distance.h b/src/libigl/igl/point_mesh_squared_distance.h similarity index 100% rename from src/igl/point_mesh_squared_distance.h rename to src/libigl/igl/point_mesh_squared_distance.h diff --git a/src/igl/point_simplex_squared_distance.cpp b/src/libigl/igl/point_simplex_squared_distance.cpp similarity index 100% rename from src/igl/point_simplex_squared_distance.cpp rename to src/libigl/igl/point_simplex_squared_distance.cpp diff --git a/src/igl/point_simplex_squared_distance.h b/src/libigl/igl/point_simplex_squared_distance.h similarity index 100% rename from src/igl/point_simplex_squared_distance.h rename to src/libigl/igl/point_simplex_squared_distance.h diff --git a/src/igl/polar_dec.cpp b/src/libigl/igl/polar_dec.cpp similarity index 100% rename from src/igl/polar_dec.cpp rename to src/libigl/igl/polar_dec.cpp diff --git a/src/igl/polar_dec.h b/src/libigl/igl/polar_dec.h similarity index 100% rename from src/igl/polar_dec.h rename to src/libigl/igl/polar_dec.h diff --git a/src/igl/polar_svd.cpp b/src/libigl/igl/polar_svd.cpp similarity index 100% rename from src/igl/polar_svd.cpp rename to src/libigl/igl/polar_svd.cpp diff --git a/src/igl/polar_svd.h b/src/libigl/igl/polar_svd.h similarity index 100% rename from src/igl/polar_svd.h rename to src/libigl/igl/polar_svd.h diff --git a/src/igl/polar_svd3x3.cpp b/src/libigl/igl/polar_svd3x3.cpp similarity index 100% rename from src/igl/polar_svd3x3.cpp rename to src/libigl/igl/polar_svd3x3.cpp diff --git a/src/igl/polar_svd3x3.h b/src/libigl/igl/polar_svd3x3.h similarity index 100% rename from src/igl/polar_svd3x3.h rename to src/libigl/igl/polar_svd3x3.h diff --git a/src/igl/polygon_mesh_to_triangle_mesh.cpp b/src/libigl/igl/polygon_mesh_to_triangle_mesh.cpp similarity index 100% rename from src/igl/polygon_mesh_to_triangle_mesh.cpp rename to src/libigl/igl/polygon_mesh_to_triangle_mesh.cpp diff --git a/src/igl/polygon_mesh_to_triangle_mesh.h b/src/libigl/igl/polygon_mesh_to_triangle_mesh.h similarity index 100% rename from src/igl/polygon_mesh_to_triangle_mesh.h rename to src/libigl/igl/polygon_mesh_to_triangle_mesh.h diff --git a/src/igl/principal_curvature.cpp b/src/libigl/igl/principal_curvature.cpp similarity index 100% rename from src/igl/principal_curvature.cpp rename to src/libigl/igl/principal_curvature.cpp diff --git a/src/igl/principal_curvature.h b/src/libigl/igl/principal_curvature.h similarity index 100% rename from src/igl/principal_curvature.h rename to src/libigl/igl/principal_curvature.h diff --git a/src/igl/print_ijv.cpp b/src/libigl/igl/print_ijv.cpp similarity index 100% rename from src/igl/print_ijv.cpp rename to src/libigl/igl/print_ijv.cpp diff --git a/src/igl/print_ijv.h b/src/libigl/igl/print_ijv.h similarity index 100% rename from src/igl/print_ijv.h rename to src/libigl/igl/print_ijv.h diff --git a/src/igl/print_vector.cpp b/src/libigl/igl/print_vector.cpp similarity index 100% rename from src/igl/print_vector.cpp rename to src/libigl/igl/print_vector.cpp diff --git a/src/igl/print_vector.h b/src/libigl/igl/print_vector.h similarity index 100% rename from src/igl/print_vector.h rename to src/libigl/igl/print_vector.h diff --git a/src/igl/procrustes.cpp b/src/libigl/igl/procrustes.cpp similarity index 100% rename from src/igl/procrustes.cpp rename to src/libigl/igl/procrustes.cpp diff --git a/src/igl/procrustes.h b/src/libigl/igl/procrustes.h similarity index 100% rename from src/igl/procrustes.h rename to src/libigl/igl/procrustes.h diff --git a/src/igl/project.cpp b/src/libigl/igl/project.cpp similarity index 100% rename from src/igl/project.cpp rename to src/libigl/igl/project.cpp diff --git a/src/igl/project.h b/src/libigl/igl/project.h similarity index 100% rename from src/igl/project.h rename to src/libigl/igl/project.h diff --git a/src/igl/project_isometrically_to_plane.cpp b/src/libigl/igl/project_isometrically_to_plane.cpp similarity index 100% rename from src/igl/project_isometrically_to_plane.cpp rename to src/libigl/igl/project_isometrically_to_plane.cpp diff --git a/src/igl/project_isometrically_to_plane.h b/src/libigl/igl/project_isometrically_to_plane.h similarity index 100% rename from src/igl/project_isometrically_to_plane.h rename to src/libigl/igl/project_isometrically_to_plane.h diff --git a/src/igl/project_to_line.cpp b/src/libigl/igl/project_to_line.cpp similarity index 100% rename from src/igl/project_to_line.cpp rename to src/libigl/igl/project_to_line.cpp diff --git a/src/igl/project_to_line.h b/src/libigl/igl/project_to_line.h similarity index 100% rename from src/igl/project_to_line.h rename to src/libigl/igl/project_to_line.h diff --git a/src/igl/project_to_line_segment.cpp b/src/libigl/igl/project_to_line_segment.cpp similarity index 100% rename from src/igl/project_to_line_segment.cpp rename to src/libigl/igl/project_to_line_segment.cpp diff --git a/src/igl/project_to_line_segment.h b/src/libigl/igl/project_to_line_segment.h similarity index 100% rename from src/igl/project_to_line_segment.h rename to src/libigl/igl/project_to_line_segment.h diff --git a/src/igl/pseudonormal_test.cpp b/src/libigl/igl/pseudonormal_test.cpp similarity index 100% rename from src/igl/pseudonormal_test.cpp rename to src/libigl/igl/pseudonormal_test.cpp diff --git a/src/igl/pseudonormal_test.h b/src/libigl/igl/pseudonormal_test.h similarity index 100% rename from src/igl/pseudonormal_test.h rename to src/libigl/igl/pseudonormal_test.h diff --git a/src/igl/pso.cpp b/src/libigl/igl/pso.cpp similarity index 100% rename from src/igl/pso.cpp rename to src/libigl/igl/pso.cpp diff --git a/src/igl/pso.h b/src/libigl/igl/pso.h similarity index 100% rename from src/igl/pso.h rename to src/libigl/igl/pso.h diff --git a/src/igl/qslim.cpp b/src/libigl/igl/qslim.cpp similarity index 100% rename from src/igl/qslim.cpp rename to src/libigl/igl/qslim.cpp diff --git a/src/igl/qslim.h b/src/libigl/igl/qslim.h similarity index 100% rename from src/igl/qslim.h rename to src/libigl/igl/qslim.h diff --git a/src/igl/qslim_optimal_collapse_edge_callbacks.cpp b/src/libigl/igl/qslim_optimal_collapse_edge_callbacks.cpp similarity index 100% rename from src/igl/qslim_optimal_collapse_edge_callbacks.cpp rename to src/libigl/igl/qslim_optimal_collapse_edge_callbacks.cpp diff --git a/src/igl/qslim_optimal_collapse_edge_callbacks.h b/src/libigl/igl/qslim_optimal_collapse_edge_callbacks.h similarity index 100% rename from src/igl/qslim_optimal_collapse_edge_callbacks.h rename to src/libigl/igl/qslim_optimal_collapse_edge_callbacks.h diff --git a/src/igl/quad_planarity.cpp b/src/libigl/igl/quad_planarity.cpp similarity index 100% rename from src/igl/quad_planarity.cpp rename to src/libigl/igl/quad_planarity.cpp diff --git a/src/igl/quad_planarity.h b/src/libigl/igl/quad_planarity.h similarity index 100% rename from src/igl/quad_planarity.h rename to src/libigl/igl/quad_planarity.h diff --git a/src/igl/quadric_binary_plus_operator.cpp b/src/libigl/igl/quadric_binary_plus_operator.cpp similarity index 100% rename from src/igl/quadric_binary_plus_operator.cpp rename to src/libigl/igl/quadric_binary_plus_operator.cpp diff --git a/src/igl/quadric_binary_plus_operator.h b/src/libigl/igl/quadric_binary_plus_operator.h similarity index 100% rename from src/igl/quadric_binary_plus_operator.h rename to src/libigl/igl/quadric_binary_plus_operator.h diff --git a/src/igl/quat_conjugate.cpp b/src/libigl/igl/quat_conjugate.cpp similarity index 100% rename from src/igl/quat_conjugate.cpp rename to src/libigl/igl/quat_conjugate.cpp diff --git a/src/igl/quat_conjugate.h b/src/libigl/igl/quat_conjugate.h similarity index 100% rename from src/igl/quat_conjugate.h rename to src/libigl/igl/quat_conjugate.h diff --git a/src/igl/quat_mult.cpp b/src/libigl/igl/quat_mult.cpp similarity index 100% rename from src/igl/quat_mult.cpp rename to src/libigl/igl/quat_mult.cpp diff --git a/src/igl/quat_mult.h b/src/libigl/igl/quat_mult.h similarity index 100% rename from src/igl/quat_mult.h rename to src/libigl/igl/quat_mult.h diff --git a/src/igl/quat_to_axis_angle.cpp b/src/libigl/igl/quat_to_axis_angle.cpp similarity index 100% rename from src/igl/quat_to_axis_angle.cpp rename to src/libigl/igl/quat_to_axis_angle.cpp diff --git a/src/igl/quat_to_axis_angle.h b/src/libigl/igl/quat_to_axis_angle.h similarity index 100% rename from src/igl/quat_to_axis_angle.h rename to src/libigl/igl/quat_to_axis_angle.h diff --git a/src/igl/quat_to_mat.cpp b/src/libigl/igl/quat_to_mat.cpp similarity index 100% rename from src/igl/quat_to_mat.cpp rename to src/libigl/igl/quat_to_mat.cpp diff --git a/src/igl/quat_to_mat.h b/src/libigl/igl/quat_to_mat.h similarity index 100% rename from src/igl/quat_to_mat.h rename to src/libigl/igl/quat_to_mat.h diff --git a/src/igl/quats_to_column.cpp b/src/libigl/igl/quats_to_column.cpp similarity index 100% rename from src/igl/quats_to_column.cpp rename to src/libigl/igl/quats_to_column.cpp diff --git a/src/igl/quats_to_column.h b/src/libigl/igl/quats_to_column.h similarity index 100% rename from src/igl/quats_to_column.h rename to src/libigl/igl/quats_to_column.h diff --git a/src/igl/ramer_douglas_peucker.cpp b/src/libigl/igl/ramer_douglas_peucker.cpp similarity index 100% rename from src/igl/ramer_douglas_peucker.cpp rename to src/libigl/igl/ramer_douglas_peucker.cpp diff --git a/src/igl/ramer_douglas_peucker.h b/src/libigl/igl/ramer_douglas_peucker.h similarity index 100% rename from src/igl/ramer_douglas_peucker.h rename to src/libigl/igl/ramer_douglas_peucker.h diff --git a/src/igl/random_dir.cpp b/src/libigl/igl/random_dir.cpp similarity index 100% rename from src/igl/random_dir.cpp rename to src/libigl/igl/random_dir.cpp diff --git a/src/igl/random_dir.h b/src/libigl/igl/random_dir.h similarity index 100% rename from src/igl/random_dir.h rename to src/libigl/igl/random_dir.h diff --git a/src/igl/random_points_on_mesh.cpp b/src/libigl/igl/random_points_on_mesh.cpp similarity index 100% rename from src/igl/random_points_on_mesh.cpp rename to src/libigl/igl/random_points_on_mesh.cpp diff --git a/src/igl/random_points_on_mesh.h b/src/libigl/igl/random_points_on_mesh.h similarity index 100% rename from src/igl/random_points_on_mesh.h rename to src/libigl/igl/random_points_on_mesh.h diff --git a/src/igl/random_quaternion.cpp b/src/libigl/igl/random_quaternion.cpp similarity index 100% rename from src/igl/random_quaternion.cpp rename to src/libigl/igl/random_quaternion.cpp diff --git a/src/igl/random_quaternion.h b/src/libigl/igl/random_quaternion.h similarity index 100% rename from src/igl/random_quaternion.h rename to src/libigl/igl/random_quaternion.h diff --git a/src/igl/random_search.cpp b/src/libigl/igl/random_search.cpp similarity index 100% rename from src/igl/random_search.cpp rename to src/libigl/igl/random_search.cpp diff --git a/src/igl/random_search.h b/src/libigl/igl/random_search.h similarity index 100% rename from src/igl/random_search.h rename to src/libigl/igl/random_search.h diff --git a/src/igl/randperm.cpp b/src/libigl/igl/randperm.cpp similarity index 100% rename from src/igl/randperm.cpp rename to src/libigl/igl/randperm.cpp diff --git a/src/igl/randperm.h b/src/libigl/igl/randperm.h similarity index 100% rename from src/igl/randperm.h rename to src/libigl/igl/randperm.h diff --git a/src/igl/ray_box_intersect.cpp b/src/libigl/igl/ray_box_intersect.cpp similarity index 91% rename from src/igl/ray_box_intersect.cpp rename to src/libigl/igl/ray_box_intersect.cpp index 8c6346d86d..088273f255 100644 --- a/src/igl/ray_box_intersect.cpp +++ b/src/libigl/igl/ray_box_intersect.cpp @@ -1,12 +1,12 @@ // This file is part of libigl, a simple c++ geometry processing library. -// +// // Copyright (C) 2016 Alec Jacobson -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can // obtain one at http://mozilla.org/MPL/2.0/. #include "ray_box_intersect.h" -#include +#include template < typename Derivedsource, @@ -27,7 +27,7 @@ IGL_INLINE bool igl::ray_box_intersect( const Eigen::Vector3f& rayo, const Eigen::Vector3f& rayd, const Eigen::Vector3f& bmin, - const Eigen::Vector3f& bmax, + const Eigen::Vector3f& bmax, float & tnear, float & tfar )->bool @@ -35,12 +35,12 @@ IGL_INLINE bool igl::ray_box_intersect( Eigen::Vector3f bnear; Eigen::Vector3f bfar; // Checks for intersection testing on each direction coordinate - // Computes + // Computes float t1, t2; tnear = -1e+6f, tfar = 1e+6f; //, tCube; bool intersectFlag = true; for (int i = 0; i < 3; ++i) { - // std::cout << "coordinate " << i << ": bmin " << bmin(i) << ", bmax " << bmax(i) << std::endl; + // std::cout << "coordinate " << i << ": bmin " << bmin(i) << ", bmax " << bmax(i) << std::endl; assert(bmin(i) <= bmax(i)); if (::fabs(rayd(i)) < 1e-6) { // Ray parallel to axis i-th if (rayo(i) < bmin(i) || rayo(i) > bmax(i)) { @@ -59,12 +59,12 @@ IGL_INLINE bool igl::ray_box_intersect( } // std::cout << " bnear " << bnear(i) << ", bfar " << bfar(i) << std::endl; // Finds the distance parameters t1 and t2 of the two ray-box intersections: - // t1 must be the closest to the ray origin rayo. + // t1 must be the closest to the ray origin rayo. t1 = (bnear(i) - rayo(i)) / rayd(i); t2 = (bfar(i) - rayo(i)) / rayd(i); if (t1 > t2) { std::swap(t1,t2); - } + } // The two intersection values are used to saturate tnear and tfar if (t1 > tnear) { tnear = t1; @@ -72,7 +72,7 @@ IGL_INLINE bool igl::ray_box_intersect( if (t2 < tfar) { tfar = t2; } - // std::cout << " t1 " << t1 << ", t2 " << t2 << ", tnear " << tnear << ", tfar " << tfar + // std::cout << " t1 " << t1 << ", t2 " << t2 << ", tnear " << tnear << ", tfar " << tfar // << " tnear > tfar? " << (tnear > tfar) << ", tfar < 0? " << (tfar < 0) << std::endl; if(tnear > tfar) { intersectFlag = false; @@ -101,11 +101,11 @@ IGL_INLINE bool igl::ray_box_intersect( // This should be precomputed and provided as input typedef Matrix RowVector3S; const RowVector3S inv_dir( 1./dir(0),1./dir(1),1./dir(2)); - const std::vector sign = { inv_dir(0)<0, inv_dir(1)<0, inv_dir(2)<0}; + const std::array sign = { inv_dir(0)<0, inv_dir(1)<0, inv_dir(2)<0}; // http://people.csail.mit.edu/amy/papers/box-jgt.pdf // "An Efficient and Robust Ray–Box Intersection Algorithm" Scalar tymin, tymax, tzmin, tzmax; - std::vector bounds = {box.min(),box.max()}; + std::array bounds = {box.min(),box.max()}; tmin = ( bounds[sign[0]](0) - origin(0)) * inv_dir(0); tmax = ( bounds[1-sign[0]](0) - origin(0)) * inv_dir(0); tymin = (bounds[sign[1]](1) - origin(1)) * inv_dir(1); @@ -146,4 +146,4 @@ IGL_INLINE bool igl::ray_box_intersect( #ifdef IGL_STATIC_LIBRARY template bool igl::ray_box_intersect, Eigen::Matrix, double>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::AlignedBox const&, double const&, double const&, double&, double&); -#endif \ No newline at end of file +#endif diff --git a/src/igl/ray_box_intersect.h b/src/libigl/igl/ray_box_intersect.h similarity index 100% rename from src/igl/ray_box_intersect.h rename to src/libigl/igl/ray_box_intersect.h diff --git a/src/igl/ray_mesh_intersect.cpp b/src/libigl/igl/ray_mesh_intersect.cpp similarity index 98% rename from src/igl/ray_mesh_intersect.cpp rename to src/libigl/igl/ray_mesh_intersect.cpp index 512a35c461..18060fbadb 100644 --- a/src/igl/ray_mesh_intersect.cpp +++ b/src/libigl/igl/ray_mesh_intersect.cpp @@ -29,7 +29,9 @@ IGL_INLINE bool igl::ray_mesh_intersect( // Should be but can't be const Vector3d s_d = s.template cast(); Vector3d dir_d = dir.template cast(); - hits.clear(); + hits.clear(); + hits.reserve(F.rows()); + // loop over all triangles for(int f = 0;f; using Item = _Item; using Rectangle = _Rectangle; - using PackGroup = _PackGroup; -using IndexedPackGroup = _IndexedPackGroup; using FillerSelection = selections::_FillerSelection; using FirstFitSelection = selections::_FirstFitSelection; @@ -47,36 +45,34 @@ using NfpPlacer = _NfpPlacer; // This supports only box shaped bins using BottomLeftPlacer = placers::_BottomLeftPlacer; +#ifdef LIBNEST2D_STATIC + +extern template class Nester; +extern template class Nester; +extern template PackGroup Nester::execute( + std::vector::iterator, std::vector::iterator); +extern template PackGroup Nester::execute( + std::vector::iterator, std::vector::iterator); + +#endif + template::iterator> -PackGroup nest(Iterator from, Iterator to, +void nest(Iterator from, Iterator to, const typename Placer::BinType& bin, Coord dist = 0, const typename Placer::Config& pconf = {}, const typename Selector::Config& sconf = {}) { - Nester nester(bin, dist, pconf, sconf); - return nester.execute(from, to); -} - -template> -PackGroup nest(Container&& cont, - const typename Placer::BinType& bin, - Coord dist = 0, - const typename Placer::Config& pconf = {}, - const typename Selector::Config& sconf = {}) -{ - return nest(cont.begin(), cont.end(), - bin, dist, pconf, sconf); + _Nester nester(bin, dist, pconf, sconf); + nester.execute(from, to); } template::iterator> -PackGroup nest(Iterator from, Iterator to, +void nest(Iterator from, Iterator to, const typename Placer::BinType& bin, ProgressFunction prg, StopCondition scond = []() { return false; }, @@ -84,92 +80,62 @@ PackGroup nest(Iterator from, Iterator to, const typename Placer::Config& pconf = {}, const typename Selector::Config& sconf = {}) { - Nester nester(bin, dist, pconf, sconf); + _Nester nester(bin, dist, pconf, sconf); if(prg) nester.progressIndicator(prg); if(scond) nester.stopCondition(scond); - return nester.execute(from, to); -} - -template> -PackGroup nest(Container&& cont, - const typename Placer::BinType& bin, - ProgressFunction prg, - StopCondition scond = []() { return false; }, - Coord dist = 0, - const typename Placer::Config& pconf = {}, - const typename Selector::Config& sconf = {}) -{ - return nest(cont.begin(), cont.end(), - bin, prg, scond, dist, pconf, sconf); + nester.execute(from, to); } #ifdef LIBNEST2D_STATIC -extern template -PackGroup nest&>( - std::vector& cont, - const Box& bin, - Coord dist, - const NfpPlacer::Config& pcfg, - const FirstFitSelection::Config& scfg -); -extern template -PackGroup nest&>( - std::vector& cont, - const Box& bin, - ProgressFunction prg, - StopCondition scond, - Coord dist, - const NfpPlacer::Config& pcfg, - const FirstFitSelection::Config& scfg -); +extern template class Nester; +extern template class Nester; -extern template -PackGroup nest>( - std::vector&& cont, - const Box& bin, - Coord dist, - const NfpPlacer::Config& pcfg, - const FirstFitSelection::Config& scfg -); +extern template void nest(std::vector::iterator from, + std::vector::iterator to, + const Box& bin, + Coord dist = 0, + const NfpPlacer::Config& pconf, + const FirstFitSelection::Config& sconf); -extern template -PackGroup nest>( - std::vector&& cont, - const Box& bin, - ProgressFunction prg, - StopCondition scond, - Coord dist, - const NfpPlacer::Config& pcfg, - const FirstFitSelection::Config& scfg -); - -extern template -PackGroup nest::iterator>( - std::vector::iterator from, - std::vector::iterator to, - const Box& bin, - Coord dist, - const NfpPlacer::Config& pcfg, - const FirstFitSelection::Config& scfg -); - -extern template -PackGroup nest::iterator>( - std::vector::iterator from, - std::vector::iterator to, - const Box& bin, - ProgressFunction prg, - StopCondition scond, - Coord dist, - const NfpPlacer::Config& pcfg, - const FirstFitSelection::Config& scfg -); +extern template void nest(std::vector::iterator from, + std::vector::iterator to, + const Box& bin, + ProgressFunction prg, + StopCondition scond, + Coord dist = 0, + const NfpPlacer::Config& pconf, + const FirstFitSelection::Config& sconf); #endif +template> +void nest(Container&& cont, + const typename Placer::BinType& bin, + Coord dist = 0, + const typename Placer::Config& pconf = {}, + const typename Selector::Config& sconf = {}) +{ + nest(cont.begin(), cont.end(), bin, dist, pconf, sconf); +} + +template> +void nest(Container&& cont, + const typename Placer::BinType& bin, + ProgressFunction prg, + StopCondition scond = []() { return false; }, + Coord dist = 0, + const typename Placer::Config& pconf = {}, + const typename Selector::Config& sconf = {}) +{ + nest(cont.begin(), cont.end(), bin, prg, scond, dist, + pconf, sconf); +} + } #endif // LIBNEST2D_H diff --git a/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt b/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt index e20cbc70da..2020893567 100644 --- a/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt +++ b/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt @@ -33,19 +33,18 @@ if(NOT TARGET clipper) # If there is a clipper target in the parent project we a # ${clipper_library_BINARY_DIR} # ) - add_library(ClipperBackend STATIC + add_library(clipperBackend STATIC ${clipper_library_SOURCE_DIR}/clipper.cpp ${clipper_library_SOURCE_DIR}/clipper.hpp) - target_include_directories(ClipperBackend INTERFACE - ${clipper_library_SOURCE_DIR}) + target_include_directories(clipperBackend INTERFACE ${clipper_library_SOURCE_DIR}) else() message(FATAL_ERROR "Can't find clipper library and no SVN client found to download. You can download the clipper sources and define a clipper target in your project, that will work for libnest2d.") endif() else() - add_library(ClipperBackend INTERFACE) - target_link_libraries(ClipperBackend INTERFACE Clipper::Clipper) + add_library(clipperBackend INTERFACE) + target_link_libraries(clipperBackend INTERFACE Clipper::Clipper) endif() else() # set(CLIPPER_INCLUDE_DIRS "" PARENT_SCOPE) @@ -56,12 +55,12 @@ endif() # Clipper backend is not enough on its own, it still needs some functions # from Boost geometry -if(NOT Boost_INCLUDE_DIRS_FOUND) +if(NOT Boost_FOUND) find_package(Boost 1.58 REQUIRED) # TODO automatic download of boost geometry headers endif() -target_include_directories(clipperBackend SYSTEM INTERFACE ${Boost_INCLUDE_DIRS} ) +target_link_libraries(clipperBackend INTERFACE Boost::boost ) #target_sources(ClipperBackend INTERFACE # ${CMAKE_CURRENT_SOURCE_DIR}/geometries.hpp # ${CMAKE_CURRENT_SOURCE_DIR}/clipper_polygon.hpp @@ -69,6 +68,6 @@ target_include_directories(clipperBackend SYSTEM INTERFACE ${Boost_INCLUDE_DIRS} target_compile_definitions(clipperBackend INTERFACE LIBNEST2D_BACKEND_CLIPPER) -# And finally plug the ClipperBackend into libnest2d -#target_link_libraries(libnest2d INTERFACE ClipperBackend) +# And finally plug the clipperBackend into libnest2d +# target_link_libraries(libnest2d INTERFACE clipperBackend) diff --git a/src/libnest2d/include/libnest2d/backends/clipper/clipper_polygon.hpp b/src/libnest2d/include/libnest2d/backends/clipper/clipper_polygon.hpp index e9fbfbd18a..6511fbb72a 100644 --- a/src/libnest2d/include/libnest2d/backends/clipper/clipper_polygon.hpp +++ b/src/libnest2d/include/libnest2d/backends/clipper/clipper_polygon.hpp @@ -12,13 +12,13 @@ struct Polygon { inline Polygon() = default; inline explicit Polygon(const Path& cont): Contour(cont) {} - inline explicit Polygon(const Paths& holes): - Holes(holes) {} +// inline explicit Polygon(const Paths& holes): +// Holes(holes) {} inline Polygon(const Path& cont, const Paths& holes): Contour(cont), Holes(holes) {} inline explicit Polygon(Path&& cont): Contour(std::move(cont)) {} - inline explicit Polygon(Paths&& holes): Holes(std::move(holes)) {} +// inline explicit Polygon(Paths&& holes): Holes(std::move(holes)) {} inline Polygon(Path&& cont, Paths&& holes): Contour(std::move(cont)), Holes(std::move(holes)) {} }; @@ -42,7 +42,7 @@ inline IntPoint& operator -=(IntPoint& p, const IntPoint& pa ) { return p; } -inline IntPoint operator -(IntPoint& p ) { +inline IntPoint operator -(const IntPoint& p ) { IntPoint ret = p; ret.X = -ret.X; ret.Y = -ret.Y; diff --git a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp index 232668f610..56330e15e7 100644 --- a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp +++ b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp @@ -20,106 +20,59 @@ using PathImpl = ClipperLib::Path; using HoleStore = ClipperLib::Paths; using PolygonImpl = ClipperLib::Polygon; -// Type of coordinate units used by Clipper -template<> struct CoordType { - using Type = ClipperLib::cInt; -}; - -// Type of point used by Clipper -template<> struct PointType { - using Type = PointImpl; -}; - -template<> struct PointType { - using Type = PointImpl; -}; - -template<> struct PointType { - using Type = PointImpl; -}; - -template<> struct CountourType { - using Type = PathImpl; -}; - template<> struct ShapeTag { using Type = PolygonTag; }; -template<> struct ShapeTag { using Type = PathTag; }; -template<> struct ShapeTag { using Type = PointTag; }; +template<> struct ShapeTag { using Type = PathTag; }; +template<> struct ShapeTag { using Type = PointTag; }; -template<> struct ShapeTag> { - using Type = MultiPolygonTag; -}; +// Type of coordinate units used by Clipper. Enough to specialize for point, +// the rest of the types will work (Path, Polygon) +template<> struct CoordType { using Type = ClipperLib::cInt; }; -template<> struct PointType> { - using Type = PointImpl; -}; +// Enough to specialize for path, it will work for multishape and Polygon +template<> struct PointType { using Type = PointImpl; }; -template<> struct HolesContainer { - using Type = ClipperLib::Paths; -}; +// This is crucial. CountourType refers to itself by default, so we don't have +// to secialize for clipper Path. ContourType::Type is PathImpl. +template<> struct ContourType { using Type = PathImpl; }; + +// The holes are contained in Clipper::Paths +template<> struct HolesContainer { using Type = ClipperLib::Paths; }; namespace pointlike { // Tell libnest2d how to extract the X coord from a ClipperPoint object -template<> inline TCoord x(const PointImpl& p) +template<> inline ClipperLib::cInt x(const PointImpl& p) { return p.X; } // Tell libnest2d how to extract the Y coord from a ClipperPoint object -template<> inline TCoord y(const PointImpl& p) +template<> inline ClipperLib::cInt y(const PointImpl& p) { return p.Y; } // Tell libnest2d how to extract the X coord from a ClipperPoint object -template<> inline TCoord& x(PointImpl& p) +template<> inline ClipperLib::cInt& x(PointImpl& p) { return p.X; } // Tell libnest2d how to extract the Y coord from a ClipperPoint object -template<> inline TCoord& y(PointImpl& p) +template<> inline ClipperLib::cInt& y(PointImpl& p) { return p.Y; } } +// Using the libnest2d default area implementation #define DISABLE_BOOST_AREA -namespace _smartarea { - -template -inline double area(const PolygonImpl& /*sh*/) { - return std::nan(""); -} - -template<> -inline double area(const PolygonImpl& sh) { - return std::accumulate(sh.Holes.begin(), sh.Holes.end(), - ClipperLib::Area(sh.Contour), - [](double a, const ClipperLib::Path& pt){ - return a + ClipperLib::Area(pt); - }); -} - -template<> -inline double area(const PolygonImpl& sh) { - return -area(sh); -} - -} - namespace shapelike { -// Tell libnest2d how to make string out of a ClipperPolygon object -template<> inline double area(const PolygonImpl& sh, const PolygonTag&) -{ - return _smartarea::area::Value>(sh); -} - -template<> inline void offset(PolygonImpl& sh, TCoord distance) +template<> +inline void offset(PolygonImpl& sh, TCoord distance, const PolygonTag&) { #define DISABLE_BOOST_OFFSET @@ -171,6 +124,14 @@ template<> inline void offset(PolygonImpl& sh, TCoord distance) } } +template<> +inline void offset(PathImpl& sh, TCoord distance, const PathTag&) +{ + PolygonImpl p(std::move(sh)); + offset(p, distance, PolygonTag()); + sh = p.Contour; +} + // Tell libnest2d how to make string out of a ClipperPolygon object template<> inline std::string toString(const PolygonImpl& sh) { @@ -200,43 +161,16 @@ inline PolygonImpl create(const PathImpl& path, const HoleStore& holes) { PolygonImpl p; p.Contour = path; - - // Expecting that the coordinate system Y axis is positive in upwards - // direction - if(ClipperLib::Orientation(p.Contour)) { - // Not clockwise then reverse the b*tch - ClipperLib::ReversePath(p.Contour); - } - p.Holes = holes; - for(auto& h : p.Holes) { - if(!ClipperLib::Orientation(h)) { - ClipperLib::ReversePath(h); - } - } - + return p; } template<> inline PolygonImpl create( PathImpl&& path, HoleStore&& holes) { PolygonImpl p; p.Contour.swap(path); - - // Expecting that the coordinate system Y axis is positive in upwards - // direction - if(ClipperLib::Orientation(p.Contour)) { - // Not clockwise then reverse the b*tch - ClipperLib::ReversePath(p.Contour); - } - p.Holes.swap(holes); - - for(auto& h : p.Holes) { - if(!ClipperLib::Orientation(h)) { - ClipperLib::ReversePath(h); - } - } - + return p; } @@ -314,13 +248,13 @@ inline void rotate(PolygonImpl& sh, const Radians& rads) } // namespace shapelike #define DISABLE_BOOST_NFP_MERGE -inline std::vector clipper_execute( +inline TMultiShape clipper_execute( ClipperLib::Clipper& clipper, ClipperLib::ClipType clipType, ClipperLib::PolyFillType subjFillType = ClipperLib::pftEvenOdd, ClipperLib::PolyFillType clipFillType = ClipperLib::pftEvenOdd) { - shapelike::Shapes retv; + TMultiShape retv; ClipperLib::PolyTree result; clipper.Execute(clipType, result, subjFillType, clipFillType); @@ -334,10 +268,12 @@ inline std::vector clipper_execute( poly.Contour.swap(pptr->Contour); assert(!pptr->IsHole()); - - if(pptr->IsOpen()) { + + if(!poly.Contour.empty() ) { auto front_p = poly.Contour.front(); - poly.Contour.emplace_back(front_p); + auto &back_p = poly.Contour.back(); + if(front_p.X != back_p.X || front_p.Y != back_p.X) + poly.Contour.emplace_back(front_p); } for(auto h : pptr->Childs) { processHole(h, poly); } @@ -349,10 +285,12 @@ inline std::vector clipper_execute( poly.Holes.emplace_back(std::move(pptr->Contour)); assert(pptr->IsHole()); - - if(pptr->IsOpen()) { - auto front_p = poly.Holes.back().front(); - poly.Holes.back().emplace_back(front_p); + + if(!poly.Contour.empty() ) { + auto front_p = poly.Contour.front(); + auto &back_p = poly.Contour.back(); + if(front_p.X != back_p.X || front_p.Y != back_p.X) + poly.Contour.emplace_back(front_p); } for(auto c : pptr->Childs) processPoly(c); @@ -370,8 +308,8 @@ inline std::vector clipper_execute( namespace nfp { -template<> inline std::vector -merge(const std::vector& shapes) +template<> inline TMultiShape +merge(const TMultiShape& shapes) { ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution); @@ -394,10 +332,20 @@ merge(const std::vector& shapes) } +#define DISABLE_BOOST_CONVEX_HULL + //#define DISABLE_BOOST_SERIALIZE //#define DISABLE_BOOST_UNSERIALIZE +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4244) +#pragma warning(disable: 4267) +#endif // All other operators and algorithms are implemented with boost #include +#ifdef _MSC_VER +#pragma warning(pop) +#endif #endif // CLIPPER_BACKEND_HPP diff --git a/src/libnest2d/include/libnest2d/common.hpp b/src/libnest2d/include/libnest2d/common.hpp index 6867f76f3e..66e095ae27 100644 --- a/src/libnest2d/include/libnest2d/common.hpp +++ b/src/libnest2d/include/libnest2d/common.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #if defined(_MSC_VER) && _MSC_VER <= 1800 || __cplusplus < 201103L #define BP2D_NOEXCEPT @@ -197,6 +198,33 @@ public: } }; +struct ScalarTag {}; +struct BigIntTag {}; +struct RationalTag {}; + +template struct _NumTag { + using Type = + enable_if_t::value, ScalarTag>; +}; + +template using NumTag = typename _NumTag>::Type; + +/// A local version for abs that is garanteed to work with libnest2d types +template inline T abs(const T& v, ScalarTag) +{ + return std::abs(v); +} + +template inline T abs(const T& v) { return abs(v, NumTag()); } + +template inline T2 cast(const T1& v, ScalarTag, ScalarTag) +{ + return static_cast(v); +} + +template inline T2 cast(const T1& v) { + return cast(v, NumTag(), NumTag()); +} } #endif // LIBNEST2D_CONFIG_HPP diff --git a/src/libnest2d/include/libnest2d/geometry_traits.hpp b/src/libnest2d/include/libnest2d/geometry_traits.hpp index 917f5280d1..827e2d8ba3 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits.hpp @@ -7,68 +7,15 @@ #include #include #include -#include #include #include +#include -#include "common.hpp" +#include namespace libnest2d { -/// Getting the coordinate data type for a geometry class. -template struct CoordType { using Type = long; }; - -/// TCoord as shorthand for typename `CoordType::Type`. -template -using TCoord = typename CoordType>::Type; - - -/// Getting the type of point structure used by a shape. -template struct PointType { using Type = typename Sh::PointType; }; - -/// TPoint as shorthand for `typename PointType::Type`. -template -using TPoint = typename PointType>::Type; - - -template struct CountourType { using Type = RawShape; }; - -template -using TContour = typename CountourType>::Type; - - -template -struct HolesContainer { using Type = std::vector>; }; - -template -using THolesContainer = typename HolesContainer>::Type; - - -template -struct LastPointIsFirst { static const bool Value = true; }; - -enum class Orientation { - CLOCKWISE, - COUNTER_CLOCKWISE -}; - -template -struct OrientationType { - - // Default Polygon orientation that the library expects - static const Orientation Value = Orientation::CLOCKWISE; -}; - -/** - * \brief A point pair base class for other point pairs (segment, box, ...). - * \tparam RawPoint The actual point type to use. - */ -template -struct PointPair { - RawPoint p1; - RawPoint p2; -}; - +// Meta tags for different geometry concepts. struct PointTag {}; struct PolygonTag {}; struct PathTag {}; @@ -76,108 +23,244 @@ struct MultiPolygonTag {}; struct BoxTag {}; struct CircleTag {}; -/// Meta-functions to derive the tags +/// Meta-function to derive the tag of a shape type. template struct ShapeTag { using Type = typename Shape::Tag; }; + +/// Tag will be used instead of `typename ShapeTag::Type` template using Tag = typename ShapeTag>::Type; -template struct MultiShape { using Type = std::vector; }; +/// Meta function to derive the contour type for a polygon which could be itself +template struct ContourType { using Type = S; }; + +/// TContour instead of `typename ContourType::type` template -using TMultiShape =typename MultiShape>::Type; +using TContour = typename ContourType>::Type; + +/// Getting the type of point structure used by a shape. +template struct PointType { + using Type = typename PointType>::Type; +}; + +/// TPoint as shorthand for `typename PointType::Type`. +template +using TPoint = typename PointType>::Type; + +/// Getting the coordinate data type for a geometry class. +template struct CoordType { + using Type = typename CoordType>::Type; +}; + +/// TCoord as shorthand for typename `CoordType::Type`. +template +using TCoord = typename CoordType>::Type; + + +/// Getting the computation type for a certain geometry type. +/// It is the coordinate type by default but it is advised that a type with +/// larger precision and (or) range is specified. +template::value> struct ComputeType {}; + +/// A compute type is introduced to hold the results of computations on +/// coordinates and points. It should be larger in range than the coordinate +/// type or the range of coordinates should be limited to not loose precision. +template struct ComputeType { + using Type = typename ComputeType>::Type; +}; + +/// libnest2d will choose a default compute type for various coordinate types +/// if the backend has not specified anything. +template struct DoublePrecision { using Type = T; }; +template<> struct DoublePrecision { using Type = int16_t; }; +template<> struct DoublePrecision { using Type = int32_t; }; +template<> struct DoublePrecision { using Type = int64_t; }; +template<> struct DoublePrecision { using Type = double; }; +template<> struct DoublePrecision { using Type = long double; }; +template struct ComputeType { + using Type = typename DoublePrecision::Type; +}; + +/// TCompute shorthand for `typename ComputeType::Type` +template using TCompute = typename ComputeType>::Type; + +/// A meta function to derive a container type for holes in a polygon +template +struct HolesContainer { using Type = std::vector>; }; + +/// Shorthand for `typename HolesContainer::Type` +template +using THolesContainer = typename HolesContainer>::Type; + +/* + * TContour, TPoint, TCoord and TCompute should be usable for any type for which + * it makes sense. For example, the point type could be derived from the contour, + * the polygon and (or) the multishape as well. The coordinate type also and + * including the point type. TCoord, TCoord, TCoord are + * all valid types and derives the coordinate type of template argument Polygon, + * Path and Point. This is also true for TCompute, but it can also take the + * coordinate type as argument. + */ + +/* + * A Multi shape concept is also introduced. A multi shape is something that + * can contain the result of an operation where the input is one polygon and + * the result could be many polygons or path -> paths. The MultiShape should be + * a container type. If the backend does not specialize the MultiShape template, + * a default multi shape container will be used. + */ + +/// The default multi shape container. +template struct DefaultMultiShape: public std::vector { + using Tag = MultiPolygonTag; + template DefaultMultiShape(Args&&...args): + std::vector(std::forward(args)...) {} +}; + +/// The MultiShape Type trait which gets the container type for a geometry type. +template struct MultiShape { using Type = DefaultMultiShape; }; + +/// use TMultiShape instead of `typename MultiShape::Type` +template +using TMultiShape = typename MultiShape>::Type; + +// A specialization of ContourType to work with the default multishape type +template struct ContourType> { + using Type = typename ContourType::Type; +}; + +enum class Orientation { + CLOCKWISE, + COUNTER_CLOCKWISE +}; + +template +struct OrientationType { + + // Default Polygon orientation that the library expects + static const Orientation Value = Orientation::CLOCKWISE; +}; + +template inline /*constexpr*/ bool is_clockwise() { + return OrientationType>::Value == Orientation::CLOCKWISE; +} + + +/** + * \brief A point pair base class for other point pairs (segment, box, ...). + * \tparam P The actual point type to use. + */ +template +struct PointPair { + P p1; + P p2; +}; /** * \brief An abstraction of a box; */ -template -class _Box: PointPair { - using PointPair::p1; - using PointPair::p2; +template +class _Box: PointPair

{ + using PointPair

::p1; + using PointPair

::p2; public: using Tag = BoxTag; - using PointType = RawPoint; + using PointType = P; - inline _Box() = default; - inline _Box(const RawPoint& p, const RawPoint& pp): - PointPair({p, pp}) {} + inline _Box(const P& center = {TCoord

(0), TCoord

(0)}): + _Box(TCoord

(0), TCoord

(0), center) {} + + inline _Box(const P& p, const P& pp): + PointPair

({p, pp}) {} + + inline _Box(TCoord

width, TCoord

height, + const P& p = {TCoord

(0), TCoord

(0)});/*: + _Box(p, P{width, height}) {}*/ - inline _Box(TCoord width, TCoord height): - _Box(RawPoint{0, 0}, RawPoint{width, height}) {} + inline const P& minCorner() const BP2D_NOEXCEPT { return p1; } + inline const P& maxCorner() const BP2D_NOEXCEPT { return p2; } - inline const RawPoint& minCorner() const BP2D_NOEXCEPT { return p1; } - inline const RawPoint& maxCorner() const BP2D_NOEXCEPT { return p2; } + inline P& minCorner() BP2D_NOEXCEPT { return p1; } + inline P& maxCorner() BP2D_NOEXCEPT { return p2; } - inline RawPoint& minCorner() BP2D_NOEXCEPT { return p1; } - inline RawPoint& maxCorner() BP2D_NOEXCEPT { return p2; } + inline TCoord

width() const BP2D_NOEXCEPT; + inline TCoord

height() const BP2D_NOEXCEPT; - inline TCoord width() const BP2D_NOEXCEPT; - inline TCoord height() const BP2D_NOEXCEPT; + inline P center() const BP2D_NOEXCEPT; - inline RawPoint center() const BP2D_NOEXCEPT; - - inline double area() const BP2D_NOEXCEPT { - return double(width()*height()); + template> + inline Unit area() const BP2D_NOEXCEPT { + return Unit(width())*height(); } + + static inline _Box infinite(const P ¢er = {TCoord

(0), TCoord

(0)}); }; -template +template struct PointType<_Box> { + using Type = typename _Box::PointType; +}; + +template class _Circle { - RawPoint center_; + P center_; double radius_ = 0; public: using Tag = CircleTag; - using PointType = RawPoint; + using PointType = P; _Circle() = default; + _Circle(const P& center, double r): center_(center), radius_(r) {} - _Circle(const RawPoint& center, double r): center_(center), radius_(r) {} - - inline const RawPoint& center() const BP2D_NOEXCEPT { return center_; } - inline void center(const RawPoint& c) { center_ = c; } + inline const P& center() const BP2D_NOEXCEPT { return center_; } + inline void center(const P& c) { center_ = c; } inline double radius() const BP2D_NOEXCEPT { return radius_; } inline void radius(double r) { radius_ = r; } - + inline double area() const BP2D_NOEXCEPT { - return 2.0*Pi*radius_*radius_; + return Pi_2 * radius_ * radius_; } }; +template struct PointType<_Circle> { + using Type = typename _Circle::PointType; +}; + /** * \brief An abstraction of a directed line segment with two points. */ -template -class _Segment: PointPair { - using PointPair::p1; - using PointPair::p2; +template +class _Segment: PointPair

{ + using PointPair

::p1; + using PointPair

::p2; mutable Radians angletox_ = std::nan(""); public: - using PointType = RawPoint; + using PointType = P; inline _Segment() = default; - inline _Segment(const RawPoint& p, const RawPoint& pp): - PointPair({p, pp}) {} + inline _Segment(const P& p, const P& pp): + PointPair

({p, pp}) {} /** * @brief Get the first point. * @return Returns the starting point. */ - inline const RawPoint& first() const BP2D_NOEXCEPT { return p1; } + inline const P& first() const BP2D_NOEXCEPT { return p1; } /** * @brief The end point. * @return Returns the end point of the segment. */ - inline const RawPoint& second() const BP2D_NOEXCEPT { return p2; } + inline const P& second() const BP2D_NOEXCEPT { return p2; } - inline void first(const RawPoint& p) BP2D_NOEXCEPT + inline void first(const P& p) BP2D_NOEXCEPT { angletox_ = std::nan(""); p1 = p; } - inline void second(const RawPoint& p) BP2D_NOEXCEPT { + inline void second(const P& p) BP2D_NOEXCEPT { angletox_ = std::nan(""); p2 = p; } @@ -185,64 +268,92 @@ public: inline Radians angleToXaxis() const; /// The length of the segment in the measure of the coordinate system. - inline double length(); + template> inline Unit sqlength() const; + +}; + +template struct PointType<_Segment> { + using Type = typename _Circle::PointType; }; // This struct serves almost as a namespace. The only difference is that is can // used in friend declarations. namespace pointlike { -template -inline TCoord x(const RawPoint& p) +template +inline TCoord

x(const P& p) { return p.x(); } -template -inline TCoord y(const RawPoint& p) +template +inline TCoord

y(const P& p) { return p.y(); } -template -inline TCoord& x(RawPoint& p) +template +inline TCoord

& x(P& p) { return p.x(); } -template -inline TCoord& y(RawPoint& p) +template +inline TCoord

& y(P& p) { return p.y(); } -template -inline double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/) +template> +inline Unit squaredDistance(const P& p1, const P& p2) { - static_assert(always_false::value, - "PointLike::distance(point, point) unimplemented!"); - return 0; + auto x1 = Unit(x(p1)), y1 = Unit(y(p1)), x2 = Unit(x(p2)), y2 = Unit(y(p2)); + Unit a = (x2 - x1), b = (y2 - y1); + return a * a + b * b; } -template -inline double distance(const RawPoint& /*p1*/, - const _Segment& /*s*/) +template +inline double distance(const P& p1, const P& p2) { - static_assert(always_false::value, - "PointLike::distance(point, segment) unimplemented!"); - return 0; + return std::sqrt(squaredDistance(p1, p2)); } -template -inline std::pair, bool> horizontalDistance( - const RawPoint& p, const _Segment& s) -{ - using Unit = TCoord; - auto x = pointlike::x(p), y = pointlike::y(p); - auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first()); - auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second()); +// create perpendicular vector +template inline Pt perp(const Pt& p) +{ + return Pt(y(p), -x(p)); +} - TCoord ret; +template> +inline Unit dotperp(const Pt& a, const Pt& b) +{ + return Unit(x(a)) * Unit(y(b)) - Unit(y(a)) * Unit(x(b)); +} + +// dot product +template> +inline Unit dot(const Pt& a, const Pt& b) +{ + return Unit(x(a)) * x(b) + Unit(y(a)) * y(b); +} + +// squared vector magnitude +template> +inline Unit magnsq(const Pt& p) +{ + return Unit(x(p)) * x(p) + Unit(y(p)) * y(p); +} + +template> +inline std::pair horizontalDistance( + const P& p, const _Segment

& s) +{ + namespace pl = pointlike; + auto x = Unit(pl::x(p)), y = Unit(pl::y(p)); + auto x1 = Unit(pl::x(s.first())), y1 = Unit(pl::y(s.first())); + auto x2 = Unit(pl::x(s.second())), y2 = Unit(pl::y(s.second())); + + Unit ret; if( (y < y1 && y < y2) || (y > y1 && y > y2) ) return {0, false}; @@ -250,8 +361,7 @@ inline std::pair, bool> horizontalDistance( ret = std::min( x-x1, x -x2); else if( (y == y1 && y == y2) && (x < x1 && x < x2)) ret = -std::min(x1 - x, x2 - x); - else if(std::abs(y - y1) <= std::numeric_limits::epsilon() && - std::abs(y - y2) <= std::numeric_limits::epsilon()) + else if(y == y1 && y == y2) ret = 0; else ret = x - x1 + (x1 - x2)*(y1 - y)/(y1 - y2); @@ -259,16 +369,16 @@ inline std::pair, bool> horizontalDistance( return {ret, true}; } -template -inline std::pair, bool> verticalDistance( - const RawPoint& p, const _Segment& s) +template> +inline std::pair verticalDistance( + const P& p, const _Segment

& s) { - using Unit = TCoord; - auto x = pointlike::x(p), y = pointlike::y(p); - auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first()); - auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second()); + namespace pl = pointlike; + auto x = Unit(pl::x(p)), y = Unit(pl::y(p)); + auto x1 = Unit(pl::x(s.first())), y1 = Unit(pl::y(s.first())); + auto x2 = Unit(pl::x(s.second())), y2 = Unit(pl::y(s.second())); - TCoord ret; + Unit ret; if( (x < x1 && x < x2) || (x > x1 && x > x2) ) return {0, false}; @@ -276,8 +386,7 @@ inline std::pair, bool> verticalDistance( ret = std::min( y-y1, y -y2); else if( (x == x1 && x == x2) && (y < y1 && y < y2)) ret = -std::min(y1 - y, y2 - y); - else if(std::abs(x - x1) <= std::numeric_limits::epsilon() && - std::abs(x - x2) <= std::numeric_limits::epsilon()) + else if(x == x1 && x == x2) ret = 0; else ret = y - y1 + (y1 - y2)*(x1 - x)/(x1 - x2); @@ -286,42 +395,42 @@ inline std::pair, bool> verticalDistance( } } -template -TCoord _Box::width() const BP2D_NOEXCEPT +template +TCoord

_Box

::width() const BP2D_NOEXCEPT { return pointlike::x(maxCorner()) - pointlike::x(minCorner()); } -template -TCoord _Box::height() const BP2D_NOEXCEPT +template +TCoord

_Box

::height() const BP2D_NOEXCEPT { return pointlike::y(maxCorner()) - pointlike::y(minCorner()); } -template -TCoord getX(const RawPoint& p) { return pointlike::x(p); } +template +TCoord

getX(const P& p) { return pointlike::x

(p); } -template -TCoord getY(const RawPoint& p) { return pointlike::y(p); } +template +TCoord

getY(const P& p) { return pointlike::y

(p); } -template -void setX(RawPoint& p, const TCoord& val) +template +void setX(P& p, const TCoord

& val) { - pointlike::x(p) = val; + pointlike::x

(p) = val; } -template -void setY(RawPoint& p, const TCoord& val) +template +void setY(P& p, const TCoord

& val) { - pointlike::y(p) = val; + pointlike::y

(p) = val; } -template -inline Radians _Segment::angleToXaxis() const +template +inline Radians _Segment

::angleToXaxis() const { if(std::isnan(static_cast(angletox_))) { - TCoord dx = getX(second()) - getX(first()); - TCoord dy = getY(second()) - getY(first()); + TCoord

dx = getX(second()) - getX(first()); + TCoord

dy = getY(second()) - getY(first()); double a = std::atan2(dy, dx); auto s = std::signbit(a); @@ -332,22 +441,57 @@ inline Radians _Segment::angleToXaxis() const return angletox_; } -template -inline double _Segment::length() +template +template +inline Unit _Segment

::sqlength() const { - return pointlike::distance(first(), second()); + return pointlike::squaredDistance(first(), second()); } -template -inline RawPoint _Box::center() const BP2D_NOEXCEPT { +template +enable_if_t::value, T> modulo(const T &v, const T &m) +{ + return 0; +} +template +enable_if_t::value, T> modulo(const T &v, const T &m) +{ + return v % m; +} + +template +inline _Box

::_Box(TCoord

width, TCoord

height, const P & center) : + PointPair

({center - P{width / 2, height / 2}, + center + P{width / 2, height / 2} + + P{modulo(width, TCoord

(2)), + modulo(height, TCoord

(2))}}) {} + +template +inline _Box

_Box

::infinite(const P& center) { + using C = TCoord

; + _Box

ret; + + // It is important for Mx and My to be strictly less than half of the + // range of type C. width(), height() and area() will not overflow this way. + C Mx = C((std::numeric_limits::lowest() + 2 * getX(center)) / 2.01); + C My = C((std::numeric_limits::lowest() + 2 * getY(center)) / 2.01); + + ret.maxCorner() = center - P{Mx, My}; + ret.minCorner() = center + P{Mx, My}; + + return ret; +} + +template +inline P _Box

::center() const BP2D_NOEXCEPT { auto& minc = minCorner(); auto& maxc = maxCorner(); - using Coord = TCoord; + using Coord = TCoord

; - RawPoint ret = { // No rounding here, we dont know if these are int coords - static_cast( (getX(minc) + getX(maxc))/2.0 ), - static_cast( (getY(minc) + getY(maxc))/2.0 ) + P ret = { // No rounding here, we dont know if these are int coords + Coord( (getX(minc) + getX(maxc)) / Coord(2) ), + Coord( (getY(minc) + getY(maxc)) / Coord(2) ) }; return ret; @@ -362,79 +506,74 @@ enum class Formats { // used in friend declarations and can be aliased at class scope. namespace shapelike { -template -using Shapes = TMultiShape; - -template -inline RawShape create(const TContour& contour, - const THolesContainer& holes) +template +inline S create(const TContour& contour, const THolesContainer& holes) { - return RawShape(contour, holes); + return S(contour, holes); } -template -inline RawShape create(TContour&& contour, - THolesContainer&& holes) +template +inline S create(TContour&& contour, THolesContainer&& holes) { - return RawShape(contour, holes); + return S(contour, holes); } -template -inline RawShape create(const TContour& contour) +template +inline S create(const TContour& contour) { - return create(contour, {}); + return create(contour, {}); } -template -inline RawShape create(TContour&& contour) +template +inline S create(TContour&& contour) { - return create(contour, {}); + return create(contour, {}); } -template -inline THolesContainer& holes(RawShape& /*sh*/) +template +inline THolesContainer& holes(S& /*sh*/) { - static THolesContainer empty; + static THolesContainer empty; return empty; } -template -inline const THolesContainer& holes(const RawShape& /*sh*/) +template +inline const THolesContainer& holes(const S& /*sh*/) { - static THolesContainer empty; + static THolesContainer empty; return empty; } -template -inline TContour& hole(RawShape& sh, unsigned long idx) +template +inline TContour& hole(S& sh, unsigned long idx) { return holes(sh)[idx]; } -template -inline const TContour& hole(const RawShape& sh, unsigned long idx) +template +inline const TContour& hole(const S& sh, unsigned long idx) { return holes(sh)[idx]; } -template -inline size_t holeCount(const RawShape& sh) +template +inline size_t holeCount(const S& sh) { return holes(sh).size(); } -template -inline TContour& contour(RawShape& sh) +template +inline TContour& contour(S& sh) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::contour() unimplemented!"); return sh; } -template -inline const TContour& contour(const RawShape& sh) +template +inline const TContour& contour(const S& sh) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::contour() unimplemented!"); return sh; } @@ -446,76 +585,71 @@ inline void reserve(RawPath& p, size_t vertex_capacity, const PathTag&) p.reserve(vertex_capacity); } -template -inline void addVertex(RawShape& sh, const PathTag&, Args...args) +template +inline void addVertex(S& sh, const PathTag&, Args...args) { - return sh.emplace_back(std::forward(args)...); + sh.emplace_back(std::forward(args)...); } -template -inline void foreachVertex(RawShape& sh, Fn fn, const PathTag&) { +template +inline void foreachVertex(S& sh, Fn fn, const PathTag&) { std::for_each(sh.begin(), sh.end(), fn); } -template -inline typename RawShape::iterator begin(RawShape& sh, const PathTag&) +template +inline typename S::iterator begin(S& sh, const PathTag&) { return sh.begin(); } -template -inline typename RawShape::iterator end(RawShape& sh, const PathTag&) +template +inline typename S::iterator end(S& sh, const PathTag&) { return sh.end(); } -template -inline typename RawShape::const_iterator -cbegin(const RawShape& sh, const PathTag&) +template +inline typename S::const_iterator +cbegin(const S& sh, const PathTag&) { return sh.cbegin(); } -template -inline typename RawShape::const_iterator -cend(const RawShape& sh, const PathTag&) +template +inline typename S::const_iterator +cend(const S& sh, const PathTag&) { return sh.cend(); } -template -inline std::string toString(const RawShape& /*sh*/) +template +inline std::string toString(const S& /*sh*/) { return ""; } -template -inline std::string serialize(const RawShape& /*sh*/, double /*scale*/=1) +template +inline std::string serialize(const S& /*sh*/, double /*scale*/=1) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::serialize() unimplemented!"); return ""; } -template -inline void unserialize(RawShape& /*sh*/, const std::string& /*str*/) +template +inline void unserialize(S& /*sh*/, const std::string& /*str*/) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::unserialize() unimplemented!"); } -template -inline double area(const RawShape& /*sh*/, const PolygonTag&) -{ - static_assert(always_false::value, - "shapelike::area() unimplemented!"); - return 0; -} +template +inline Unit area(const Cntr& poly, const PathTag& ); -template -inline bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/) +template +inline bool intersects(const S& /*sh*/, const S& /*sh*/) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::intersects() unimplemented!"); return false; } @@ -536,79 +670,75 @@ inline bool isInside(const TGuest&, const THost&, return false; } -template -inline bool touches( const RawShape& /*shape*/, - const RawShape& /*shape*/) +template +inline bool touches( const S& /*shape*/, + const S& /*shape*/) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::touches(shape, shape) unimplemented!"); return false; } -template -inline bool touches( const TPoint& /*point*/, - const RawShape& /*shape*/) +template +inline bool touches( const TPoint& /*point*/, + const S& /*shape*/) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::touches(point, shape) unimplemented!"); return false; } -template -inline _Box> boundingBox(const RawShape& /*sh*/, - const PolygonTag&) +template +inline _Box> boundingBox(const S& /*sh*/, + const PathTag&) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::boundingBox(shape) unimplemented!"); } template -inline _Box> +inline _Box> boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&) { static_assert(always_false::value, "shapelike::boundingBox(shapes) unimplemented!"); } -template -inline RawShape convexHull(const RawShape& /*sh*/, const PolygonTag&) -{ - static_assert(always_false::value, - "shapelike::convexHull(shape) unimplemented!"); - return RawShape(); -} +template +inline S convexHull(const S& sh, const PathTag&); -template -inline typename RawShapes::value_type -convexHull(const RawShapes& /*sh*/, const MultiPolygonTag&) -{ - static_assert(always_false::value, - "shapelike::convexHull(shapes) unimplemented!"); - return typename RawShapes::value_type(); -} +template +inline S convexHull(const RawShapes& sh, const MultiPolygonTag&); -template -inline void rotate(RawShape& /*sh*/, const Radians& /*rads*/) +template +inline void rotate(S& /*sh*/, const Radians& /*rads*/) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::rotate() unimplemented!"); } -template -inline void translate(RawShape& /*sh*/, const RawPoint& /*offs*/) +template +inline void translate(S& /*sh*/, const P& /*offs*/) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::translate() unimplemented!"); } -template -inline void offset(RawShape& /*sh*/, TCoord> /*distance*/) +template +inline void offset(S& /*sh*/, TCoord /*distance*/, const PathTag&) { dout() << "The current geometry backend does not support offsetting!\n"; } -template -inline std::pair isValid(const RawShape& /*sh*/) +template +inline void offset(S& sh, TCoord distance, const PolygonTag&) +{ + offset(contour(sh), distance); + for(auto &h : holes(sh)) offset(h, -distance); +} + +template +inline std::pair isValid(const S& /*sh*/) { return {false, "shapelike::isValid() unimplemented!"}; } @@ -649,56 +779,56 @@ template inline bool isConvex(const RawPath& sh, const PathTag&) // No need to implement these // ***************************************************************************** -template -inline typename TContour::iterator -begin(RawShape& sh, const PolygonTag&) +template +inline typename TContour::iterator +begin(S& sh, const PolygonTag&) { return begin(contour(sh), PathTag()); } -template // Tag dispatcher -inline auto begin(RawShape& sh) -> decltype(begin(sh, Tag())) +template // Tag dispatcher +inline auto begin(S& sh) -> decltype(begin(sh, Tag())) { - return begin(sh, Tag()); + return begin(sh, Tag()); } -template -inline typename TContour::const_iterator -cbegin(const RawShape& sh, const PolygonTag&) +template +inline typename TContour::const_iterator +cbegin(const S& sh, const PolygonTag&) { return cbegin(contour(sh), PathTag()); } -template // Tag dispatcher -inline auto cbegin(const RawShape& sh) -> decltype(cbegin(sh, Tag())) +template // Tag dispatcher +inline auto cbegin(const S& sh) -> decltype(cbegin(sh, Tag())) { - return cbegin(sh, Tag()); + return cbegin(sh, Tag()); } -template -inline typename TContour::iterator -end(RawShape& sh, const PolygonTag&) +template +inline typename TContour::iterator +end(S& sh, const PolygonTag&) { return end(contour(sh), PathTag()); } -template // Tag dispatcher -inline auto end(RawShape& sh) -> decltype(begin(sh, Tag())) +template // Tag dispatcher +inline auto end(S& sh) -> decltype(begin(sh, Tag())) { - return end(sh, Tag()); + return end(sh, Tag()); } -template -inline typename TContour::const_iterator -cend(const RawShape& sh, const PolygonTag&) +template +inline typename TContour::const_iterator +cend(const S& sh, const PolygonTag&) { return cend(contour(sh), PathTag()); } -template // Tag dispatcher -inline auto cend(const RawShape& sh) -> decltype(cend(sh, Tag())) +template // Tag dispatcher +inline auto cend(const S& sh) -> decltype(cend(sh, Tag())) { - return cend(sh, Tag()); + return cend(sh, Tag()); } template std::reverse_iterator _backward(It iter) { @@ -731,8 +861,8 @@ template TPoint

back (const P& p) { } // Optional, does nothing by default -template -inline void reserve(RawShape& sh, size_t vertex_capacity, const PolygonTag&) +template +inline void reserve(S& sh, size_t vertex_capacity, const PolygonTag&) { reserve(contour(sh), vertex_capacity, PathTag()); } @@ -742,16 +872,22 @@ inline void reserve(T& sh, size_t vertex_capacity) { reserve(sh, vertex_capacity, Tag()); } -template -inline void addVertex(RawShape& sh, const PolygonTag&, Args...args) +template +inline void addVertex(S& sh, const PolygonTag&, Args...args) { - return addVertex(contour(sh), PathTag(), std::forward(args)...); + addVertex(contour(sh), PathTag(), std::forward(args)...); } -template // Tag dispatcher -inline void addVertex(RawShape& sh, Args...args) +template // Tag dispatcher +inline void addVertex(S& sh, Args...args) { - return addVertex(sh, Tag(), std::forward(args)...); + addVertex(sh, Tag(), std::forward(args)...); +} + +template +inline _Box> boundingBox(const S& poly, const PolygonTag&) +{ + return boundingBox(contour(poly), PathTag()); } template @@ -783,10 +919,32 @@ inline _Box> boundingBox(const S& sh) return boundingBox(sh, Tag() ); } +template _Box

boundingBox(const _Box

& bb1, const _Box

& bb2 ) +{ + auto& pminc = bb1.minCorner(); + auto& pmaxc = bb1.maxCorner(); + auto& iminc = bb2.minCorner(); + auto& imaxc = bb2.maxCorner(); + P minc, maxc; + + setX(minc, std::min(getX(pminc), getX(iminc))); + setY(minc, std::min(getY(pminc), getY(iminc))); + + setX(maxc, std::max(getX(pmaxc), getX(imaxc))); + setY(maxc, std::max(getY(pmaxc), getY(imaxc))); + return _Box

(minc, maxc); +} + +template +_Box> boundingBox(const S1 &s1, const S2 &s2) +{ + return boundingBox(boundingBox(s1), boundingBox(s2)); +} + template inline double area(const Box& box, const BoxTag& ) { - return box.area(); + return box.template area(); } template @@ -795,34 +953,149 @@ inline double area(const Circle& circ, const CircleTag& ) return circ.area(); } -template // Dispatching function -inline double area(const RawShape& sh) +template +inline Unit area(const Cntr& poly, const PathTag& ) { - return area(sh, Tag()); + namespace sl = shapelike; + if (sl::cend(poly) - sl::cbegin(poly) < 3) return 0.0; + + Unit a = 0; + for (auto i = sl::cbegin(poly), j = std::prev(sl::cend(poly)); + i < sl::cend(poly); ++i) + { + auto xj = Unit(getX(*j)), yj = Unit(getY(*j)); + auto xi = Unit(getX(*i)), yi = Unit(getY(*i)); + a += (xj + xi) * (yj - yi); + j = i; + } + a /= 2; + return is_clockwise() ? a : -a; +} + +template inline double area(const S& poly, const PolygonTag& ) +{ + auto hls = holes(poly); + return std::accumulate(hls.begin(), hls.end(), + area(contour(poly), PathTag()), + [](double a, const TContour &h){ + return a + area(h, PathTag()); + }); +} + +template // Dispatching function +inline double area(const S& sh) +{ + return area(sh, Tag()); } template inline double area(const RawShapes& shapes, const MultiPolygonTag&) { - using RawShape = typename RawShapes::value_type; + using S = typename RawShapes::value_type; return std::accumulate(shapes.begin(), shapes.end(), 0.0, - [](double a, const RawShape& b) { + [](double a, const S& b) { return a += area(b); }); } -template -inline auto convexHull(const RawShape& sh) - -> decltype(convexHull(sh, Tag())) // TODO: C++14 could deduce +template +inline S convexHull(const S& sh, const PolygonTag&) { - return convexHull(sh, Tag()); + return create(convexHull(contour(sh), PathTag())); +} + +template +inline auto convexHull(const S& sh) + -> decltype(convexHull(sh, Tag())) // TODO: C++14 could deduce +{ + return convexHull(sh, Tag()); +} + +template +inline S convexHull(const S& sh, const PathTag&) +{ + using Unit = TCompute; + using Point = TPoint; + namespace sl = shapelike; + + size_t edges = sl::cend(sh) - sl::cbegin(sh); + if(edges <= 3) return {}; + + bool closed = false; + std::vector U, L; + U.reserve(1 + edges / 2); L.reserve(1 + edges / 2); + + std::vector pts; pts.reserve(edges); + std::copy(sl::cbegin(sh), sl::cend(sh), std::back_inserter(pts)); + + auto fpt = pts.front(), lpt = pts.back(); + if(getX(fpt) == getX(lpt) && getY(fpt) == getY(lpt)) { + closed = true; pts.pop_back(); + } + + std::sort(pts.begin(), pts.end(), + [](const Point& v1, const Point& v2) + { + Unit x1 = getX(v1), x2 = getX(v2), y1 = getY(v1), y2 = getY(v2); + return x1 == x2 ? y1 < y2 : x1 < x2; + }); + + auto dir = [](const Point& p, const Point& q, const Point& r) { + return (Unit(getY(q)) - getY(p)) * (Unit(getX(r)) - getX(p)) - + (Unit(getX(q)) - getX(p)) * (Unit(getY(r)) - getY(p)); + }; + + auto ik = pts.begin(); + + while(ik != pts.end()) { + + while(U.size() > 1 && dir(U[U.size() - 2], U.back(), *ik) <= 0) + U.pop_back(); + while(L.size() > 1 && dir(L[L.size() - 2], L.back(), *ik) >= 0) + L.pop_back(); + + U.emplace_back(*ik); + L.emplace_back(*ik); + + ++ik; + } + + S ret; reserve(ret, U.size() + L.size()); + if(is_clockwise()) { + for(auto it = U.begin(); it != std::prev(U.end()); ++it) + addVertex(ret, *it); + for(auto it = L.rbegin(); it != std::prev(L.rend()); ++it) + addVertex(ret, *it); + if(closed) addVertex(ret, *std::prev(L.rend())); + } else { + for(auto it = L.begin(); it != std::prev(L.end()); ++it) + addVertex(ret, *it); + for(auto it = U.rbegin(); it != std::prev(U.rend()); ++it) + addVertex(ret, *it); + if(closed) addVertex(ret, *std::prev(U.rend())); + } + + return ret; +} + +template +inline S convexHull(const RawShapes& sh, const MultiPolygonTag&) +{ + namespace sl = shapelike; + S cntr; + for(auto& poly : sh) + for(auto it = sl::cbegin(poly); it != sl::cend(poly); ++it) + addVertex(cntr, *it); + + return convexHull(cntr, Tag()); } template inline bool isInside(const TP& point, const TC& circ, const PointTag&, const CircleTag&) { - return pointlike::distance(point, circ.center()) < circ.radius(); + auto r = circ.radius(); + return pointlike::squaredDistance(point, circ.center()) < r * r; } template @@ -839,11 +1112,11 @@ inline bool isInside(const TP& point, const TB& box, return px > minx && px < maxx && py > miny && py < maxy; } -template -inline bool isInside(const RawShape& sh, const TC& circ, +template +inline bool isInside(const S& sh, const TC& circ, const PolygonTag&, const CircleTag&) { - return std::all_of(cbegin(sh), cend(sh), [&circ](const TPoint& p) + return std::all_of(cbegin(sh), cend(sh), [&circ](const TPoint& p) { return isInside(p, circ, PointTag(), CircleTag()); }); @@ -853,8 +1126,8 @@ template inline bool isInside(const TB& box, const TC& circ, const BoxTag&, const CircleTag&) { - return isInside(box.minCorner(), circ, BoxTag(), CircleTag()) && - isInside(box.maxCorner(), circ, BoxTag(), CircleTag()); + return isInside(box.minCorner(), circ, PointTag(), CircleTag()) && + isInside(box.maxCorner(), circ, PointTag(), CircleTag()); } template @@ -874,8 +1147,8 @@ inline bool isInside(const TBGuest& ibb, const TBHost& box, return iminX > minX && imaxX < maxX && iminY > minY && imaxY < maxY; } -template -inline bool isInside(const RawShape& poly, const TB& box, +template +inline bool isInside(const S& poly, const TB& box, const PolygonTag&, const BoxTag&) { return isInside(boundingBox(poly), box, BoxTag(), BoxTag()); @@ -886,36 +1159,36 @@ inline bool isInside(const TGuest& guest, const THost& host) { return isInside(guest, host, Tag(), Tag()); } -template // Potential O(1) implementation may exist -inline TPoint& vertex(RawShape& sh, unsigned long idx, +template // Potential O(1) implementation may exist +inline TPoint& vertex(S& sh, unsigned long idx, const PolygonTag&) { return *(shapelike::begin(contour(sh)) + idx); } -template // Potential O(1) implementation may exist -inline TPoint& vertex(RawShape& sh, unsigned long idx, +template // Potential O(1) implementation may exist +inline TPoint& vertex(S& sh, unsigned long idx, const PathTag&) { return *(shapelike::begin(sh) + idx); } -template // Potential O(1) implementation may exist -inline TPoint& vertex(RawShape& sh, unsigned long idx) +template // Potential O(1) implementation may exist +inline TPoint& vertex(S& sh, unsigned long idx) { - return vertex(sh, idx, Tag()); + return vertex(sh, idx, Tag()); } -template // Potential O(1) implementation may exist -inline const TPoint& vertex(const RawShape& sh, +template // Potential O(1) implementation may exist +inline const TPoint& vertex(const S& sh, unsigned long idx, const PolygonTag&) { return *(shapelike::cbegin(contour(sh)) + idx); } -template // Potential O(1) implementation may exist -inline const TPoint& vertex(const RawShape& sh, +template // Potential O(1) implementation may exist +inline const TPoint& vertex(const S& sh, unsigned long idx, const PathTag&) { @@ -923,28 +1196,28 @@ inline const TPoint& vertex(const RawShape& sh, } -template // Potential O(1) implementation may exist -inline const TPoint& vertex(const RawShape& sh, +template // Potential O(1) implementation may exist +inline const TPoint& vertex(const S& sh, unsigned long idx) { - return vertex(sh, idx, Tag()); + return vertex(sh, idx, Tag()); } -template -inline size_t contourVertexCount(const RawShape& sh) +template +inline size_t contourVertexCount(const S& sh) { return shapelike::cend(sh) - shapelike::cbegin(sh); } -template -inline void foreachVertex(RawShape& sh, Fn fn, const PolygonTag&) { +template +inline void foreachVertex(S& sh, Fn fn, const PolygonTag&) { foreachVertex(contour(sh), fn, PathTag()); for(auto& h : holes(sh)) foreachVertex(h, fn, PathTag()); } -template -inline void foreachVertex(RawShape& sh, Fn fn) { - foreachVertex(sh, fn, Tag()); +template +inline void foreachVertex(S& sh, Fn fn) { + foreachVertex(sh, fn, Tag()); } template inline bool isConvex(const Poly& sh, const PolygonTag&) @@ -955,9 +1228,26 @@ template inline bool isConvex(const Poly& sh, const PolygonTag&) return convex; } -template inline bool isConvex(const RawShape& sh) // dispatch +template inline bool isConvex(const S& sh) // dispatch { - return isConvex(sh, Tag()); + return isConvex(sh, Tag()); +} + +template inline void offset(Box& bb, TCoord d, const BoxTag&) +{ + TPoint md{d, d}; + bb.minCorner() -= md; + bb.maxCorner() += md; +} + +template inline void offset(C& circ, TCoord d, const CircleTag&) +{ + circ.radius(circ.radius() + double(d)); +} + +// Dispatch function +template inline void offset(S& sh, TCoord d) { + offset(sh, d, Tag()); } } @@ -972,6 +1262,9 @@ template inline bool isConvex(const RawShape& sh) // dispatch using Segment = _Segment; \ using Polygons = TMultiShape +namespace sl = shapelike; +namespace pl = pointlike; + } #endif // GEOMETRY_TRAITS_HPP diff --git a/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp b/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp index cb0580ef4f..4a2c69bca2 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp @@ -1,26 +1,22 @@ #ifndef GEOMETRIES_NOFITPOLYGON_HPP #define GEOMETRIES_NOFITPOLYGON_HPP -#include "geometry_traits.hpp" #include #include #include #include +#include + namespace libnest2d { namespace __nfp { // Do not specialize this... -template +template> inline bool _vsort(const TPoint& v1, const TPoint& v2) { - using Coord = TCoord>; - Coord &&x1 = getX(v1), &&x2 = getX(v2), &&y1 = getY(v1), &&y2 = getY(v2); - auto diff = y1 - y2; - if(std::abs(diff) <= std::numeric_limits::epsilon()) - return x1 < x2; - - return diff < 0; + Unit x1 = getX(v1), x2 = getX(v2), y1 = getY(v1), y2 = getY(v2); + return y1 == y2 ? x1 < x2 : y1 < y2; } template> @@ -202,7 +198,7 @@ inline TPoint referenceVertex(const RawShape& sh) * convex as well in this case. * */ -template +template inline NfpResult nfpConvexOnly(const RawShape& sh, const RawShape& other) { @@ -238,12 +234,62 @@ inline NfpResult nfpConvexOnly(const RawShape& sh, ++first; ++next; } } - - // Sort the edges by angle to X axis. - std::sort(edgelist.begin(), edgelist.end(), - [](const Edge& e1, const Edge& e2) + + std::sort(edgelist.begin(), edgelist.end(), + [](const Edge& e1, const Edge& e2) { - return e1.angleToXaxis() > e2.angleToXaxis(); + Vertex ax(1, 0); // Unit vector for the X axis + + // get cectors from the edges + Vertex p1 = e1.second() - e1.first(); + Vertex p2 = e2.second() - e2.first(); + + // Quadrant mapping array. The quadrant of a vector can be determined + // from the dot product of the vector and its perpendicular pair + // with the unit vector X axis. The products will carry the values + // lcos = dot(p, ax) = l * cos(phi) and + // lsin = -dotperp(p, ax) = l * sin(phi) where + // l is the length of vector p. From the signs of these values we can + // construct an index which has the sign of lcos as MSB and the + // sign of lsin as LSB. This index can be used to retrieve the actual + // quadrant where vector p resides using the following map: + // (+ is 0, - is 1) + // cos | sin | decimal | quadrant + // + | + | 0 | 0 + // + | - | 1 | 3 + // - | + | 2 | 1 + // - | - | 3 | 2 + std::array quadrants {0, 3, 1, 2 }; + + std::array q {0, 0}; // Quadrant indices for p1 and p2 + + using TDots = std::array, 2>; + TDots lcos { pl::dot(p1, ax), pl::dot(p2, ax) }; + TDots lsin { -pl::dotperp(p1, ax), -pl::dotperp(p2, ax) }; + + // Construct the quadrant indices for p1 and p2 + for(size_t i = 0; i < 2; ++i) + if(lcos[i] == 0) q[i] = lsin[i] > 0 ? 1 : 3; + else if(lsin[i] == 0) q[i] = lcos[i] > 0 ? 0 : 2; + else q[i] = quadrants[((lcos[i] < 0) << 1) + (lsin[i] < 0)]; + + if(q[0] == q[1]) { // only bother if p1 and p2 are in the same quadrant + auto lsq1 = pl::magnsq(p1); // squared magnitudes, avoid sqrt + auto lsq2 = pl::magnsq(p2); // squared magnitudes, avoid sqrt + + // We will actually compare l^2 * cos^2(phi) which saturates the + // cos function. But with the quadrant info we can get the sign back + int sign = q[0] == 1 || q[0] == 2 ? -1 : 1; + + // If Ratio is an actual rational type, there is no precision loss + auto pcos1 = Ratio(lcos[0]) / lsq1 * sign * lcos[0]; + auto pcos2 = Ratio(lcos[1]) / lsq2 * sign * lcos[1]; + + return q[0] < 2 ? pcos1 < pcos2 : pcos1 > pcos2; + } + + // If in different quadrants, compare the quadrant indices only. + return q[0] > q[1]; }); __nfp::buildPolygon(edgelist, rsh, top_nfp); @@ -253,456 +299,9 @@ inline NfpResult nfpConvexOnly(const RawShape& sh, template NfpResult nfpSimpleSimple(const RawShape& cstationary, - const RawShape& cother) + const RawShape& cother) { - - // Algorithms are from the original algorithm proposed in paper: - // https://eprints.soton.ac.uk/36850/1/CORMSIS-05-05.pdf - - // ///////////////////////////////////////////////////////////////////////// - // Algorithm 1: Obtaining the minkowski sum - // ///////////////////////////////////////////////////////////////////////// - - // I guess this is not a full minkowski sum of the two input polygons by - // definition. This yields a subset that is compatible with the next 2 - // algorithms. - - using Result = NfpResult; - using Vertex = TPoint; - using Coord = TCoord; - using Edge = _Segment; - namespace sl = shapelike; - using std::signbit; - using std::sort; - using std::vector; - using std::ref; - using std::reference_wrapper; - - // TODO The original algorithms expects the stationary polygon in - // counter clockwise and the orbiter in clockwise order. - // So for preventing any further complication, I will make the input - // the way it should be, than make my way around the orientations. - - // Reverse the stationary contour to counter clockwise - auto stcont = sl::contour(cstationary); - { - std::reverse(sl::begin(stcont), sl::end(stcont)); - stcont.pop_back(); - auto it = std::min_element(sl::begin(stcont), sl::end(stcont), - [](const Vertex& v1, const Vertex& v2) { - return getY(v1) < getY(v2); - }); - std::rotate(sl::begin(stcont), it, sl::end(stcont)); - sl::addVertex(stcont, sl::front(stcont)); - } - RawShape stationary; - sl::contour(stationary) = stcont; - - // Reverse the orbiter contour to counter clockwise - auto orbcont = sl::contour(cother); - { - std::reverse(orbcont.begin(), orbcont.end()); - - // Step 1: Make the orbiter reverse oriented - - orbcont.pop_back(); - auto it = std::min_element(orbcont.begin(), orbcont.end(), - [](const Vertex& v1, const Vertex& v2) { - return getY(v1) < getY(v2); - }); - - std::rotate(orbcont.begin(), it, orbcont.end()); - orbcont.emplace_back(orbcont.front()); - - for(auto &v : orbcont) v = -v; - - } - - // Copy the orbiter (contour only), we will have to work on it - RawShape orbiter; - sl::contour(orbiter) = orbcont; - - // An edge with additional data for marking it - struct MarkedEdge { - Edge e; Radians turn_angle = 0; bool is_turning_point = false; - MarkedEdge() = default; - MarkedEdge(const Edge& ed, Radians ta, bool tp): - e(ed), turn_angle(ta), is_turning_point(tp) {} - - // debug - std::string label; - }; - - // Container for marked edges - using EdgeList = vector; - - EdgeList A, B; - - // This is how an edge list is created from the polygons - auto fillEdgeList = [](EdgeList& L, const RawShape& ppoly, int dir) { - auto& poly = sl::contour(ppoly); - - L.reserve(sl::contourVertexCount(poly)); - - if(dir > 0) { - auto it = poly.begin(); - auto nextit = std::next(it); - - double turn_angle = 0; - bool is_turn_point = false; - - while(nextit != poly.end()) { - L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point); - it++; nextit++; - } - } else { - auto it = sl::rbegin(poly); - auto nextit = std::next(it); - - double turn_angle = 0; - bool is_turn_point = false; - - while(nextit != sl::rend(poly)) { - L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point); - it++; nextit++; - } - } - - auto getTurnAngle = [](const Edge& e1, const Edge& e2) { - auto phi = e1.angleToXaxis(); - auto phi_prev = e2.angleToXaxis(); - auto turn_angle = phi-phi_prev; - if(turn_angle > Pi) turn_angle -= TwoPi; - if(turn_angle < -Pi) turn_angle += TwoPi; - return turn_angle; - }; - - auto eit = L.begin(); - auto enext = std::next(eit); - - eit->turn_angle = getTurnAngle(L.front().e, L.back().e); - - while(enext != L.end()) { - enext->turn_angle = getTurnAngle( enext->e, eit->e); - eit->is_turning_point = - signbit(enext->turn_angle) != signbit(eit->turn_angle); - ++eit; ++enext; - } - - L.back().is_turning_point = signbit(L.back().turn_angle) != - signbit(L.front().turn_angle); - - }; - - // Step 2: Fill the edgelists - fillEdgeList(A, stationary, 1); - fillEdgeList(B, orbiter, 1); - - int i = 1; - for(MarkedEdge& me : A) { - std::cout << "a" << i << ":\n\t" - << getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t" - << getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t" - << "Turning point: " << (me.is_turning_point ? "yes" : "no") - << std::endl; - - me.label = "a"; me.label += std::to_string(i); - i++; - } - - i = 1; - for(MarkedEdge& me : B) { - std::cout << "b" << i << ":\n\t" - << getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t" - << getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t" - << "Turning point: " << (me.is_turning_point ? "yes" : "no") - << std::endl; - me.label = "b"; me.label += std::to_string(i); - i++; - } - - // A reference to a marked edge that also knows its container - struct MarkedEdgeRef { - reference_wrapper eref; - reference_wrapper> container; - Coord dir = 1; // Direction modifier - - inline Radians angleX() const { return eref.get().e.angleToXaxis(); } - inline const Edge& edge() const { return eref.get().e; } - inline Edge& edge() { return eref.get().e; } - inline bool isTurningPoint() const { - return eref.get().is_turning_point; - } - inline bool isFrom(const vector& cont ) { - return &(container.get()) == &cont; - } - inline bool eq(const MarkedEdgeRef& mr) { - return &(eref.get()) == &(mr.eref.get()); - } - - MarkedEdgeRef(reference_wrapper er, - reference_wrapper> ec): - eref(er), container(ec), dir(1) {} - - MarkedEdgeRef(reference_wrapper er, - reference_wrapper> ec, - Coord d): - eref(er), container(ec), dir(d) {} - }; - - using EdgeRefList = vector; - - // Comparing two marked edges - auto sortfn = [](const MarkedEdgeRef& e1, const MarkedEdgeRef& e2) { - return e1.angleX() < e2.angleX(); - }; - - EdgeRefList Aref, Bref; // We create containers for the references - Aref.reserve(A.size()); Bref.reserve(B.size()); - - // Fill reference container for the stationary polygon - std::for_each(A.begin(), A.end(), [&Aref](MarkedEdge& me) { - Aref.emplace_back( ref(me), ref(Aref) ); - }); - - // Fill reference container for the orbiting polygon - std::for_each(B.begin(), B.end(), [&Bref](MarkedEdge& me) { - Bref.emplace_back( ref(me), ref(Bref) ); - }); - - auto mink = [sortfn] // the Mink(Q, R, direction) sub-procedure - (const EdgeRefList& Q, const EdgeRefList& R, bool positive) - { - - // Step 1 "merge sort_list(Q) and sort_list(R) to form merge_list(Q,R)" - // Sort the containers of edge references and merge them. - // Q could be sorted only once and be reused here but we would still - // need to merge it with sorted(R). - - EdgeRefList merged; - EdgeRefList S, seq; - merged.reserve(Q.size() + R.size()); - - merged.insert(merged.end(), R.begin(), R.end()); - std::stable_sort(merged.begin(), merged.end(), sortfn); - merged.insert(merged.end(), Q.begin(), Q.end()); - std::stable_sort(merged.begin(), merged.end(), sortfn); - - // Step 2 "set i = 1, k = 1, direction = 1, s1 = q1" - // we don't use i, instead, q is an iterator into Q. k would be an index - // into the merged sequence but we use "it" as an iterator for that - - // here we obtain references for the containers for later comparisons - const auto& Rcont = R.begin()->container.get(); - const auto& Qcont = Q.begin()->container.get(); - - // Set the initial direction - Coord dir = 1; - - // roughly i = 1 (so q = Q.begin()) and s1 = q1 so S[0] = q; - if(positive) { - auto q = Q.begin(); - S.emplace_back(*q); - - // Roughly step 3 - - std::cout << "merged size: " << merged.size() << std::endl; - auto mit = merged.begin(); - for(bool finish = false; !finish && q != Q.end();) { - ++q; // "Set i = i + 1" - - while(!finish && mit != merged.end()) { - if(mit->isFrom(Rcont)) { - auto s = *mit; - s.dir = dir; - S.emplace_back(s); - } - - if(mit->eq(*q)) { - S.emplace_back(*q); - if(mit->isTurningPoint()) dir = -dir; - if(q == Q.begin()) finish = true; - break; - } - - mit += dir; - // __nfp::advance(mit, merged, dir > 0); - } - } - } else { - auto q = Q.rbegin(); - S.emplace_back(*q); - - // Roughly step 3 - - std::cout << "merged size: " << merged.size() << std::endl; - auto mit = merged.begin(); - for(bool finish = false; !finish && q != Q.rend();) { - ++q; // "Set i = i + 1" - - while(!finish && mit != merged.end()) { - if(mit->isFrom(Rcont)) { - auto s = *mit; - s.dir = dir; - S.emplace_back(s); - } - - if(mit->eq(*q)) { - S.emplace_back(*q); - S.back().dir = -1; - if(mit->isTurningPoint()) dir = -dir; - if(q == Q.rbegin()) finish = true; - break; - } - - mit += dir; - // __nfp::advance(mit, merged, dir > 0); - } - } - } - - - // Step 4: - - // "Let starting edge r1 be in position si in sequence" - // whaaat? I guess this means the following: - auto it = S.begin(); - while(!it->eq(*R.begin())) ++it; - - // "Set j = 1, next = 2, direction = 1, seq1 = si" - // we don't use j, seq is expanded dynamically. - dir = 1; - auto next = std::next(R.begin()); seq.emplace_back(*it); - - // Step 5: - // "If all si edges have been allocated to seqj" should mean that - // we loop until seq has equal size with S - auto send = it; //it == S.begin() ? it : std::prev(it); - while(it != S.end()) { - ++it; if(it == S.end()) it = S.begin(); - if(it == send) break; - - if(it->isFrom(Qcont)) { - seq.emplace_back(*it); // "If si is from Q, j = j + 1, seqj = si" - - // "If si is a turning point in Q, - // direction = - direction, next = next + direction" - if(it->isTurningPoint()) { - dir = -dir; - next += dir; -// __nfp::advance(next, R, dir > 0); - } - } - - if(it->eq(*next) /*&& dir == next->dir*/) { // "If si = direction.rnext" - // "j = j + 1, seqj = si, next = next + direction" - seq.emplace_back(*it); - next += dir; -// __nfp::advance(next, R, dir > 0); - } - } - - return seq; - }; - - std::vector seqlist; - seqlist.reserve(Bref.size()); - - EdgeRefList Bslope = Bref; // copy Bref, we will make a slope diagram - - // make the slope diagram of B - std::sort(Bslope.begin(), Bslope.end(), sortfn); - - auto slopeit = Bslope.begin(); // search for the first turning point - while(!slopeit->isTurningPoint() && slopeit != Bslope.end()) slopeit++; - - if(slopeit == Bslope.end()) { - // no turning point means convex polygon. - seqlist.emplace_back(mink(Aref, Bref, true)); - } else { - int dir = 1; - - auto firstturn = Bref.begin(); - while(!firstturn->eq(*slopeit)) ++firstturn; - - assert(firstturn != Bref.end()); - - EdgeRefList bgroup; bgroup.reserve(Bref.size()); - bgroup.emplace_back(*slopeit); - - auto b_it = std::next(firstturn); - while(b_it != firstturn) { - if(b_it == Bref.end()) b_it = Bref.begin(); - - while(!slopeit->eq(*b_it)) { - __nfp::advance(slopeit, Bslope, dir > 0); - } - - if(!slopeit->isTurningPoint()) { - bgroup.emplace_back(*slopeit); - } else { - if(!bgroup.empty()) { - if(dir > 0) bgroup.emplace_back(*slopeit); - for(auto& me : bgroup) { - std::cout << me.eref.get().label << ", "; - } - std::cout << std::endl; - seqlist.emplace_back(mink(Aref, bgroup, dir == 1 ? true : false)); - bgroup.clear(); - if(dir < 0) bgroup.emplace_back(*slopeit); - } else { - bgroup.emplace_back(*slopeit); - } - - dir *= -1; - } - ++b_it; - } - } - -// while(it != Bref.end()) // This is step 3 and step 4 in one loop -// if(it->isTurningPoint()) { -// R = {R.last, it++}; -// auto seq = mink(Q, R, orientation); - -// // TODO step 6 (should be 5 shouldn't it?): linking edges from A -// // I don't get this step - -// seqlist.insert(seqlist.end(), seq.begin(), seq.end()); -// orientation = !orientation; -// } else ++it; - -// if(seqlist.empty()) seqlist = mink(Q, {Bref.begin(), Bref.end()}, true); - - // ///////////////////////////////////////////////////////////////////////// - // Algorithm 2: breaking Minkowski sums into track line trips - // ///////////////////////////////////////////////////////////////////////// - - - // ///////////////////////////////////////////////////////////////////////// - // Algorithm 3: finding the boundary of the NFP from track line trips - // ///////////////////////////////////////////////////////////////////////// - - - for(auto& seq : seqlist) { - std::cout << "seqlist size: " << seq.size() << std::endl; - for(auto& s : seq) { - std::cout << (s.dir > 0 ? "" : "-") << s.eref.get().label << ", "; - } - std::cout << std::endl; - } - - auto& seq = seqlist.front(); - RawShape rsh; - Vertex top_nfp; - std::vector edgelist; edgelist.reserve(seq.size()); - for(auto& s : seq) { - edgelist.emplace_back(s.eref.get().e); - } - - __nfp::buildPolygon(edgelist, rsh, top_nfp); - - return Result(rsh, top_nfp); + return {}; } // Specializable NFP implementation class. Specialize it if you have a faster diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index c7b252e5d5..29d52c10f8 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -8,12 +8,11 @@ #include #include -#include "geometry_traits.hpp" +#include namespace libnest2d { -namespace sl = shapelike; -namespace pl = pointlike; +static const constexpr int BIN_ID_UNSET = -1; /** * \brief An item to be placed on a bin. @@ -37,22 +36,22 @@ class _Item { RawShape sh_; // Transformation data - Vertex translation_; - Radians rotation_; - Coord offset_distance_; + Vertex translation_{0, 0}; + Radians rotation_{0.0}; + Coord inflation_{0}; // Info about whether the transformations will have to take place // This is needed because if floating point is used, it is hard to say // that a zero angle is not a rotation because of testing for equality. - bool has_rotation_ = false, has_translation_ = false, has_offset_ = false; + bool has_rotation_ = false, has_translation_ = false, has_inflation_ = false; // For caching the calculations as they can get pretty expensive. mutable RawShape tr_cache_; mutable bool tr_cache_valid_ = false; mutable double area_cache_ = 0; mutable bool area_cache_valid_ = false; - mutable RawShape offset_cache_; - mutable bool offset_cache_valid_ = false; + mutable RawShape inflate_cache_; + mutable bool inflate_cache_valid_ = false; enum class Convexity: char { UNCHECKED, @@ -68,6 +67,9 @@ class _Item { Box bb; bool valid; BBCache(): valid(false) {} } bb_cache_; + + int binid_{BIN_ID_UNSET}, priority_{0}; + bool fixed_{false}; public: @@ -124,8 +126,16 @@ public: inline _Item(TContour&& contour, THolesContainer&& holes): - sh_(sl::create(std::move(contour), - std::move(holes))) {} + sh_(sl::create(std::move(contour), std::move(holes))) {} + + inline bool isFixed() const noexcept { return fixed_; } + inline void markAsFixed(bool fixed = true) { fixed_ = fixed; } + + inline void binId(int idx) { binid_ = idx; } + inline int binId() const noexcept { return binid_; } + + inline void priority(int p) { priority_ = p; } + inline int priority() const noexcept { return priority_; } /** * @brief Convert the polygon to string representation. The format depends @@ -203,7 +213,7 @@ public: double ret ; if(area_cache_valid_) ret = area_cache_; else { - ret = sl::area(offsettedShape()); + ret = sl::area(infaltedShape()); area_cache_ = ret; area_cache_valid_ = true; } @@ -274,17 +284,21 @@ public: { rotation(rotation() + rads); } - - inline void addOffset(Coord distance) BP2D_NOEXCEPT + + inline void inflation(Coord distance) BP2D_NOEXCEPT { - offset_distance_ = distance; - has_offset_ = true; + inflation_ = distance; + has_inflation_ = true; invalidateCache(); } - - inline void removeOffset() BP2D_NOEXCEPT { - has_offset_ = false; - invalidateCache(); + + inline Coord inflation() const BP2D_NOEXCEPT { + return inflation_; + } + + inline void inflate(Coord distance) BP2D_NOEXCEPT + { + inflation(inflation() + distance); } inline Radians rotation() const BP2D_NOEXCEPT @@ -318,7 +332,7 @@ public: { if(tr_cache_valid_) return tr_cache_; - RawShape cpy = offsettedShape(); + RawShape cpy = infaltedShape(); if(has_rotation_) sl::rotate(cpy, rotation_); if(has_translation_) sl::translate(cpy, translation_); tr_cache_ = cpy; tr_cache_valid_ = true; @@ -339,17 +353,17 @@ public: inline void resetTransformation() BP2D_NOEXCEPT { - has_translation_ = false; has_rotation_ = false; has_offset_ = false; + has_translation_ = false; has_rotation_ = false; has_inflation_ = false; invalidateCache(); } inline Box boundingBox() const { if(!bb_cache_.valid) { if(!has_rotation_) - bb_cache_.bb = sl::boundingBox(offsettedShape()); + bb_cache_.bb = sl::boundingBox(infaltedShape()); else { // TODO make sure this works - auto rotsh = offsettedShape(); + auto rotsh = infaltedShape(); sl::rotate(rotsh, rotation_); bb_cache_.bb = sl::boundingBox(rotsh); } @@ -398,14 +412,14 @@ public: private: - inline const RawShape& offsettedShape() const { - if(has_offset_ ) { - if(offset_cache_valid_) return offset_cache_; + inline const RawShape& infaltedShape() const { + if(has_inflation_ ) { + if(inflate_cache_valid_) return inflate_cache_; - offset_cache_ = sh_; - sl::offset(offset_cache_, offset_distance_); - offset_cache_valid_ = true; - return offset_cache_; + inflate_cache_ = sh_; + sl::offset(inflate_cache_, inflation_); + inflate_cache_valid_ = true; + return inflate_cache_; } return sh_; } @@ -415,20 +429,16 @@ private: tr_cache_valid_ = false; lmb_valid_ = false; rmt_valid_ = false; area_cache_valid_ = false; - offset_cache_valid_ = false; + inflate_cache_valid_ = false; bb_cache_.valid = false; convexity_ = Convexity::UNCHECKED; } static inline bool vsort(const Vertex& v1, const Vertex& v2) { - Coord &&x1 = getX(v1), &&x2 = getX(v2); - Coord &&y1 = getY(v1), &&y2 = getY(v2); - auto diff = y1 - y2; - if(std::abs(diff) <= std::numeric_limits::epsilon()) - return x1 < x2; - - return diff < 0; + TCompute x1 = getX(v1), x2 = getX(v2); + TCompute y1 = getY(v1), y2 = getY(v2); + return y1 == y2 ? x1 < x2 : y1 < y2; } }; @@ -499,24 +509,6 @@ template using _ItemGroup = std::vector<_ItemRef>; template using _PackGroup = std::vector>>; -/** - * \brief A list of packed (index, item) pair vectors. Each vector represents a - * bin. - * - * The index is points to the position of the item in the original input - * sequence. This way the caller can use the items as a transformation data - * carrier and transform the original objects manually. - */ -template -using _IndexedPackGroup = std::vector< - std::vector< - std::pair< - unsigned, - _ItemRef - > - > - >; - template struct ConstItemRange { Iterator from; @@ -745,54 +737,45 @@ public: return impl_.getResult(); } - /** - * @brief Loading a group of already packed bins. It is best to use a result - * from a previous packing. The algorithm will consider this input as if the - * objects are already packed and not move them. If any of these items are - * outside the bin, it is up to the placer algorithm what will happen. - * Packing additional items can fail for the bottom-left and nfp placers. - * @param pckgrp A packgroup which is a vector of item vectors. Each item - * vector corresponds to a packed bin. - */ - inline void preload(const PackGroup& pckgrp) { impl_.preload(pckgrp); } - void clear() { impl_.clear(); } }; /** - * The Arranger is the front-end class for the libnest2d library. It takes the - * input items and outputs the items with the proper transformations to be - * inside the provided bin. + * The _Nester is the front-end class for the libnest2d library. It takes the + * input items and changes their transformations to be inside the provided bin. */ template -class Nester { +class _Nester { using TSel = SelectionStrategyLike; TSel selector_; public: using Item = typename PlacementStrategy::Item; + using ShapeType = typename Item::ShapeType; using ItemRef = std::reference_wrapper; using TPlacer = PlacementStrategyLike; using BinType = typename TPlacer::BinType; using PlacementConfig = typename TPlacer::Config; using SelectionConfig = typename TSel::Config; - - using Unit = TCoord>; - - using IndexedPackGroup = _IndexedPackGroup; + using Coord = TCoord>; using PackGroup = _PackGroup; using ResultType = PackGroup; - using ResultTypeIndexed = IndexedPackGroup; private: BinType bin_; PlacementConfig pconfig_; - Unit min_obj_distance_; + Coord min_obj_distance_; using SItem = typename SelectionStrategy::Item; using TPItem = remove_cvref_t; using TSItem = remove_cvref_t; - std::vector item_cache_; + StopCondition stopfn_; + + template using TVal = remove_ref_t; + + template + using ItemIteratorOnly = + enable_if_t&, TPItem&>::value, Out>; public: @@ -805,10 +788,8 @@ public: template - Nester( TBinType&& bin, - Unit min_obj_distance = 0, - const PConf& pconfig = PConf(), - const SConf& sconfig = SConf()): + _Nester(TBinType&& bin, Coord min_obj_distance = 0, + const PConf& pconfig = PConf(), const SConf& sconfig = SConf()): bin_(std::forward(bin)), pconfig_(pconfig), min_obj_distance_(min_obj_distance) @@ -821,182 +802,59 @@ public: void configure(const PlacementConfig& pconf) { pconfig_ = pconf; } void configure(const SelectionConfig& sconf) { selector_.configure(sconf); } - void configure(const PlacementConfig& pconf, const SelectionConfig& sconf) { + void configure(const PlacementConfig& pconf, const SelectionConfig& sconf) + { pconfig_ = pconf; selector_.configure(sconf); } - void configure(const SelectionConfig& sconf, const PlacementConfig& pconf) { + void configure(const SelectionConfig& sconf, const PlacementConfig& pconf) + { pconfig_ = pconf; selector_.configure(sconf); } /** - * \brief Arrange an input sequence and return a PackGroup object with - * the packed groups corresponding to the bins. + * \brief Arrange an input sequence of _Item-s. + * + * To get the result, call the translation(), rotation() and binId() + * methods of each item. If only the transformed polygon is needed, call + * transformedShape() to get the properly transformed shapes. * * The number of groups in the pack group is the number of bins opened by * the selection algorithm. */ - template - inline PackGroup execute(TIterator from, TIterator to) + template + inline ItemIteratorOnly execute(It from, It to) { - return _execute(from, to); - } - - /** - * A version of the arrange method returning an IndexedPackGroup with - * the item indexes into the original input sequence. - * - * Takes a little longer to collect the indices. Scales linearly with the - * input sequence size. - */ - template - inline IndexedPackGroup executeIndexed(TIterator from, TIterator to) - { - return _executeIndexed(from, to); - } - - /// Shorthand to normal arrange method. - template - inline PackGroup operator() (TIterator from, TIterator to) - { - return _execute(from, to); + auto infl = static_cast(std::ceil(min_obj_distance_/2.0)); + if(infl > 0) std::for_each(from, to, [this, infl](Item& item) { + item.inflate(infl); + }); + + selector_.template packItems( + from, to, bin_, pconfig_); + + if(min_obj_distance_ > 0) std::for_each(from, to, [infl](Item& item) { + item.inflate(-infl); + }); } /// Set a progress indicator function object for the selector. - inline Nester& progressIndicator(ProgressFunction func) + inline _Nester& progressIndicator(ProgressFunction func) { selector_.progressIndicator(func); return *this; } /// Set a predicate to tell when to abort nesting. - inline Nester& stopCondition(StopCondition fn) + inline _Nester& stopCondition(StopCondition fn) { - selector_.stopCondition(fn); return *this; + stopfn_ = fn; selector_.stopCondition(fn); return *this; } inline const PackGroup& lastResult() const { return selector_.getResult(); } - - inline void preload(const PackGroup& pgrp) - { - selector_.preload(pgrp); - } - - inline void preload(const IndexedPackGroup& ipgrp) - { - PackGroup pgrp; pgrp.reserve(ipgrp.size()); - for(auto& ig : ipgrp) { - pgrp.emplace_back(); pgrp.back().reserve(ig.size()); - for(auto& r : ig) pgrp.back().emplace_back(r.second); - } - preload(pgrp); - } - -private: - - template, - - // This function will be used only if the iterators are pointing to - // a type compatible with the libnets2d::_Item template. - // This way we can use references to input elements as they will - // have to exist for the lifetime of this call. - class T = enable_if_t< std::is_convertible::value, IT> - > - inline const PackGroup& _execute(TIterator from, TIterator to, bool = false) - { - __execute(from, to); - return lastResult(); - } - - template, - class T = enable_if_t::value, IT> - > - inline const PackGroup& _execute(TIterator from, TIterator to, int = false) - { - item_cache_ = {from, to}; - - __execute(item_cache_.begin(), item_cache_.end()); - return lastResult(); - } - - template, - - // This function will be used only if the iterators are pointing to - // a type compatible with the libnest2d::_Item template. - // This way we can use references to input elements as they will - // have to exist for the lifetime of this call. - class T = enable_if_t< std::is_convertible::value, IT> - > - inline IndexedPackGroup _executeIndexed(TIterator from, - TIterator to, - bool = false) - { - __execute(from, to); - return createIndexedPackGroup(from, to, selector_); - } - - template, - class T = enable_if_t::value, IT> - > - inline IndexedPackGroup _executeIndexed(TIterator from, - TIterator to, - int = false) - { - item_cache_ = {from, to}; - __execute(item_cache_.begin(), item_cache_.end()); - return createIndexedPackGroup(from, to, selector_); - } - - template - static IndexedPackGroup createIndexedPackGroup(TIterator from, - TIterator to, - TSel& selector) - { - IndexedPackGroup pg; - pg.reserve(selector.getResult().size()); - - const PackGroup& pckgrp = selector.getResult(); - - for(size_t i = 0; i < pckgrp.size(); i++) { - auto items = pckgrp[i]; - pg.emplace_back(); - pg[i].reserve(items.size()); - - for(Item& itemA : items) { - auto it = from; - unsigned idx = 0; - while(it != to) { - Item& itemB = *it; - if(&itemB == &itemA) break; - it++; idx++; - } - pg[i].emplace_back(idx, itemA); - } - } - - return pg; - } - - template inline void __execute(TIter from, TIter to) - { - if(min_obj_distance_ > 0) std::for_each(from, to, [this](Item& item) { - item.addOffset(static_cast(std::ceil(min_obj_distance_/2.0))); - }); - - selector_.template packItems( - from, to, bin_, pconfig_); - - if(min_obj_distance_ > 0) std::for_each(from, to, [](Item& item) { - item.removeOffset(); - }); - } }; } diff --git a/src/libnest2d/include/libnest2d/optimizer.hpp b/src/libnest2d/include/libnest2d/optimizer.hpp index 962a47392f..e4c149f228 100644 --- a/src/libnest2d/include/libnest2d/optimizer.hpp +++ b/src/libnest2d/include/libnest2d/optimizer.hpp @@ -4,7 +4,8 @@ #include #include #include -#include "common.hpp" + +#include namespace libnest2d { namespace opt { @@ -60,6 +61,7 @@ enum class Method { L_SIMPLEX, L_SUBPLEX, G_GENETIC, + G_PARTICLE_SWARM //... }; diff --git a/src/libnest2d/include/libnest2d/optimizers/nlopt/CMakeLists.txt b/src/libnest2d/include/libnest2d/optimizers/nlopt/CMakeLists.txt index 4e16d4fc58..6f51718d8b 100644 --- a/src/libnest2d/include/libnest2d/optimizers/nlopt/CMakeLists.txt +++ b/src/libnest2d/include/libnest2d/optimizers/nlopt/CMakeLists.txt @@ -48,7 +48,7 @@ else() target_link_libraries(nloptOptimizer INTERFACE Nlopt::Nlopt) endif() -#target_sources( NloptOptimizer INTERFACE +#target_sources( nloptOptimizer INTERFACE #${CMAKE_CURRENT_SOURCE_DIR}/simplex.hpp #${CMAKE_CURRENT_SOURCE_DIR}/subplex.hpp #${CMAKE_CURRENT_SOURCE_DIR}/genetic.hpp @@ -57,5 +57,5 @@ endif() target_compile_definitions(nloptOptimizer INTERFACE LIBNEST2D_OPTIMIZER_NLOPT) -# And finally plug the NloptOptimizer into libnest2d -#target_link_libraries(libnest2d INTERFACE NloptOptimizer) +# And finally plug the nloptOptimizer into libnest2d +#target_link_libraries(libnest2d INTERFACE nloptOptimizer) diff --git a/src/libnest2d/include/libnest2d/optimizers/optimlib/CMakeLists.txt b/src/libnest2d/include/libnest2d/optimizers/optimlib/CMakeLists.txt deleted file mode 100644 index efbbd9cfb2..0000000000 --- a/src/libnest2d/include/libnest2d/optimizers/optimlib/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -find_package(Armadillo REQUIRED) - -add_library(OptimlibOptimizer INTERFACE) -target_include_directories(OptimlibOptimizer INTERFACE ${ARMADILLO_INCLUDE_DIRS}) -target_link_libraries(OptimlibOptimizer INTERFACE ${ARMADILLO_LIBRARIES}) \ No newline at end of file diff --git a/src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp b/src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp index 7f10be7d73..e1a4ffbd92 100644 --- a/src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp @@ -7,15 +7,15 @@ namespace libnest2d { namespace placers { -template struct Epsilon {}; +template struct DefaultEpsilon {}; template -struct Epsilon::value, T> > { +struct DefaultEpsilon::value, T> > { static const T Value = 1; }; template -struct Epsilon::value, T> > { +struct DefaultEpsilon::value, T> > { static const T Value = 1e-3; }; @@ -24,7 +24,7 @@ struct BLConfig { DECLARE_MAIN_TYPES(RawShape); Coord min_obj_distance = 0; - Coord epsilon = Epsilon::Value; + Coord epsilon = DefaultEpsilon::Value; bool allow_rotations = false; }; @@ -68,11 +68,11 @@ public: return toWallPoly(item, Dir::DOWN); } - inline Unit availableSpaceLeft(const Item& item) { + inline Coord availableSpaceLeft(const Item& item) { return availableSpace(item, Dir::LEFT); } - inline Unit availableSpaceDown(const Item& item) { + inline Coord availableSpaceDown(const Item& item) { return availableSpace(item, Dir::DOWN); } @@ -83,7 +83,7 @@ protected: // Get initial position for item in the top right corner setInitialPosition(item); - Unit d = availableSpaceDown(item); + Coord d = availableSpaceDown(item); auto eps = config_.epsilon; bool can_move = d > eps; bool can_be_packed = can_move; @@ -179,7 +179,7 @@ protected: return ret; } - Unit availableSpace(const Item& _item, const Dir dir) { + Coord availableSpace(const Item& _item, const Dir dir) { Item item (_item.transformedShape()); @@ -223,7 +223,7 @@ protected: cmp); // Get the initial distance in floating point - Unit m = getCoord(*minvertex_it); + Coord m = getCoord(*minvertex_it); // Check available distance for every vertex of item to the objects // in the way for the nearest intersection diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index 91affe9786..4ef1fe71d9 100644 --- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -103,14 +103,14 @@ Key hash(const _Item& item) { while(deg > N) { ms++; deg -= N; } ls += int(deg); ret.push_back(char(ms)); ret.push_back(char(ls)); - circ += seg.length(); + circ += std::sqrt(seg.template sqlength()); } it = ctr.begin(); nx = std::next(it); while(nx != ctr.end()) { Segment seg(*it++, *nx++); - auto l = int(M * seg.length() / circ); + auto l = int(M * std::sqrt(seg.template sqlength()) / circ); int ms = 'A', ls = 'A'; while(l > N) { ms++; l -= N; } ls += l; @@ -249,6 +249,11 @@ template class EdgeCache { std::vector holes_; double accuracy_ = 1.0; + + static double length(const Edge &e) + { + return std::sqrt(e.template sqlength()); + } void createCache(const RawShape& sh) { { // For the contour @@ -260,7 +265,7 @@ template class EdgeCache { while(next != endit) { contour_.emap.emplace_back(*(first++), *(next++)); - contour_.full_distance += contour_.emap.back().length(); + contour_.full_distance += length(contour_.emap.back()); contour_.distances.emplace_back(contour_.full_distance); } } @@ -275,7 +280,7 @@ template class EdgeCache { while(next != endit) { hc.emap.emplace_back(*(first++), *(next++)); - hc.full_distance += hc.emap.back().length(); + hc.full_distance += length(hc.emap.back()); hc.distances.emplace_back(hc.full_distance); } @@ -576,8 +581,12 @@ public: static inline double overfit(const Box& bb, const Box& bin) { - auto wdiff = double(bb.width() - bin.width()); - auto hdiff = double(bb.height() - bin.height()); + auto Bw = bin.width(); + auto Bh = bin.height(); + auto mBw = -Bw; + auto mBh = -Bh; + auto wdiff = double(bb.width()) + mBw; + auto hdiff = double(bb.height()) + mBh; double diff = 0; if(wdiff > 0) diff += wdiff; if(hdiff > 0) diff += hdiff; @@ -796,7 +805,6 @@ private: // optimize config_.object_function = prev_func; } - } struct Optimum { @@ -811,29 +819,14 @@ private: class Optimizer: public opt::TOptimizer { public: - Optimizer() { + Optimizer(float accuracy = 1.f) { opt::StopCriteria stopcr; - stopcr.max_iterations = 200; + stopcr.max_iterations = unsigned(std::floor(1000 * accuracy)); stopcr.relative_score_difference = 1e-20; this->stopcr_ = stopcr; } }; - static Box boundingBox(const Box& pilebb, const Box& ibb ) { - auto& pminc = pilebb.minCorner(); - auto& pmaxc = pilebb.maxCorner(); - auto& iminc = ibb.minCorner(); - auto& imaxc = ibb.maxCorner(); - Vertex minc, maxc; - - setX(minc, std::min(getX(pminc), getX(iminc))); - setY(minc, std::min(getY(pminc), getY(iminc))); - - setX(maxc, std::max(getX(pmaxc), getX(imaxc))); - setY(maxc, std::max(getY(pmaxc), getY(imaxc))); - return Box(minc, maxc); - } - using Edges = EdgeCache; template> @@ -930,7 +923,7 @@ private: _objfunc = [norm, binbb, pbb, ins_check](const Item& item) { auto ibb = item.boundingBox(); - auto fullbb = boundingBox(pbb, ibb); + auto fullbb = sl::boundingBox(pbb, ibb); double score = pl::distance(ibb.center(), binbb.center()); @@ -1000,14 +993,15 @@ private: auto& rofn = rawobjfunc; auto& nfpoint = getNfpPoint; + float accuracy = config_.accuracy; __parallel::enumerate( cache.corners().begin(), cache.corners().end(), - [&results, &item, &rofn, &nfpoint, ch] + [&results, &item, &rofn, &nfpoint, ch, accuracy] (double pos, size_t n) { - Optimizer solver; + Optimizer solver(accuracy); Item itemcpy = item; auto contour_ofn = [&rofn, &nfpoint, ch, &itemcpy] @@ -1054,10 +1048,10 @@ private: __parallel::enumerate(cache.corners(hidx).begin(), cache.corners(hidx).end(), [&results, &item, &nfpoint, - &rofn, ch, hidx] + &rofn, ch, hidx, accuracy] (double pos, size_t n) { - Optimizer solver; + Optimizer solver(accuracy); Item itmcpy = item; auto hole_ofn = diff --git a/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp b/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp index abcd861830..26681aeec6 100644 --- a/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp +++ b/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp @@ -18,7 +18,6 @@ public: using Segment = _Segment; using BinType = TBin; using Coord = TCoord; - using Unit = Coord; using Config = Cfg; using ItemGroup = _ItemGroup; using DefaultIter = typename ItemGroup::const_iterator; @@ -131,7 +130,6 @@ using typename Base::Vertex; \ using typename Base::Segment; \ using typename Base::PackResult; \ using typename Base::Coord; \ -using typename Base::Unit; \ private: } diff --git a/src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp b/src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp index 25007e580e..f904210aac 100644 --- a/src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp +++ b/src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp @@ -711,7 +711,12 @@ public: addBin(); packjob(placers[idx], remaining, idx); idx++; } - + + int binid = 0; + for(auto &bin : packed_bins_) { + for(Item& itm : bin) itm.binId(binid); + binid++; + } } }; diff --git a/src/libnest2d/include/libnest2d/selections/firstfit.hpp b/src/libnest2d/include/libnest2d/selections/firstfit.hpp index d521673b44..5585e565d5 100644 --- a/src/libnest2d/include/libnest2d/selections/firstfit.hpp +++ b/src/libnest2d/include/libnest2d/selections/firstfit.hpp @@ -39,6 +39,20 @@ public: std::vector placers; placers.reserve(last-first); + + std::for_each(first, last, [this](Item& itm) { + if(itm.isFixed()) { + if (itm.binId() < 0) itm.binId(0); + auto binidx = size_t(itm.binId()); + + while(packed_bins_.size() <= binidx) + packed_bins_.emplace_back(); + + packed_bins_[binidx].emplace_back(itm); + } else { + store_.emplace_back(itm); + } + }); // If the packed_items array is not empty we have to create as many // placers as there are elements in packed bins and preload each item @@ -48,11 +62,10 @@ public: placers.back().configure(pconfig); placers.back().preload(ig); } - - std::copy(first, last, std::back_inserter(store_)); - + auto sortfunc = [](Item& i1, Item& i2) { - return i1.area() > i2.area(); + int p1 = i1.priority(), p2 = i2.priority(); + return p1 == p2 ? i1.area() > i2.area() : p1 > p2; }; std::sort(store_.begin(), store_.end(), sortfunc); @@ -76,7 +89,6 @@ public: } } - auto it = store_.begin(); while(it != store_.end() && !cancelled()) { @@ -84,8 +96,10 @@ public: size_t j = 0; while(!was_packed && !cancelled()) { for(; j < placers.size() && !was_packed && !cancelled(); j++) { - if((was_packed = placers[j].pack(*it, rem(it, store_) ))) - makeProgress(placers[j], j); + if((was_packed = placers[j].pack(*it, rem(it, store_) ))) { + it->get().binId(int(j)); + makeProgress(placers[j], j); + } } if(!was_packed) { diff --git a/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp b/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp index fd6577d978..9ae92fe290 100644 --- a/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp +++ b/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp @@ -22,8 +22,6 @@ public: inline void stopCondition(StopCondition cond) { stopcond_ = cond; } - inline void preload(const PackGroup& pckgrp) { packed_bins_ = pckgrp; } - inline void clear() { packed_bins_.clear(); } protected: diff --git a/src/libnest2d/include/libnest2d/utils/boost_alg.hpp b/src/libnest2d/include/libnest2d/utils/boost_alg.hpp index baf1c6a105..16dee513b2 100644 --- a/src/libnest2d/include/libnest2d/utils/boost_alg.hpp +++ b/src/libnest2d/include/libnest2d/utils/boost_alg.hpp @@ -311,19 +311,19 @@ struct range_value { namespace libnest2d { // Now the algorithms that boost can provide... -namespace pointlike { -template<> -inline double distance(const PointImpl& p1, const PointImpl& p2 ) -{ - return boost::geometry::distance(p1, p2); -} +//namespace pointlike { +//template<> +//inline double distance(const PointImpl& p1, const PointImpl& p2 ) +//{ +// return boost::geometry::distance(p1, p2); +//} -template<> -inline double distance(const PointImpl& p, const bp2d::Segment& seg ) -{ - return boost::geometry::distance(p, seg); -} -} +//template<> +//inline double distance(const PointImpl& p, const bp2d::Segment& seg ) +//{ +// return boost::geometry::distance(p, seg); +//} +//} namespace shapelike { // Tell libnest2d how to make string out of a ClipperPolygon object @@ -382,16 +382,9 @@ inline bool touches( const PointImpl& point, const PolygonImpl& shape) } #ifndef DISABLE_BOOST_BOUNDING_BOX -template<> -inline bp2d::Box boundingBox(const PolygonImpl& sh, const PolygonTag&) -{ - bp2d::Box b; - boost::geometry::envelope(sh, b); - return b; -} template<> -inline bp2d::Box boundingBox(const PathImpl& sh, const PolygonTag&) +inline bp2d::Box boundingBox(const PathImpl& sh, const PathTag&) { bp2d::Box b; boost::geometry::envelope(sh, b); @@ -410,9 +403,9 @@ inline bp2d::Box boundingBox(const bp2d::Shapes& shapes, #ifndef DISABLE_BOOST_CONVEX_HULL template<> -inline PolygonImpl convexHull(const PolygonImpl& sh, const PolygonTag&) +inline PathImpl convexHull(const PathImpl& sh, const PathTag&) { - PolygonImpl ret; + PathImpl ret; boost::geometry::convex_hull(sh, ret); return ret; } diff --git a/src/libnest2d/include/libnest2d/utils/rotcalipers.hpp b/src/libnest2d/include/libnest2d/utils/rotcalipers.hpp new file mode 100644 index 0000000000..76f91ba0cf --- /dev/null +++ b/src/libnest2d/include/libnest2d/utils/rotcalipers.hpp @@ -0,0 +1,268 @@ +#ifndef ROTCALIPERS_HPP +#define ROTCALIPERS_HPP + +#include +#include +#include +#include + +#include + +namespace libnest2d { + +template> class RotatedBox { + Pt axis_; + Unit bottom_ = Unit(0), right_ = Unit(0); +public: + + RotatedBox() = default; + RotatedBox(const Pt& axis, Unit b, Unit r): + axis_(axis), bottom_(b), right_(r) {} + + inline long double area() const { + long double asq = pl::magnsq(axis_); + return cast(bottom_) * cast(right_) / asq; + } + + inline long double width() const { + return abs(bottom_) / std::sqrt(pl::magnsq(axis_)); + } + + inline long double height() const { + return abs(right_) / std::sqrt(pl::magnsq(axis_)); + } + + inline Unit bottom_extent() const { return bottom_; } + inline Unit right_extent() const { return right_; } + inline const Pt& axis() const { return axis_; } + + inline Radians angleToX() const { + double ret = std::atan2(getY(axis_), getX(axis_)); + auto s = std::signbit(ret); + if(s) ret += Pi_2; + return -ret; + } +}; + +template , class Unit = TCompute> +Poly removeCollinearPoints(const Poly& sh, Unit eps = Unit(0)) +{ + Poly ret; sl::reserve(ret, sl::contourVertexCount(sh)); + + Pt eprev = *sl::cbegin(sh) - *std::prev(sl::cend(sh)); + + auto it = sl::cbegin(sh); + auto itx = std::next(it); + if(itx != sl::cend(sh)) while (it != sl::cend(sh)) + { + Pt enext = *itx - *it; + + auto dp = pl::dotperp(eprev, enext); + if(abs(dp) > eps) sl::addVertex(ret, *it); + + eprev = enext; + if (++itx == sl::cend(sh)) itx = sl::cbegin(sh); + ++it; + } + + return ret; +} + +// The area of the bounding rectangle with the axis dir and support vertices +template, class R = TCompute> +inline R rectarea(const Pt& w, // the axis + const Pt& vb, const Pt& vr, + const Pt& vt, const Pt& vl) +{ + Unit a = pl::dot(w, vr - vl); + Unit b = pl::dot(-pl::perp(w), vt - vb); + R m = R(a) / pl::magnsq(w); + m = m * b; + return m; +}; + +template, + class R = TCompute, + class It = typename std::vector::const_iterator> +inline R rectarea(const Pt& w, const std::array& rect) +{ + return rectarea(w, *rect[0], *rect[1], *rect[2], *rect[3]); +} + +// This function is only applicable to counter-clockwise oriented convex +// polygons where only two points can be collinear witch each other. +template , + class Ratio = TCompute> +RotatedBox, Unit> minAreaBoundingBox(const RawShape& sh) +{ + using Point = TPoint; + using Iterator = typename TContour::const_iterator; + using pointlike::dot; using pointlike::magnsq; using pointlike::perp; + + // Get the first and the last vertex iterator + auto first = sl::cbegin(sh); + auto last = std::prev(sl::cend(sh)); + + // Check conditions and return undefined box if input is not sane. + if(last == first) return {}; + if(getX(*first) == getX(*last) && getY(*first) == getY(*last)) --last; + if(last - first < 2) return {}; + + RawShape shcpy; // empty at this point + { + Point p = *first, q = *std::next(first), r = *last; + + // Determine orientation from first 3 vertex (should be consistent) + Unit d = (Unit(getY(q)) - getY(p)) * (Unit(getX(r)) - getX(p)) - + (Unit(getX(q)) - getX(p)) * (Unit(getY(r)) - getY(p)); + + if(d > 0) { + // The polygon is clockwise. A flip is needed (for now) + sl::reserve(shcpy, last - first); + auto it = last; while(it != first) sl::addVertex(shcpy, *it--); + sl::addVertex(shcpy, *first); + first = sl::cbegin(shcpy); last = std::prev(sl::cend(shcpy)); + } + } + + // Cyclic iterator increment + auto inc = [&first, &last](Iterator& it) { + if(it == last) it = first; else ++it; + }; + + // Cyclic previous iterator + auto prev = [&first, &last](Iterator it) { + return it == first ? last : std::prev(it); + }; + + // Cyclic next iterator + auto next = [&first, &last](Iterator it) { + return it == last ? first : std::next(it); + }; + + // Establish initial (axis aligned) rectangle support verices by determining + // polygon extremes: + + auto it = first; + Iterator minX = it, maxX = it, minY = it, maxY = it; + + do { // Linear walk through the vertices and save the extreme positions + + Point v = *it, d = v - *minX; + if(getX(d) < 0 || (getX(d) == 0 && getY(d) < 0)) minX = it; + + d = v - *maxX; + if(getX(d) > 0 || (getX(d) == 0 && getY(d) > 0)) maxX = it; + + d = v - *minY; + if(getY(d) < 0 || (getY(d) == 0 && getX(d) > 0)) minY = it; + + d = v - *maxY; + if(getY(d) > 0 || (getY(d) == 0 && getX(d) < 0)) maxY = it; + + } while(++it != std::next(last)); + + // Update the vertices defining the bounding rectangle. The rectangle with + // the smallest rotation is selected and the supporting vertices are + // returned in the 'rect' argument. + auto update = [&next, &inc] + (const Point& w, std::array& rect) + { + Iterator B = rect[0], Bn = next(B); + Iterator R = rect[1], Rn = next(R); + Iterator T = rect[2], Tn = next(T); + Iterator L = rect[3], Ln = next(L); + + Point b = *Bn - *B, r = *Rn - *R, t = *Tn - *T, l = *Ln - *L; + Point pw = perp(w); + using Pt = Point; + + Unit dotwpb = dot( w, b), dotwpr = dot(-pw, r); + Unit dotwpt = dot(-w, t), dotwpl = dot( pw, l); + Unit dw = magnsq(w); + + std::array angles; + angles[0] = (Ratio(dotwpb) / magnsq(b)) * dotwpb; + angles[1] = (Ratio(dotwpr) / magnsq(r)) * dotwpr; + angles[2] = (Ratio(dotwpt) / magnsq(t)) * dotwpt; + angles[3] = (Ratio(dotwpl) / magnsq(l)) * dotwpl; + + using AngleIndex = std::pair; + std::vector A; A.reserve(4); + + for (size_t i = 3, j = 0; j < 4; i = j++) { + if(rect[i] != rect[j] && angles[i] < dw) { + auto iv = std::make_pair(angles[i], i); + auto it = std::lower_bound(A.begin(), A.end(), iv, + [](const AngleIndex& ai, + const AngleIndex& aj) + { + return ai.first > aj.first; + }); + + A.insert(it, iv); + } + } + + // The polygon is supposed to be a rectangle. + if(A.empty()) return false; + + auto amin = A.front().first; + auto imin = A.front().second; + for(auto& a : A) if(a.first == amin) inc(rect[a.second]); + + std::rotate(rect.begin(), rect.begin() + imin, rect.end()); + + return true; + }; + + Point w(1, 0); + Point w_min = w; + Ratio minarea((Unit(getX(*maxX)) - getX(*minX)) * + (Unit(getY(*maxY)) - getY(*minY))); + + std::array rect = {minY, maxX, maxY, minX}; + std::array minrect = rect; + + // An edge might be examined twice in which case the algorithm terminates. + size_t c = 0, count = last - first + 1; + std::vector edgemask(count, false); + + while(c++ < count) + { + // Update the support vertices, if cannot be updated, break the cycle. + if(! update(w, rect)) break; + + size_t eidx = size_t(rect[0] - first); + + if(edgemask[eidx]) break; + edgemask[eidx] = true; + + // get the unnormalized direction vector + w = *rect[0] - *prev(rect[0]); + + // get the area of the rotated rectangle + Ratio rarea = rectarea(w, rect); + + // Update min area and the direction of the min bounding box; + if(rarea <= minarea) { w_min = w; minarea = rarea; minrect = rect; } + } + + Unit a = dot(w_min, *minrect[1] - *minrect[3]); + Unit b = dot(-perp(w_min), *minrect[2] - *minrect[0]); + RotatedBox bb(w_min, a, b); + + return bb; +} + +template Radians minAreaBoundingBoxRotation(const RawShape& sh) +{ + return minAreaBoundingBox(sh).angleToX(); +} + + +} + +#endif // ROTCALIPERS_HPP diff --git a/src/libnest2d/src/libnest2d.cpp b/src/libnest2d/src/libnest2d.cpp new file mode 100644 index 0000000000..0214587874 --- /dev/null +++ b/src/libnest2d/src/libnest2d.cpp @@ -0,0 +1,23 @@ +#include + +namespace libnest2d { + +template class Nester; +template class Nester; + +template PackGroup nest(std::vector::iterator from, + std::vector::iterator to, + const Box& bin, + Coord dist = 0, + const NfpPlacer::Config& pconf, + const FirstFitSelection::Config& sconf); + +template PackGroup nest(std::vector::iterator from, + std::vector::iterator to, + const Box& bin, + ProgressFunction prg, + StopCondition scond, + Coord dist = 0, + const NfpPlacer::Config& pconf, + const FirstFitSelection::Config& sconf); +} diff --git a/src/libnest2d/tests/CMakeLists.txt b/src/libnest2d/tests/CMakeLists.txt index fc3cd309dd..1b7d8e3ae6 100644 --- a/src/libnest2d/tests/CMakeLists.txt +++ b/src/libnest2d/tests/CMakeLists.txt @@ -49,7 +49,12 @@ add_executable(tests_clipper_nlopt target_link_libraries(tests_clipper_nlopt libnest2d ${GTEST_LIBS_TO_LINK} ) -target_include_directories(tests_clipper_nlopt PRIVATE BEFORE - ${GTEST_INCLUDE_DIRS}) +target_include_directories(tests_clipper_nlopt PRIVATE BEFORE ${GTEST_INCLUDE_DIRS}) + +if(NOT LIBNEST2D_HEADER_ONLY) + target_link_libraries(tests_clipper_nlopt ${LIBNAME}) +else() + target_link_libraries(tests_clipper_nlopt libnest2d) +endif() add_test(libnest2d_tests tests_clipper_nlopt) diff --git a/src/libnest2d/tests/test.cpp b/src/libnest2d/tests/test.cpp index 3b0eae1618..4a6691415f 100644 --- a/src/libnest2d/tests/test.cpp +++ b/src/libnest2d/tests/test.cpp @@ -3,203 +3,235 @@ #include #include "printer_parts.h" -#include +//#include #include "../tools/svgtools.hpp" +#include + +#if defined(_MSC_VER) && defined(__clang__) +#define BOOST_NO_CXX17_HDR_STRING_VIEW +#endif + +#include "boost/multiprecision/integer.hpp" +#include "boost/rational.hpp" + //#include "../tools/libnfpglue.hpp" //#include "../tools/nfp_svgnest_glue.hpp" +namespace libnest2d { +#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__) +using LargeInt = __int128; +#else +using LargeInt = boost::multiprecision::int128_t; +template<> struct _NumTag { using Type = ScalarTag; }; +#endif +template struct _NumTag> { using Type = RationalTag; }; + +namespace nfp { + +template +struct NfpImpl +{ + NfpResult operator()(const S &sh, const S &other) + { + return nfpConvexOnly>(sh, other); + } +}; + +} +} + std::vector& prusaParts() { static std::vector ret; - + if(ret.empty()) { ret.reserve(PRINTER_PART_POLYGONS.size()); for(auto& inp : PRINTER_PART_POLYGONS) ret.emplace_back(inp); } - + return ret; } TEST(BasicFunctionality, Angles) { - + using namespace libnest2d; - + Degrees deg(180); Radians rad(deg); Degrees deg2(rad); - + ASSERT_DOUBLE_EQ(rad, Pi); ASSERT_DOUBLE_EQ(deg, 180); ASSERT_DOUBLE_EQ(deg2, 180); - ASSERT_DOUBLE_EQ(rad, (Radians) deg); - ASSERT_DOUBLE_EQ( (Degrees) rad, deg); - + ASSERT_DOUBLE_EQ(rad, Radians(deg)); + ASSERT_DOUBLE_EQ( Degrees(rad), deg); + ASSERT_TRUE(rad == deg); - + Segment seg = {{0, 0}, {12, -10}}; - + ASSERT_TRUE(Degrees(seg.angleToXaxis()) > 270 && Degrees(seg.angleToXaxis()) < 360); - + seg = {{0, 0}, {12, 10}}; - + ASSERT_TRUE(Degrees(seg.angleToXaxis()) > 0 && Degrees(seg.angleToXaxis()) < 90); - + seg = {{0, 0}, {-12, 10}}; - + ASSERT_TRUE(Degrees(seg.angleToXaxis()) > 90 && Degrees(seg.angleToXaxis()) < 180); - + seg = {{0, 0}, {-12, -10}}; - + ASSERT_TRUE(Degrees(seg.angleToXaxis()) > 180 && Degrees(seg.angleToXaxis()) < 270); - + seg = {{0, 0}, {1, 0}}; - + ASSERT_DOUBLE_EQ(Degrees(seg.angleToXaxis()), 0); - + seg = {{0, 0}, {0, 1}}; - + ASSERT_DOUBLE_EQ(Degrees(seg.angleToXaxis()), 90); - - + + seg = {{0, 0}, {-1, 0}}; - + ASSERT_DOUBLE_EQ(Degrees(seg.angleToXaxis()), 180); - - + + seg = {{0, 0}, {0, -1}}; - + ASSERT_DOUBLE_EQ(Degrees(seg.angleToXaxis()), 270); - + } // Simple test, does not use gmock TEST(BasicFunctionality, creationAndDestruction) { using namespace libnest2d; - + Item sh = { {0, 0}, {1, 0}, {1, 1}, {0, 1} }; - + ASSERT_EQ(sh.vertexCount(), 4u); - + Item sh2 ({ {0, 0}, {1, 0}, {1, 1}, {0, 1} }); - + ASSERT_EQ(sh2.vertexCount(), 4u); - + // copy Item sh3 = sh2; - + ASSERT_EQ(sh3.vertexCount(), 4u); - + sh2 = {}; - + ASSERT_EQ(sh2.vertexCount(), 0u); ASSERT_EQ(sh3.vertexCount(), 4u); - + } TEST(GeometryAlgorithms, boundingCircle) { using namespace libnest2d; using placers::boundingCircle; - + PolygonImpl p = {{{0, 10}, {10, 0}, {0, -10}, {0, 10}}, {}}; Circle c = boundingCircle(p); - + ASSERT_EQ(c.center().X, 0); ASSERT_EQ(c.center().Y, 0); ASSERT_DOUBLE_EQ(c.radius(), 10); - + shapelike::translate(p, PointImpl{10, 10}); c = boundingCircle(p); - + ASSERT_EQ(c.center().X, 10); ASSERT_EQ(c.center().Y, 10); ASSERT_DOUBLE_EQ(c.radius(), 10); - + auto parts = prusaParts(); - + int i = 0; for(auto& part : parts) { c = boundingCircle(part.transformedShape()); if(std::isnan(c.radius())) std::cout << "fail: radius is nan" << std::endl; - + else for(auto v : shapelike::contour(part.transformedShape()) ) { - auto d = pointlike::distance(v, c.center()); - if(d > c.radius() ) { - auto e = std::abs( 1.0 - d/c.radius()); - ASSERT_LE(e, 1e-3); + auto d = pointlike::distance(v, c.center()); + if(d > c.radius() ) { + auto e = std::abs( 1.0 - d/c.radius()); + ASSERT_LE(e, 1e-3); + } } - } i++; } - + } TEST(GeometryAlgorithms, Distance) { using namespace libnest2d; - + Point p1 = {0, 0}; - + Point p2 = {10, 0}; Point p3 = {10, 10}; - + ASSERT_DOUBLE_EQ(pointlike::distance(p1, p2), 10); ASSERT_DOUBLE_EQ(pointlike::distance(p1, p3), sqrt(200)); - + Segment seg(p1, p3); - - ASSERT_DOUBLE_EQ(pointlike::distance(p2, seg), 7.0710678118654755); - + + // ASSERT_DOUBLE_EQ(pointlike::distance(p2, seg), 7.0710678118654755); + auto result = pointlike::horizontalDistance(p2, seg); - - auto check = [](Coord val, Coord expected) { - if(std::is_floating_point::value) + + auto check = [](TCompute val, TCompute expected) { + if(std::is_floating_point>::value) ASSERT_DOUBLE_EQ(static_cast(val), static_cast(expected)); else ASSERT_EQ(val, expected); }; - + ASSERT_TRUE(result.second); check(result.first, 10); - + result = pointlike::verticalDistance(p2, seg); ASSERT_TRUE(result.second); check(result.first, -10); - + result = pointlike::verticalDistance(Point{10, 20}, seg); ASSERT_TRUE(result.second); check(result.first, 10); - - + + Point p4 = {80, 0}; Segment seg2 = { {0, 0}, {0, 40} }; - + result = pointlike::horizontalDistance(p4, seg2); - + ASSERT_TRUE(result.second); check(result.first, 80); - + result = pointlike::verticalDistance(p4, seg2); // Point should not be related to the segment ASSERT_FALSE(result.second); - + } TEST(GeometryAlgorithms, Area) { using namespace libnest2d; - + Rectangle rect(10, 10); - + ASSERT_EQ(rect.area(), 100); - + Rectangle rect2 = {100, 100}; - + ASSERT_EQ(rect2.area(), 10000); - + Item item = { {61, 97}, {70, 151}, @@ -210,33 +242,33 @@ TEST(GeometryAlgorithms, Area) { {61, 77}, {61, 97} }; - + ASSERT_TRUE(shapelike::area(item.transformedShape()) > 0 ); } TEST(GeometryAlgorithms, IsPointInsidePolygon) { using namespace libnest2d; - + Rectangle rect(10, 10); - + Point p = {1, 1}; - + ASSERT_TRUE(rect.isInside(p)); - + p = {11, 11}; - + ASSERT_FALSE(rect.isInside(p)); - - + + p = {11, 12}; - + ASSERT_FALSE(rect.isInside(p)); - - + + p = {3, 3}; - + ASSERT_TRUE(rect.isInside(p)); - + } //TEST(GeometryAlgorithms, Intersections) { @@ -262,21 +294,21 @@ TEST(GeometryAlgorithms, LeftAndDownPolygon) { using namespace libnest2d; using namespace libnest2d; - + Box bin(100, 100); BottomLeftPlacer placer(bin); - + Item item = {{70, 75}, {88, 60}, {65, 50}, {60, 30}, {80, 20}, {42, 20}, {35, 35}, {35, 55}, {40, 75}, {70, 75}}; - + Item leftControl = { {40, 75}, - {35, 55}, - {35, 35}, - {42, 20}, - {0, 20}, - {0, 75}, - {40, 75}}; - + {35, 55}, + {35, 35}, + {42, 20}, + {0, 20}, + {0, 75}, + {40, 75}}; + Item downControl = {{88, 60}, {88, 0}, {35, 0}, @@ -286,22 +318,22 @@ TEST(GeometryAlgorithms, LeftAndDownPolygon) {60, 30}, {65, 50}, {88, 60}}; - + Item leftp(placer.leftPoly(item)); - + ASSERT_TRUE(shapelike::isValid(leftp.rawShape()).first); ASSERT_EQ(leftp.vertexCount(), leftControl.vertexCount()); - + for(unsigned long i = 0; i < leftControl.vertexCount(); i++) { ASSERT_EQ(getX(leftp.vertex(i)), getX(leftControl.vertex(i))); ASSERT_EQ(getY(leftp.vertex(i)), getY(leftControl.vertex(i))); } - + Item downp(placer.downPoly(item)); - + ASSERT_TRUE(shapelike::isValid(downp.rawShape()).first); ASSERT_EQ(downp.vertexCount(), downControl.vertexCount()); - + for(unsigned long i = 0; i < downControl.vertexCount(); i++) { ASSERT_EQ(getX(downp.vertex(i)), getX(downControl.vertex(i))); ASSERT_EQ(getY(downp.vertex(i)), getY(downControl.vertex(i))); @@ -312,7 +344,7 @@ TEST(GeometryAlgorithms, LeftAndDownPolygon) TEST(GeometryAlgorithms, ArrangeRectanglesTight) { using namespace libnest2d; - + std::vector rects = { {80, 80}, {60, 90}, @@ -334,37 +366,51 @@ TEST(GeometryAlgorithms, ArrangeRectanglesTight) {5, 5}, {5, 5}, {20, 20} }; + + Box bin(210, 250, {105, 125}); + + ASSERT_EQ(bin.width(), 210); + ASSERT_EQ(bin.height(), 250); + ASSERT_EQ(getX(bin.center()), 105); + ASSERT_EQ(getY(bin.center()), 125); + + _Nester arrange(bin); + + arrange.execute(rects.begin(), rects.end()); + auto max_group = std::max_element(rects.begin(), rects.end(), + [](const Item &i1, const Item &i2) { + return i1.binId() < i2.binId(); + }); - Nester arrange(Box(210, 250)); + int groups = max_group == rects.end() ? 0 : max_group->binId() + 1; - auto groups = arrange(rects.begin(), rects.end()); - - ASSERT_EQ(groups.size(), 1u); - ASSERT_EQ(groups[0].size(), rects.size()); + ASSERT_EQ(groups, 1u); + ASSERT_TRUE( + std::all_of(rects.begin(), rects.end(), [](const Rectangle &itm) { + return itm.binId() != BIN_ID_UNSET; + })); // check for no intersections, no containment: - - for(auto result : groups) { - bool valid = true; - for(Item& r1 : result) { - for(Item& r2 : result) { - if(&r1 != &r2 ) { - valid = !Item::intersects(r1, r2) || Item::touches(r1, r2); - valid = (valid && !r1.isInside(r2) && !r2.isInside(r1)); - ASSERT_TRUE(valid); - } + + bool valid = true; + for(Item& r1 : rects) { + for(Item& r2 : rects) { + if(&r1 != &r2 ) { + valid = !Item::intersects(r1, r2) || Item::touches(r1, r2); + ASSERT_TRUE(valid); + valid = (valid && !r1.isInside(r2) && !r2.isInside(r1)); + ASSERT_TRUE(valid); } } } - } TEST(GeometryAlgorithms, ArrangeRectanglesLoose) { using namespace libnest2d; - -// std::vector rects = { {40, 40}, {10, 10}, {20, 20} }; + + // std::vector rects = { {40, 40}, {10, 10}, {20, 20} }; std::vector rects = { {80, 80}, {60, 90}, @@ -386,22 +432,37 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose) {5, 5}, {5, 5}, {20, 20} }; - + + Box bin(210, 250, {105, 125}); + + ASSERT_EQ(bin.width(), 210); + ASSERT_EQ(bin.height(), 250); + ASSERT_EQ(getX(bin.center()), 105); + ASSERT_EQ(getY(bin.center()), 125); + Coord min_obj_distance = 5; - - Nester arrange(Box(210, 250), - min_obj_distance); - - auto groups = arrange(rects.begin(), rects.end()); - - ASSERT_EQ(groups.size(), 1u); - ASSERT_EQ(groups[0].size(), rects.size()); - + + _Nester arrange(bin, min_obj_distance); + + arrange.execute(rects.begin(), rects.end()); + + auto max_group = std::max_element(rects.begin(), rects.end(), + [](const Item &i1, const Item &i2) { + return i1.binId() < i2.binId(); + }); + + size_t groups = max_group == rects.end() ? 0 : max_group->binId() + 1; + + ASSERT_EQ(groups, 1u); + ASSERT_TRUE( + std::all_of(rects.begin(), rects.end(), [](const Rectangle &itm) { + return itm.binId() != BIN_ID_UNSET; + })); + // check for no intersections, no containment: - auto result = groups[0]; bool valid = true; - for(Item& r1 : result) { - for(Item& r2 : result) { + for(Item& r1 : rects) { + for(Item& r2 : rects) { if(&r1 != &r2 ) { valid = !Item::intersects(r1, r2); valid = (valid && !r1.isInside(r2) && !r2.isInside(r1)); @@ -409,76 +470,76 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose) } } } - + } namespace { using namespace libnest2d; -template +template void exportSVG(std::vector>& result, const Bin& bin, int idx = 0) { - - + + std::string loc = "out"; - + static std::string svg_header = -R"raw( + R"raw( )raw"; - + int i = idx; auto r = result; -// for(auto r : result) { - std::fstream out(loc + std::to_string(i) + ".svg", std::fstream::out); - if(out.is_open()) { - out << svg_header; - Item rbin( Rectangle(bin.width(), bin.height()) ); - for(unsigned i = 0; i < rbin.vertexCount(); i++) { - auto v = rbin.vertex(i); - setY(v, -getY(v)/SCALE + 500 ); - setX(v, getX(v)/SCALE); - rbin.setVertex(i, v); - } - out << shapelike::serialize(rbin.rawShape()) << std::endl; - for(Item& sh : r) { - Item tsh(sh.transformedShape()); - for(unsigned i = 0; i < tsh.vertexCount(); i++) { - auto v = tsh.vertex(i); - setY(v, -getY(v)/SCALE + 500); - setX(v, getX(v)/SCALE); - tsh.setVertex(i, v); - } - out << shapelike::serialize(tsh.rawShape()) << std::endl; - } - out << "\n" << std::endl; + // for(auto r : result) { + std::fstream out(loc + std::to_string(i) + ".svg", std::fstream::out); + if(out.is_open()) { + out << svg_header; + Item rbin( Rectangle(bin.width(), bin.height()) ); + for(unsigned i = 0; i < rbin.vertexCount(); i++) { + auto v = rbin.vertex(i); + setY(v, -getY(v)/SCALE + 500 ); + setX(v, getX(v)/SCALE); + rbin.setVertex(i, v); } - out.close(); - -// i++; -// } + out << shapelike::serialize(rbin.rawShape()) << std::endl; + for(Item& sh : r) { + Item tsh(sh.transformedShape()); + for(unsigned i = 0; i < tsh.vertexCount(); i++) { + auto v = tsh.vertex(i); + setY(v, -getY(v)/SCALE + 500); + setX(v, getX(v)/SCALE); + tsh.setVertex(i, v); + } + out << shapelike::serialize(tsh.rawShape()) << std::endl; + } + out << "\n" << std::endl; + } + out.close(); + + // i++; + // } } } TEST(GeometryAlgorithms, BottomLeftStressTest) { using namespace libnest2d; - + const Coord SCALE = 1000000; auto& input = prusaParts(); - + Box bin(210*SCALE, 250*SCALE); BottomLeftPlacer placer(bin); - + auto it = input.begin(); auto next = it; int i = 0; while(it != input.end() && ++next != input.end()) { placer.pack(*it); placer.pack(*next); - + auto result = placer.getItems(); bool valid = true; - + if(result.size() == 2) { Item& r1 = result[0]; Item& r2 = result[1]; @@ -493,13 +554,46 @@ TEST(GeometryAlgorithms, BottomLeftStressTest) { std::cout << "something went terribly wrong!" << std::endl; FAIL(); } - + placer.clearItems(); it++; i++; } } +TEST(GeometryAlgorithms, convexHull) { + using namespace libnest2d; + + ClipperLib::Path poly = PRINTER_PART_POLYGONS[0]; + + auto chull = sl::convexHull(poly); + + ASSERT_EQ(chull.size(), poly.size()); +} + + +TEST(GeometryAlgorithms, NestTest) { + std::vector input = prusaParts(); + + libnest2d::nest(input, Box(250000000, 210000000), [](unsigned cnt) { + std::cout << "parts left: " << cnt << std::endl; + }); + + auto max_binid_it = std::max_element(input.begin(), input.end(), + [](const Item &i1, const Item &i2) { + return i1.binId() < i2.binId(); + }); + + size_t bins = max_binid_it == input.end() ? 0 : max_binid_it->binId() + 1; + + ASSERT_EQ(bins, 2u); + + ASSERT_TRUE( + std::all_of(input.begin(), input.end(), [](const Item &itm) { + return itm.binId() != BIN_ID_UNSET; + })); +} + namespace { struct ItemPair { @@ -580,7 +674,7 @@ std::vector nfp_testdata = { {118, 101}, {117, 103}, {117, 107} - }, + }, { {102, 116}, {111, 126}, @@ -591,7 +685,7 @@ std::vector nfp_testdata = { {147, 84}, {102, 84}, {102, 116}, - } + } }, { { @@ -607,7 +701,7 @@ std::vector nfp_testdata = { {108, 70}, {99, 102}, {99, 122}, - }, + }, { {107, 124}, {128, 125}, @@ -624,7 +718,7 @@ std::vector nfp_testdata = { {108, 85}, {107, 86}, {107, 124}, - } + } }, { { @@ -639,7 +733,7 @@ std::vector nfp_testdata = { {132, 57}, {91, 98}, {91, 100}, - }, + }, { {101, 90}, {103, 98}, @@ -657,74 +751,74 @@ std::vector nfp_testdata = { {102, 87}, {101, 89}, {101, 90}, - } + } } }; -std::vector nfp_concave_testdata = { - { // ItemPair - { - { - {533726, 142141}, - {532359, 143386}, - {530141, 142155}, - {528649, 160091}, - {533659, 157607}, - {538669, 160091}, - {537178, 142155}, - {534959, 143386}, - {533726, 142141}, - } - }, - { - { - {118305, 11603}, - {118311, 26616}, - {113311, 26611}, - {109311, 29604}, - {109300, 44608}, - {109311, 49631}, - {113300, 52636}, - {118311, 52636}, - {118308, 103636}, - {223830, 103636}, - {236845, 90642}, - {236832, 11630}, - {232825, 11616}, - {210149, 11616}, - {211308, 13625}, - {209315, 17080}, - {205326, 17080}, - {203334, 13629}, - {204493, 11616}, - {118305, 11603}, - } - }, - } + std::vector nfp_concave_testdata = { + { // ItemPair + { + { + {533726, 142141}, + {532359, 143386}, + {530141, 142155}, + {528649, 160091}, + {533659, 157607}, + {538669, 160091}, + {537178, 142155}, + {534959, 143386}, + {533726, 142141}, + } + }, + { + { + {118305, 11603}, + {118311, 26616}, + {113311, 26611}, + {109311, 29604}, + {109300, 44608}, + {109311, 49631}, + {113300, 52636}, + {118311, 52636}, + {118308, 103636}, + {223830, 103636}, + {236845, 90642}, + {236832, 11630}, + {232825, 11616}, + {210149, 11616}, + {211308, 13625}, + {209315, 17080}, + {205326, 17080}, + {203334, 13629}, + {204493, 11616}, + {118305, 11603}, + } + }, + } }; template void testNfp(const std::vector& testdata) { using namespace libnest2d; - + Box bin(210*SCALE, 250*SCALE); - + int testcase = 0; - + auto& exportfun = exportSVG; - - auto onetest = [&](Item& orbiter, Item& stationary, unsigned testidx){ + + auto onetest = [&](Item& orbiter, Item& stationary, unsigned /*testidx*/){ testcase++; - + orbiter.translate({210*SCALE, 0}); - + auto&& nfp = nfp::noFitPolygon(stationary.rawShape(), orbiter.transformedShape()); - + placers::correctNfpPosition(nfp, stationary, orbiter); - + auto valid = shapelike::isValid(nfp.first); - + /*Item infp(nfp.first); if(!valid.first) { std::cout << "test instance: " << testidx << " " @@ -732,46 +826,46 @@ void testNfp(const std::vector& testdata) { std::vector> inp = {std::ref(infp)}; exportfun(inp, bin, testidx); }*/ - + ASSERT_TRUE(valid.first); - + Item infp(nfp.first); - + int i = 0; auto rorbiter = orbiter.transformedShape(); auto vo = nfp::referenceVertex(rorbiter); - + ASSERT_TRUE(stationary.isInside(infp)); - + for(auto v : infp) { auto dx = getX(v) - getX(vo); auto dy = getY(v) - getY(vo); - + Item tmp = orbiter; - + tmp.translate({dx, dy}); - + bool touching = Item::touches(tmp, stationary); - + if(!touching || !valid.first) { std::vector> inp = { std::ref(stationary), std::ref(tmp), std::ref(infp) }; - + exportfun(inp, bin, testcase*i++); } - + ASSERT_TRUE(touching); } }; - + unsigned tidx = 0; for(auto& td : testdata) { auto orbiter = td.orbiter; auto stationary = td.stationary; onetest(orbiter, stationary, tidx++); } - + tidx = 0; for(auto& td : testdata) { auto orbiter = td.stationary; @@ -791,19 +885,19 @@ TEST(GeometryAlgorithms, nfpConvexConvex) { TEST(GeometryAlgorithms, pointOnPolygonContour) { using namespace libnest2d; - + Rectangle input(10, 10); - + placers::EdgeCache ecache(input); - + auto first = *input.begin(); ASSERT_TRUE(getX(first) == getX(ecache.coords(0))); ASSERT_TRUE(getY(first) == getY(ecache.coords(0))); - + auto last = *std::prev(input.end()); ASSERT_TRUE(getX(last) == getX(ecache.coords(1.0))); ASSERT_TRUE(getY(last) == getY(ecache.coords(1.0))); - + for(int i = 0; i <= 100; i++) { auto v = ecache.coords(i*(0.01)); ASSERT_TRUE(shapelike::touches(v, input.transformedShape())); @@ -812,28 +906,119 @@ TEST(GeometryAlgorithms, pointOnPolygonContour) { TEST(GeometryAlgorithms, mergePileWithPolygon) { using namespace libnest2d; - + Rectangle rect1(10, 15); Rectangle rect2(15, 15); Rectangle rect3(20, 15); - + rect2.translate({10, 0}); rect3.translate({25, 0}); - - shapelike::Shapes pile; + + TMultiShape pile; pile.push_back(rect1.transformedShape()); pile.push_back(rect2.transformedShape()); - + auto result = nfp::merge(pile, rect3.transformedShape()); - + ASSERT_EQ(result.size(), 1); - + Rectangle ref(45, 15); - + ASSERT_EQ(shapelike::area(result.front()), ref.area()); } -int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); +namespace { + +long double refMinAreaBox(const PolygonImpl& p) { + + auto it = sl::cbegin(p), itx = std::next(it); + + long double min_area = std::numeric_limits::max(); + + + auto update_min = [&min_area, &it, &itx, &p]() { + Segment s(*it, *itx); + + PolygonImpl rotated = p; + sl::rotate(rotated, -s.angleToXaxis()); + auto bb = sl::boundingBox(rotated); + auto area = cast(sl::area(bb)); + if(min_area > area) min_area = area; + }; + + while(itx != sl::cend(p)) { + update_min(); + ++it; ++itx; + } + + it = std::prev(sl::cend(p)); itx = sl::cbegin(p); + update_min(); + + return min_area; +} + +template struct BoostGCD { + T operator()(const T &a, const T &b) { return boost::gcd(a, b); } +}; + +using Unit = int64_t; +using Ratio = boost::rational; + +} + +TEST(RotatingCalipers, MinAreaBBCClk) { + auto u = [](ClipperLib::cInt n) { return n*1000000; }; + PolygonImpl poly({ {u(0), u(0)}, {u(4), u(1)}, {u(2), u(4)}}); + + long double arearef = refMinAreaBox(poly); + long double area = minAreaBoundingBox(poly).area(); + + ASSERT_LE(std::abs(area - arearef), 500e6 ); +} + +TEST(RotatingCalipers, AllPrusaMinBB) { + // /size_t idx = 0; + long double err_epsilon = 500e6l; + + for(ClipperLib::Path rinput : PRINTER_PART_POLYGONS) { + // ClipperLib::Path rinput = PRINTER_PART_POLYGONS[idx]; + // rinput.pop_back(); + // std::reverse(rinput.begin(), rinput.end()); + + // PolygonImpl poly(removeCollinearPoints(rinput, 1000000)); + PolygonImpl poly(rinput); + + long double arearef = refMinAreaBox(poly); + auto bb = minAreaBoundingBox(rinput); + long double area = cast(bb.area()); + + bool succ = std::abs(arearef - area) < err_epsilon; + // std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: " +// << arearef << " actual: " << area << std::endl; + + ASSERT_TRUE(succ); + } + + for(ClipperLib::Path rinput : STEGOSAUR_POLYGONS) { + rinput.pop_back(); + std::reverse(rinput.begin(), rinput.end()); + + PolygonImpl poly(removeCollinearPoints(rinput, 1000000)); + + long double arearef = refMinAreaBox(poly); + auto bb = minAreaBoundingBox(poly); + long double area = cast(bb.area()); + + + bool succ = std::abs(arearef - area) < err_epsilon; + // std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: " +// << arearef << " actual: " << area << std::endl; + + ASSERT_TRUE(succ); + } +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); } diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp new file mode 100644 index 0000000000..34e07302a0 --- /dev/null +++ b/src/libslic3r/Arrange.cpp @@ -0,0 +1,698 @@ +#include "Arrange.hpp" +#include "Geometry.hpp" +#include "SVG.hpp" +#include "MTUtils.hpp" + +#include +#include +#include +#include + +#include +#include + +#include + +#if defined(_MSC_VER) && defined(__clang__) +#define BOOST_NO_CXX17_HDR_STRING_VIEW +#endif + +#include +#include + +namespace libnest2d { +#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__) +using LargeInt = __int128; +#else +using LargeInt = boost::multiprecision::int128_t; +template<> struct _NumTag +{ + using Type = ScalarTag; +}; +#endif + +template struct _NumTag> +{ + using Type = RationalTag; +}; + +namespace nfp { + +template struct NfpImpl +{ + NfpResult operator()(const S &sh, const S &other) + { + return nfpConvexOnly>(sh, other); + } +}; + +} // namespace nfp +} // namespace libnest2d + +namespace Slic3r { + +template, int...EigenArgs> +inline SLIC3R_CONSTEXPR Eigen::Matrix unscaled( + const ClipperLib::IntPoint &v) SLIC3R_NOEXCEPT +{ + return Eigen::Matrix{unscaled(v.X), + unscaled(v.Y)}; +} + +namespace arrangement { + +using namespace libnest2d; +namespace clppr = ClipperLib; + +// Get the libnest2d types for clipper backend +using Item = _Item; +using Box = _Box; +using Circle = _Circle; +using Segment = _Segment; +using MultiPolygon = TMultiShape; + +// Summon the spatial indexing facilities from boost +namespace bgi = boost::geometry::index; +using SpatElement = std::pair; +using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; +using ItemGroup = std::vector>; + +// A coefficient used in separating bigger items and smaller items. +const double BIG_ITEM_TRESHOLD = 0.02; + +// Fill in the placer algorithm configuration with values carefully chosen for +// Slic3r. +template +void fillConfig(PConf& pcfg) { + + // Align the arranged pile into the center of the bin + pcfg.alignment = PConf::Alignment::CENTER; + + // Start placing the items from the center of the print bed + pcfg.starting_point = PConf::Alignment::CENTER; + + // TODO cannot use rotations until multiple objects of same geometry can + // handle different rotations. + pcfg.rotations = { 0.0 }; + + // The accuracy of optimization. + // Goes from 0.0 to 1.0 and scales performance as well + pcfg.accuracy = 0.65f; + + // Allow parallel execution. + pcfg.parallel = true; +} + +// Apply penalty to object function result. This is used only when alignment +// after arrange is explicitly disabled (PConfig::Alignment::DONT_ALIGN) +double fixed_overfit(const std::tuple& result, const Box &binbb) +{ + double score = std::get<0>(result); + Box pilebb = std::get<1>(result); + Box fullbb = sl::boundingBox(pilebb, binbb); + auto diff = double(fullbb.area()) - binbb.area(); + if(diff > 0) score += diff; + + return score; +} + +// A class encapsulating the libnest2d Nester class and extending it with other +// management and spatial index structures for acceleration. +template +class AutoArranger { +public: + // Useful type shortcuts... + using Placer = typename placers::_NofitPolyPlacer; + using Selector = selections::_FirstFitSelection; + using Packer = _Nester; + using PConfig = typename Packer::PlacementConfig; + using Distance = TCoord; + +protected: + Packer m_pck; + PConfig m_pconf; // Placement configuration + TBin m_bin; + double m_bin_area; + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4244) +#pragma warning(disable: 4267) +#endif + SpatIndex m_rtree; // spatial index for the normal (bigger) objects + SpatIndex m_smallsrtree; // spatial index for only the smaller items +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + double m_norm; // A coefficient to scale distances + MultiPolygon m_merged_pile; // The already merged pile (vector of items) + Box m_pilebb; // The bounding box of the merged pile. + ItemGroup m_remaining; // Remaining items + ItemGroup m_items; // allready packed items + size_t m_item_count = 0; // Number of all items to be packed + + template ArithmeticOnly norm(T val) + { + return double(val) / m_norm; + } + + // This is "the" object function which is evaluated many times for each + // vertex (decimated with the accuracy parameter) of each object. + // Therefore it is upmost crucial for this function to be as efficient + // as it possibly can be but at the same time, it has to provide + // reasonable results. + std::tuple + objfunc(const Item &item, const clppr::IntPoint &bincenter) + { + const double bin_area = m_bin_area; + const SpatIndex& spatindex = m_rtree; + const SpatIndex& smalls_spatindex = m_smallsrtree; + + // We will treat big items (compared to the print bed) differently + auto isBig = [bin_area](double a) { + return a/bin_area > BIG_ITEM_TRESHOLD ; + }; + + // Candidate item bounding box + auto ibb = item.boundingBox(); + + // Calculate the full bounding box of the pile with the candidate item + auto fullbb = sl::boundingBox(m_pilebb, ibb); + + // The bounding box of the big items (they will accumulate in the center + // of the pile + Box bigbb; + if(spatindex.empty()) bigbb = fullbb; + else { + auto boostbb = spatindex.bounds(); + boost::geometry::convert(boostbb, bigbb); + } + + // Will hold the resulting score + double score = 0; + + // Density is the pack density: how big is the arranged pile + double density = 0; + + // Distinction of cases for the arrangement scene + enum e_cases { + // This branch is for big items in a mixed (big and small) scene + // OR for all items in a small-only scene. + BIG_ITEM, + + // This branch is for the last big item in a mixed scene + LAST_BIG_ITEM, + + // For small items in a mixed scene. + SMALL_ITEM + } compute_case; + + bool bigitems = isBig(item.area()) || spatindex.empty(); + if(bigitems && !m_remaining.empty()) compute_case = BIG_ITEM; + else if (bigitems && m_remaining.empty()) compute_case = LAST_BIG_ITEM; + else compute_case = SMALL_ITEM; + + switch (compute_case) { + case BIG_ITEM: { + const clppr::IntPoint& minc = ibb.minCorner(); // bottom left corner + const clppr::IntPoint& maxc = ibb.maxCorner(); // top right corner + + // top left and bottom right corners + clppr::IntPoint top_left{getX(minc), getY(maxc)}; + clppr::IntPoint bottom_right{getX(maxc), getY(minc)}; + + // Now the distance of the gravity center will be calculated to the + // five anchor points and the smallest will be chosen. + std::array dists; + auto cc = fullbb.center(); // The gravity center + dists[0] = pl::distance(minc, cc); + dists[1] = pl::distance(maxc, cc); + dists[2] = pl::distance(ibb.center(), cc); + dists[3] = pl::distance(top_left, cc); + dists[4] = pl::distance(bottom_right, cc); + + // The smalles distance from the arranged pile center: + double dist = norm(*(std::min_element(dists.begin(), dists.end()))); + double bindist = norm(pl::distance(ibb.center(), bincenter)); + dist = 0.8 * dist + 0.2 * bindist; + + // Prepare a variable for the alignment score. + // This will indicate: how well is the candidate item + // aligned with its neighbors. We will check the alignment + // with all neighbors and return the score for the best + // alignment. So it is enough for the candidate to be + // aligned with only one item. + auto alignment_score = 1.0; + + auto query = bgi::intersects(ibb); + auto& index = isBig(item.area()) ? spatindex : smalls_spatindex; + + // Query the spatial index for the neighbors + std::vector result; + result.reserve(index.size()); + + index.query(query, std::back_inserter(result)); + + // now get the score for the best alignment + for(auto& e : result) { + auto idx = e.second; + Item& p = m_items[idx]; + auto parea = p.area(); + if(std::abs(1.0 - parea/item.area()) < 1e-6) { + auto bb = sl::boundingBox(p.boundingBox(), ibb); + auto bbarea = bb.area(); + auto ascore = 1.0 - (item.area() + parea)/bbarea; + + if(ascore < alignment_score) alignment_score = ascore; + } + } + + density = std::sqrt(norm(fullbb.width()) * norm(fullbb.height())); + double R = double(m_remaining.size()) / m_item_count; + + // The final mix of the score is the balance between the + // distance from the full pile center, the pack density and + // the alignment with the neighbors + if (result.empty()) + score = 0.50 * dist + 0.50 * density; + else + score = R * 0.60 * dist + + (1.0 - R) * 0.20 * density + + 0.20 * alignment_score; + + break; + } + case LAST_BIG_ITEM: { + score = norm(pl::distance(ibb.center(), m_pilebb.center())); + break; + } + case SMALL_ITEM: { + // Here there are the small items that should be placed around the + // already processed bigger items. + // No need to play around with the anchor points, the center will be + // just fine for small items + score = norm(pl::distance(ibb.center(), bigbb.center())); + break; + } + } + + return std::make_tuple(score, fullbb); + } + + std::function get_objfn(); + +public: + AutoArranger(const TBin & bin, + Distance dist, + std::function progressind, + std::function stopcond) + : m_pck(bin, dist) + , m_bin(bin) + , m_bin_area(sl::area(bin)) + , m_norm(std::sqrt(m_bin_area)) + { + fillConfig(m_pconf); + + // Set up a callback that is called just before arranging starts + // This functionality is provided by the Nester class (m_pack). + m_pconf.before_packing = + [this](const MultiPolygon& merged_pile, // merged pile + const ItemGroup& items, // packed items + const ItemGroup& remaining) // future items to be packed + { + m_items = items; + m_merged_pile = merged_pile; + m_remaining = remaining; + + m_pilebb = sl::boundingBox(merged_pile); + + m_rtree.clear(); + m_smallsrtree.clear(); + + // We will treat big items (compared to the print bed) differently + auto isBig = [this](double a) { + return a / m_bin_area > BIG_ITEM_TRESHOLD ; + }; + + for(unsigned idx = 0; idx < items.size(); ++idx) { + Item& itm = items[idx]; + if(isBig(itm.area())) m_rtree.insert({itm.boundingBox(), idx}); + m_smallsrtree.insert({itm.boundingBox(), idx}); + } + }; + + m_pconf.object_function = get_objfn(); + + if (progressind) m_pck.progressIndicator(progressind); + if (stopcond) m_pck.stopCondition(stopcond); + + m_pck.configure(m_pconf); + } + + template inline void operator()(It from, It to) { + m_rtree.clear(); + m_item_count += size_t(to - from); + m_pck.execute(from, to); + m_item_count = 0; + } + + inline void preload(std::vector& fixeditems) { + m_pconf.alignment = PConfig::Alignment::DONT_ALIGN; + auto bb = sl::boundingBox(m_bin); + auto bbcenter = bb.center(); + m_pconf.object_function = [this, bb, bbcenter](const Item &item) { + return fixed_overfit(objfunc(item, bbcenter), bb); + }; + + // Build the rtree for queries to work + + for(unsigned idx = 0; idx < fixeditems.size(); ++idx) { + Item& itm = fixeditems[idx]; + itm.markAsFixed(); + } + + m_pck.configure(m_pconf); + m_item_count += fixeditems.size(); + } +}; + +template<> std::function AutoArranger::get_objfn() +{ + auto bincenter = m_bin.center(); + + return [this, bincenter](const Item &itm) { + auto result = objfunc(itm, bincenter); + + double score = std::get<0>(result); + auto& fullbb = std::get<1>(result); + + double miss = Placer::overfit(fullbb, m_bin); + miss = miss > 0? miss : 0; + score += miss*miss; + + return score; + }; +} + +template<> std::function AutoArranger::get_objfn() +{ + auto bincenter = m_bin.center(); + return [this, bincenter](const Item &item) { + + auto result = objfunc(item, bincenter); + + double score = std::get<0>(result); + + auto isBig = [this](const Item& itm) { + return itm.area() / m_bin_area > BIG_ITEM_TRESHOLD ; + }; + + if(isBig(item)) { + auto mp = m_merged_pile; + mp.push_back(item.transformedShape()); + auto chull = sl::convexHull(mp); + double miss = Placer::overfit(chull, m_bin); + if(miss < 0) miss = 0; + score += miss*miss; + } + + return score; + }; +} + +// Specialization for a generalized polygon. +// Warning: this is unfinished business. It may or may not work. +template<> +std::function AutoArranger::get_objfn() +{ + auto bincenter = sl::boundingBox(m_bin).center(); + return [this, bincenter](const Item &item) { + return std::get<0>(objfunc(item, bincenter)); + }; +} + +inline Circle to_lnCircle(const CircleBed& circ) { + return Circle({circ.center()(0), circ.center()(1)}, circ.radius()); +} + +// Get the type of bed geometry from a simple vector of points. +void BedShapeHint::reset(BedShapes type) +{ + if (m_type != type) { + if (m_type == bsIrregular) + m_bed.polygon.Slic3r::Polyline::~Polyline(); + else if (type == bsIrregular) + ::new (&m_bed.polygon) Polyline(); + } + + m_type = type; +} + +BedShapeHint::BedShapeHint(const Polyline &bed) { + auto x = [](const Point& p) { return p(X); }; + auto y = [](const Point& p) { return p(Y); }; + + auto width = [x](const BoundingBox& box) { + return x(box.max) - x(box.min); + }; + + auto height = [y](const BoundingBox& box) { + return y(box.max) - y(box.min); + }; + + auto area = [&width, &height](const BoundingBox& box) { + double w = width(box); + double h = height(box); + return w * h; + }; + + auto poly_area = [](Polyline p) { + Polygon pp; pp.points.reserve(p.points.size() + 1); + pp.points = std::move(p.points); + pp.points.emplace_back(pp.points.front()); + return std::abs(pp.area()); + }; + + auto distance_to = [x, y](const Point& p1, const Point& p2) { + double dx = x(p2) - x(p1); + double dy = y(p2) - y(p1); + return std::sqrt(dx*dx + dy*dy); + }; + + auto bb = bed.bounding_box(); + + auto isCircle = [bb, distance_to](const Polyline& polygon) { + auto center = bb.center(); + std::vector vertex_distances; + double avg_dist = 0; + for (auto pt: polygon.points) + { + double distance = distance_to(center, pt); + vertex_distances.push_back(distance); + avg_dist += distance; + } + + avg_dist /= vertex_distances.size(); + + CircleBed ret(center, avg_dist); + for(auto el : vertex_distances) + { + if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) { + ret = CircleBed(); + break; + } + } + + return ret; + }; + + auto parea = poly_area(bed); + + if( (1.0 - parea/area(bb)) < 1e-3 ) { + m_type = BedShapes::bsBox; + m_bed.box = bb; + } + else if(auto c = isCircle(bed)) { + m_type = BedShapes::bsCircle; + m_bed.circ = c; + } else { + assert(m_type != BedShapes::bsIrregular); + m_type = BedShapes::bsIrregular; + ::new (&m_bed.polygon) Polyline(bed); + } +} + +BedShapeHint &BedShapeHint::operator=(BedShapeHint &&cpy) +{ + reset(cpy.m_type); + + switch(m_type) { + case bsBox: m_bed.box = std::move(cpy.m_bed.box); break; + case bsCircle: m_bed.circ = std::move(cpy.m_bed.circ); break; + case bsIrregular: m_bed.polygon = std::move(cpy.m_bed.polygon); break; + case bsInfinite: m_bed.infbed = std::move(cpy.m_bed.infbed); break; + case bsUnknown: break; + } + + return *this; +} + +BedShapeHint &BedShapeHint::operator=(const BedShapeHint &cpy) +{ + reset(cpy.m_type); + + switch(m_type) { + case bsBox: m_bed.box = cpy.m_bed.box; break; + case bsCircle: m_bed.circ = cpy.m_bed.circ; break; + case bsIrregular: m_bed.polygon = cpy.m_bed.polygon; break; + case bsInfinite: m_bed.infbed = cpy.m_bed.infbed; break; + case bsUnknown: break; + } + + return *this; +} + +template // Arrange for arbitrary bin type +void _arrange( + std::vector & shapes, + std::vector & excludes, + const BinT & bin, + coord_t minobjd, + std::function prind, + std::function stopfn) +{ + // Integer ceiling the min distance from the bed perimeters + coord_t md = minobjd - 2 * scaled(0.1 + EPSILON); + md = (md % 2) ? md / 2 + 1 : md / 2; + + auto corrected_bin = bin; + sl::offset(corrected_bin, md); + + AutoArranger arranger{corrected_bin, 0, prind, stopfn}; + + auto infl = coord_t(std::ceil(minobjd / 2.0)); + for (Item& itm : shapes) itm.inflate(infl); + for (Item& itm : excludes) itm.inflate(infl); + + auto it = excludes.begin(); + while (it != excludes.end()) + sl::isInside(it->transformedShape(), corrected_bin) ? + ++it : it = excludes.erase(it); + + // If there is something on the plate + if (!excludes.empty()) arranger.preload(excludes); + + std::vector> inp; + inp.reserve(shapes.size() + excludes.size()); + for (auto &itm : shapes ) inp.emplace_back(itm); + for (auto &itm : excludes) inp.emplace_back(itm); + + arranger(inp.begin(), inp.end()); + for (Item &itm : inp) itm.inflate(-infl); +} + +// The final client function for arrangement. A progress indicator and +// a stop predicate can be also be passed to control the process. +void arrange(ArrangePolygons & arrangables, + const ArrangePolygons & excludes, + coord_t min_obj_dist, + const BedShapeHint & bedhint, + std::function progressind, + std::function stopcondition) +{ + namespace clppr = ClipperLib; + + std::vector items, fixeditems; + items.reserve(arrangables.size()); + + // Create Item from Arrangeable + auto process_arrangeable = + [](const ArrangePolygon &arrpoly, std::vector &outp) + { + Polygon p = arrpoly.poly.contour; + const Vec2crd & offs = arrpoly.translation; + double rotation = arrpoly.rotation; + + if (p.is_counter_clockwise()) p.reverse(); + + clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p)); + + auto firstp = clpath.Contour.front(); + clpath.Contour.emplace_back(firstp); + + outp.emplace_back(std::move(clpath)); + outp.back().rotation(rotation); + outp.back().translation({offs.x(), offs.y()}); + outp.back().binId(arrpoly.bed_idx); + outp.back().priority(arrpoly.priority); + }; + + for (ArrangePolygon &arrangeable : arrangables) + process_arrangeable(arrangeable, items); + + for (const ArrangePolygon &fixed: excludes) + process_arrangeable(fixed, fixeditems); + + for (Item &itm : fixeditems) itm.inflate(scaled(-2. * EPSILON)); + + auto &cfn = stopcondition; + auto &pri = progressind; + + switch (bedhint.get_type()) { + case bsBox: { + // Create the arranger for the box shaped bed + BoundingBox bbb = bedhint.get_box(); + Box binbb{{bbb.min(X), bbb.min(Y)}, {bbb.max(X), bbb.max(Y)}}; + + _arrange(items, fixeditems, binbb, min_obj_dist, pri, cfn); + break; + } + case bsCircle: { + auto cc = to_lnCircle(bedhint.get_circle()); + + _arrange(items, fixeditems, cc, min_obj_dist, pri, cfn); + break; + } + case bsIrregular: { + auto ctour = Slic3rMultiPoint_to_ClipperPath(bedhint.get_irregular()); + auto irrbed = sl::create(std::move(ctour)); + BoundingBox polybb(bedhint.get_irregular()); + + _arrange(items, fixeditems, irrbed, min_obj_dist, pri, cfn); + break; + } + case bsInfinite: { + const InfiniteBed& nobin = bedhint.get_infinite(); + auto infbb = Box::infinite({nobin.center.x(), nobin.center.y()}); + + _arrange(items, fixeditems, infbb, min_obj_dist, pri, cfn); + break; + } + case bsUnknown: { + // We know nothing about the bed, let it be infinite and zero centered + _arrange(items, fixeditems, Box::infinite(), min_obj_dist, pri, cfn); + break; + } + }; + + for(size_t i = 0; i < items.size(); ++i) { + clppr::IntPoint tr = items[i].translation(); + arrangables[i].translation = {coord_t(tr.X), coord_t(tr.Y)}; + arrangables[i].rotation = items[i].rotation(); + arrangables[i].bed_idx = items[i].binId(); + } +} + +// Arrange, without the fixed items (excludes) +void arrange(ArrangePolygons & inp, + coord_t min_d, + const BedShapeHint & bedhint, + std::function prfn, + std::function stopfn) +{ + arrange(inp, {}, min_d, bedhint, prfn, stopfn); +} + +} // namespace arr +} // namespace Slic3r diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp new file mode 100644 index 0000000000..1cfe1c907d --- /dev/null +++ b/src/libslic3r/Arrange.hpp @@ -0,0 +1,182 @@ +#ifndef MODELARRANGE_HPP +#define MODELARRANGE_HPP + +#include "ExPolygon.hpp" +#include "BoundingBox.hpp" + +namespace Slic3r { + +namespace arrangement { + +/// A geometry abstraction for a circular print bed. Similarly to BoundingBox. +class CircleBed { + Point center_; + double radius_; +public: + + inline CircleBed(): center_(0, 0), radius_(std::nan("")) {} + inline CircleBed(const Point& c, double r): center_(c), radius_(r) {} + + inline double radius() const { return radius_; } + inline const Point& center() const { return center_; } + inline operator bool() { return !std::isnan(radius_); } +}; + +/// Representing an unbounded bed. +struct InfiniteBed { Point center; }; + +/// Types of print bed shapes. +enum BedShapes { + bsBox, + bsCircle, + bsIrregular, + bsInfinite, + bsUnknown +}; + +/// Info about the print bed for the arrange() function. This is a variant +/// holding one of the four shapes a bed can be. +class BedShapeHint { + BedShapes m_type = BedShapes::bsInfinite; + + // The union neither calls constructors nor destructors of its members. + // The only member with non-trivial constructor / destructor is the polygon, + // a placement new / delete needs to be called over it. + union BedShape_u { // TODO: use variant from cpp17? + CircleBed circ; + BoundingBox box; + Polyline polygon; + InfiniteBed infbed{}; + ~BedShape_u() {} + BedShape_u() {} + } m_bed; + + // Reset the type, allocate m_bed properly + void reset(BedShapes type); + +public: + + BedShapeHint(){} + + /// Get a bed shape hint for arrange() from a naked Polyline. + explicit BedShapeHint(const Polyline &polyl); + explicit BedShapeHint(const BoundingBox &bb) + { + m_type = bsBox; m_bed.box = bb; + } + + explicit BedShapeHint(const CircleBed &c) + { + m_type = bsCircle; m_bed.circ = c; + } + + explicit BedShapeHint(const InfiniteBed &ibed) + { + m_type = bsInfinite; m_bed.infbed = ibed; + } + + ~BedShapeHint() + { + if (m_type == BedShapes::bsIrregular) + m_bed.polygon.Slic3r::Polyline::~Polyline(); + } + + BedShapeHint(const BedShapeHint &cpy) { *this = cpy; } + BedShapeHint(BedShapeHint &&cpy) { *this = std::move(cpy); } + + BedShapeHint &operator=(const BedShapeHint &cpy); + BedShapeHint& operator=(BedShapeHint &&cpy); + + BedShapes get_type() const { return m_type; } + + const BoundingBox &get_box() const + { + assert(m_type == bsBox); return m_bed.box; + } + const CircleBed &get_circle() const + { + assert(m_type == bsCircle); return m_bed.circ; + } + const Polyline &get_irregular() const + { + assert(m_type == bsIrregular); return m_bed.polygon; + } + const InfiniteBed &get_infinite() const + { + assert(m_type == bsInfinite); return m_bed.infbed; + } +}; + +/// A logical bed representing an object not being arranged. Either the arrange +/// has not yet successfully run on this ArrangePolygon or it could not fit the +/// object due to overly large size or invalid geometry. +static const constexpr int UNARRANGED = -1; + +/// Input/Output structure for the arrange() function. The poly field will not +/// be modified during arrangement. Instead, the translation and rotation fields +/// will mark the needed transformation for the polygon to be in the arranged +/// position. These can also be set to an initial offset and rotation. +/// +/// The bed_idx field will indicate the logical bed into which the +/// polygon belongs: UNARRANGED means no place for the polygon +/// (also the initial state before arrange), 0..N means the index of the bed. +/// Zero is the physical bed, larger than zero means a virtual bed. +struct ArrangePolygon { + ExPolygon poly; /// The 2D silhouette to be arranged + Vec2crd translation{0, 0}; /// The translation of the poly + double rotation{0.0}; /// The rotation of the poly in radians + int bed_idx{UNARRANGED}; /// To which logical bed does poly belong... + int priority{0}; + + /// Optional setter function which can store arbitrary data in its closure + std::function setter = nullptr; + + /// Helper function to call the setter with the arrange data arguments + void apply() const { if (setter) setter(*this); } + + /// Test if arrange() was called previously and gave a successful result. + bool is_arranged() const { return bed_idx != UNARRANGED; } +}; + +using ArrangePolygons = std::vector; + +/** + * \brief Arranges the input polygons. + * + * WARNING: Currently, only convex polygons are supported by the libnest2d + * library which is used to do the arrangement. This might change in the future + * this is why the interface contains a general polygon capable to have holes. + * + * \param items Input vector of ArrangePolygons. The transformation, rotation + * and bin_idx fields will be changed after the call finished and can be used + * to apply the result on the input polygon. + * + * \param min_obj_distance The minimum distance which is allowed for any + * pair of items on the print bed in any direction. + * + * \param bedhint Info about the shape and type of the bed. + * + * \param progressind Progress indicator callback called when + * an object gets packed. The unsigned argument is the number of items + * remaining to pack. + * + * \param stopcondition A predicate returning true if abort is needed. + */ +void arrange(ArrangePolygons & items, + coord_t min_obj_distance, + const BedShapeHint & bedhint, + std::function progressind = nullptr, + std::function stopcondition = nullptr); + +/// Same as the previous, only that it takes unmovable items as an +/// additional argument. Those will be considered as already arranged objects. +void arrange(ArrangePolygons & items, + const ArrangePolygons & excludes, + coord_t min_obj_distance, + const BedShapeHint & bedhint, + std::function progressind = nullptr, + std::function stopcondition = nullptr); + +} // arr +} // Slic3r +#endif // MODELARRANGE_HPP diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 26302f7027..b1ebdcfbc7 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -161,4 +161,12 @@ inline bool empty(const BoundingBox3Base &bb) } // namespace Slic3r +// Serialization through the Cereal library +namespace cereal { + template void serialize(Archive& archive, Slic3r::BoundingBox &bb) { archive(bb.min, bb.max, bb.defined); } + template void serialize(Archive& archive, Slic3r::BoundingBox3 &bb) { archive(bb.min, bb.max, bb.defined); } + template void serialize(Archive& archive, Slic3r::BoundingBoxf &bb) { archive(bb.min, bb.max, bb.defined); } + template void serialize(Archive& archive, Slic3r::BoundingBoxf3 &bb) { archive(bb.min, bb.max, bb.defined); } +} + #endif diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 6a4fe3c289..85e11eded3 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -5,6 +5,10 @@ include(PrecompiledHeader) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/libslic3r_version.h.in ${CMAKE_CURRENT_BINARY_DIR}/libslic3r_version.h @ONLY) +if (MINGW) + add_compile_options(-Wa,-mbig-obj) +endif () + add_library(libslic3r STATIC pchheader.cpp pchheader.hpp @@ -70,7 +74,7 @@ add_library(libslic3r STATIC GCode/CoolingBuffer.cpp GCode/CoolingBuffer.hpp GCode/PostProcessor.cpp - GCode/PostProcessor.hpp + GCode/PostProcessor.hpp # GCode/PressureEqualizer.cpp # GCode/PressureEqualizer.hpp GCode/PreviewData.cpp @@ -81,9 +85,8 @@ add_library(libslic3r STATIC GCode/SpiralVase.hpp GCode/ToolOrdering.cpp GCode/ToolOrdering.hpp + GCode/WipeTower.cpp GCode/WipeTower.hpp - GCode/WipeTowerPrusaMM.cpp - GCode/WipeTowerPrusaMM.hpp GCode.cpp GCode.hpp GCodeReader.cpp @@ -107,13 +110,15 @@ add_library(libslic3r STATIC Line.hpp Model.cpp Model.hpp - ModelArrange.hpp - ModelArrange.cpp + Arrange.hpp + Arrange.cpp MotionPlanner.cpp MotionPlanner.hpp MultiPoint.cpp MultiPoint.hpp MutablePriorityQueue.hpp + ObjectID.cpp + ObjectID.hpp PerimeterGenerator.cpp PerimeterGenerator.hpp PlaceholderParser.cpp @@ -122,6 +127,8 @@ add_library(libslic3r STATIC Point.hpp Polygon.cpp Polygon.hpp + PolygonTrimmer.cpp + PolygonTrimmer.hpp Polyline.cpp Polyline.hpp PolylineCollection.cpp @@ -130,13 +137,11 @@ add_library(libslic3r STATIC Print.hpp PrintBase.cpp PrintBase.hpp - PrintExport.hpp PrintConfig.cpp PrintConfig.hpp PrintObject.cpp PrintRegion.cpp - Rasterizer/Rasterizer.hpp - Rasterizer/Rasterizer.cpp + Semver.cpp SLAPrint.cpp SLAPrint.hpp SLA/SLAAutoSupports.hpp @@ -163,6 +168,11 @@ add_library(libslic3r STATIC MTUtils.hpp Zipper.hpp Zipper.cpp + MinAreaBoundingBox.hpp + MinAreaBoundingBox.cpp + miniz_extension.hpp + miniz_extension.cpp + SLA/SLACommon.hpp SLA/SLABoilerPlate.hpp SLA/SLABasePool.hpp SLA/SLABasePool.cpp @@ -173,20 +183,27 @@ add_library(libslic3r STATIC SLA/SLARotfinder.cpp SLA/SLABoostAdapter.hpp SLA/SLASpatIndex.hpp + SLA/SLARaster.hpp + SLA/SLARaster.cpp + SLA/SLARasterWriter.hpp + SLA/SLARasterWriter.cpp ) +encoding_check(libslic3r) + if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) add_precompiled_header(libslic3r pchheader.hpp FORCEINCLUDE) endif () target_compile_definitions(libslic3r PUBLIC -DUSE_TBB) -target_include_directories(libslic3r SYSTEM PUBLIC ${Boost_INCLUDE_DIRS}) target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${LIBNEST2D_INCLUDES} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) target_link_libraries(libslic3r libnest2d admesh + cereal + libigl miniz - ${Boost_LIBRARIES} + boost_libs clipper nowide ${EXPAT_LIBRARIES} diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index 0738b77c6a..5776980714 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -209,6 +209,68 @@ std::vector ConfigOptionDef::cli_args(const std::string &key) const return args; } +ConfigOption* ConfigOptionDef::create_empty_option() const +{ + if (this->nullable) { + switch (this->type) { + case coFloats: return new ConfigOptionFloatsNullable(); + case coInts: return new ConfigOptionIntsNullable(); + case coPercents: return new ConfigOptionPercentsNullable(); + case coBools: return new ConfigOptionBoolsNullable(); + default: throw std::runtime_error(std::string("Unknown option type for nullable option ") + this->label); + } + } else { + switch (this->type) { + case coFloat: return new ConfigOptionFloat(); + case coFloats: return new ConfigOptionFloats(); + case coInt: return new ConfigOptionInt(); + case coInts: return new ConfigOptionInts(); + case coString: return new ConfigOptionString(); + case coStrings: return new ConfigOptionStrings(); + case coPercent: return new ConfigOptionPercent(); + case coPercents: return new ConfigOptionPercents(); + case coFloatOrPercent: return new ConfigOptionFloatOrPercent(); + case coPoint: return new ConfigOptionPoint(); + case coPoints: return new ConfigOptionPoints(); + case coPoint3: return new ConfigOptionPoint3(); + // case coPoint3s: return new ConfigOptionPoint3s(); + case coBool: return new ConfigOptionBool(); + case coBools: return new ConfigOptionBools(); + case coEnum: return new ConfigOptionEnumGeneric(this->enum_keys_map); + default: throw std::runtime_error(std::string("Unknown option type for option ") + this->label); + } + } +} + +ConfigOption* ConfigOptionDef::create_default_option() const +{ + if (this->default_value) + return (this->default_value->type() == coEnum) ? + // Special case: For a DynamicConfig, convert a templated enum to a generic enum. + new ConfigOptionEnumGeneric(this->enum_keys_map, this->default_value->getInt()) : + this->default_value->clone(); + return this->create_empty_option(); +} + +// Assignment of the serialization IDs is not thread safe. The Defs shall be initialized from the main thread! +ConfigOptionDef* ConfigDef::add(const t_config_option_key &opt_key, ConfigOptionType type) +{ + static size_t serialization_key_ordinal_last = 0; + ConfigOptionDef *opt = &this->options[opt_key]; + opt->opt_key = opt_key; + opt->type = type; + opt->serialization_key_ordinal = ++ serialization_key_ordinal_last; + this->by_serialization_key_ordinal[opt->serialization_key_ordinal] = opt; + return opt; +} + +ConfigOptionDef* ConfigDef::add_nullable(const t_config_option_key &opt_key, ConfigOptionType type) +{ + ConfigOptionDef *def = this->add(opt_key, type); + def->nullable = true; + return def; +} + std::string ConfigOptionDef::nocli = "~~~noCLI"; std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, std::function filter) const @@ -358,7 +420,7 @@ t_config_option_keys ConfigBase::equal(const ConfigBase &other) const return equal; } -std::string ConfigBase::serialize(const t_config_option_key &opt_key) const +std::string ConfigBase::opt_serialize(const t_config_option_key &opt_key) const { const ConfigOption* opt = this->option(opt_key); assert(opt != nullptr); @@ -469,7 +531,7 @@ void ConfigBase::setenv_() const for (size_t i = 0; i < envname.size(); ++i) envname[i] = (envname[i] <= 'z' && envname[i] >= 'a') ? envname[i]-('a'-'A') : envname[i]; - boost::nowide::setenv(envname.c_str(), this->serialize(*it).c_str(), 1); + boost::nowide::setenv(envname.c_str(), this->opt_serialize(*it).c_str(), 1); } } @@ -593,16 +655,27 @@ void ConfigBase::save(const std::string &file) const c.open(file, std::ios::out | std::ios::trunc); c << "# " << Slic3r::header_slic3r_generated() << std::endl; for (const std::string &opt_key : this->keys()) - c << opt_key << " = " << this->serialize(opt_key) << std::endl; + c << opt_key << " = " << this->opt_serialize(opt_key) << std::endl; c.close(); } +// Set all the nullable values to nils. +void ConfigBase::null_nullables() +{ + for (const std::string &opt_key : this->keys()) { + ConfigOption *opt = this->optptr(opt_key, false); + assert(opt != nullptr); + if (opt->nullable()) + opt->deserialize("nil"); + } +} + bool DynamicConfig::operator==(const DynamicConfig &rhs) const { - t_options_map::const_iterator it1 = this->options.begin(); - t_options_map::const_iterator it1_end = this->options.end(); - t_options_map::const_iterator it2 = rhs.options.begin(); - t_options_map::const_iterator it2_end = rhs.options.end(); + auto it1 = this->options.begin(); + auto it1_end = this->options.end(); + auto it2 = rhs.options.begin(); + auto it2_end = rhs.options.end(); for (; it1 != it1_end && it2 != it2_end; ++ it1, ++ it2) if (it1->first != it2->first || *it1->second != *it2->second) // key or value differ @@ -610,12 +683,25 @@ bool DynamicConfig::operator==(const DynamicConfig &rhs) const return it1 == it1_end && it2 == it2_end; } +// Remove options with all nil values, those are optional and it does not help to hold them. +size_t DynamicConfig::remove_nil_options() +{ + size_t cnt_removed = 0; + for (auto it = options.begin(); it != options.end();) + if (it->second->is_nil()) { + it = options.erase(it); + ++ cnt_removed; + } else + ++ it; + return cnt_removed; +} + ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool create) { - t_options_map::iterator it = options.find(opt_key); + auto it = options.find(opt_key); if (it != options.end()) // Option was found. - return it->second; + return it->second.get(); if (! create) // Option was not found and a new option shall not be created. return nullptr; @@ -628,34 +714,8 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre // throw std::runtime_error(std::string("Invalid option name: ") + opt_key); // Let the parent decide what to do if the opt_key is not defined by this->def(). return nullptr; - ConfigOption *opt = nullptr; - if (optdef->default_value) { - opt = (optdef->default_value->type() == coEnum) ? - // Special case: For a DynamicConfig, convert a templated enum to a generic enum. - new ConfigOptionEnumGeneric(optdef->enum_keys_map, optdef->default_value->getInt()) : - optdef->default_value->clone(); - } else { - switch (optdef->type) { - case coFloat: opt = new ConfigOptionFloat(); break; - case coFloats: opt = new ConfigOptionFloats(); break; - case coInt: opt = new ConfigOptionInt(); break; - case coInts: opt = new ConfigOptionInts(); break; - case coString: opt = new ConfigOptionString(); break; - case coStrings: opt = new ConfigOptionStrings(); break; - case coPercent: opt = new ConfigOptionPercent(); break; - case coPercents: opt = new ConfigOptionPercents(); break; - case coFloatOrPercent: opt = new ConfigOptionFloatOrPercent(); break; - case coPoint: opt = new ConfigOptionPoint(); break; - case coPoints: opt = new ConfigOptionPoints(); break; - case coPoint3: opt = new ConfigOptionPoint3(); break; - // case coPoint3s: opt = new ConfigOptionPoint3s(); break; - case coBool: opt = new ConfigOptionBool(); break; - case coBools: opt = new ConfigOptionBools(); break; - case coEnum: opt = new ConfigOptionEnumGeneric(optdef->enum_keys_map); break; - default: throw std::runtime_error(std::string("Unknown option type for option ") + opt_key); - } - } - this->options[opt_key] = opt; + ConfigOption *opt = optdef->create_default_option(); + this->options.emplace_hint(it, opt_key, std::unique_ptr(opt)); return opt; } @@ -732,18 +792,18 @@ bool DynamicConfig::read_cli(int argc, char** argv, t_config_option_keys* extra, } // Store the option value. const bool existing = this->has(opt_key); - if (keys != nullptr && !existing) { + if (keys != nullptr && ! existing) { // Save the order of detected keys. keys->push_back(opt_key); } ConfigOption *opt_base = this->option(opt_key, true); ConfigOptionVectorBase *opt_vector = opt_base->is_vector() ? static_cast(opt_base) : nullptr; if (opt_vector) { + if (! existing) + // remove the default values + opt_vector->clear(); // Vector values will be chained. Repeated use of a parameter will append the parameter or parameters // to the end of the value. - if (!existing) - // remove the default values - opt_vector->deserialize("", true); if (opt_base->type() == coBools) static_cast(opt_base)->values.push_back(!no); else @@ -802,3 +862,72 @@ t_config_option_keys StaticConfig::keys() const } } + +#include +CEREAL_REGISTER_TYPE(Slic3r::ConfigOption) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVectorBase) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloat) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloats) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloatsNullable) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionInt) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionInts) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionIntsNullable) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionString) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionStrings) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPercent) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPercents) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPercentsNullable) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloatOrPercent) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPoint) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPoints) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPoint3) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionBool) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionBools) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionBoolsNullable) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionEnumGeneric) +CEREAL_REGISTER_TYPE(Slic3r::ConfigBase) +CEREAL_REGISTER_TYPE(Slic3r::DynamicConfig) + +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionVectorBase) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionFloat) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionFloats) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionFloatsNullable) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionInt) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionInts) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionIntsNullable) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionString) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionStrings) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionFloat, Slic3r::ConfigOptionPercent) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionFloats, Slic3r::ConfigOptionPercents) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionFloats, Slic3r::ConfigOptionPercentsNullable) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionPercent, Slic3r::ConfigOptionFloatOrPercent) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionPoint) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionPoints) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionPoint3) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionBool) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionBools) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionBoolsNullable) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionInt, Slic3r::ConfigOptionEnumGeneric) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigBase, Slic3r::DynamicConfig) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index ee4bc4e463..ff55632262 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -15,9 +15,13 @@ #include "clonable_ptr.hpp" #include "Point.hpp" +#include #include #include +#include +#include + namespace Slic3r { // Name of the configuration option. @@ -90,7 +94,7 @@ enum ConfigOptionMode { comExpert }; -enum PrinterTechnology +enum PrinterTechnology : unsigned char { // Fused Filament Fabrication ptFFF, @@ -121,6 +125,23 @@ public: bool operator!=(const ConfigOption &rhs) const { return ! (*this == rhs); } bool is_scalar() const { return (int(this->type()) & int(coVectorType)) == 0; } bool is_vector() const { return ! this->is_scalar(); } + // If this option is nullable, then it may have its value or values set to nil. + virtual bool nullable() const { return false; } + // A scalar is nil, or all values of a vector are nil. + virtual bool is_nil() const { return false; } + // Is this option overridden by another option? + // An option overrides another option if it is not nil and not equal. + virtual bool overriden_by(const ConfigOption *rhs) const { + assert(! this->nullable() && ! rhs->nullable()); + return *this != *rhs; + } + // Apply an override option, possibly a nullable one. + virtual bool apply_override(const ConfigOption *rhs) { + if (*this == *rhs) + return false; + *this = *rhs; + return true; + } }; typedef ConfigOption* ConfigOptionPtr; @@ -152,6 +173,10 @@ public: bool operator==(const T &rhs) const { return this->value == rhs; } bool operator!=(const T &rhs) const { return this->value != rhs; } + +private: + friend class cereal::access; + template void serialize(Archive & ar) { ar(this->value); } }; // Value of a vector valued option (bools, ints, floats, strings, points) @@ -167,13 +192,17 @@ public: // Set a single vector item from either a scalar option or the first value of a vector option.vector of ConfigOptions. // This function is useful to split values from multiple extrder / filament settings into separate configurations. virtual void set_at(const ConfigOption *rhs, size_t i, size_t j) = 0; - + // Resize the vector of values, copy the newly added values from opt_default if provided. virtual void resize(size_t n, const ConfigOption *opt_default = nullptr) = 0; + // Clear the values vector. + virtual void clear() = 0; // Get size of this vector. virtual size_t size() const = 0; // Is this vector empty? virtual bool empty() const = 0; + // Is the value nil? That should only be possible if this->nullable(). + virtual bool is_nil(size_t idx) const = 0; protected: // Used to verify type compatibility when assigning to / from a scalar ConfigOption. @@ -277,6 +306,8 @@ public: } } + // Clear the values vector. + void clear() override { this->values.clear(); } size_t size() const override { return this->values.size(); } bool empty() const override { return this->values.empty(); } @@ -290,6 +321,66 @@ public: bool operator==(const std::vector &rhs) const { return this->values == rhs; } bool operator!=(const std::vector &rhs) const { return this->values != rhs; } + + // Is this option overridden by another option? + // An option overrides another option if it is not nil and not equal. + bool overriden_by(const ConfigOption *rhs) const override { + if (this->nullable()) + throw std::runtime_error("Cannot override a nullable ConfigOption."); + if (rhs->type() != this->type()) + throw std::runtime_error("ConfigOptionVector.overriden_by() applied to different types."); + auto rhs_vec = static_cast*>(rhs); + if (! rhs->nullable()) + // Overridding a non-nullable object with another non-nullable object. + return this->values != rhs_vec->values; + size_t i = 0; + size_t cnt = std::min(this->size(), rhs_vec->size()); + for (; i < cnt; ++ i) + if (! rhs_vec->is_nil(i) && this->values[i] != rhs_vec->values[i]) + return true; + for (; i < rhs_vec->size(); ++ i) + if (! rhs_vec->is_nil(i)) + return true; + return false; + } + // Apply an override option, possibly a nullable one. + bool apply_override(const ConfigOption *rhs) override { + if (this->nullable()) + throw std::runtime_error("Cannot override a nullable ConfigOption."); + if (rhs->type() != this->type()) + throw std::runtime_error("ConfigOptionVector.apply_override() applied to different types."); + auto rhs_vec = static_cast*>(rhs); + if (! rhs->nullable()) { + // Overridding a non-nullable object with another non-nullable object. + if (this->values != rhs_vec->values) { + this->values = rhs_vec->values; + return true; + } + return false; + } + size_t i = 0; + size_t cnt = std::min(this->size(), rhs_vec->size()); + bool modified = false; + for (; i < cnt; ++ i) + if (! rhs_vec->is_nil(i) && this->values[i] != rhs_vec->values[i]) { + this->values[i] = rhs_vec->values[i]; + modified = true; + } + for (; i < rhs_vec->size(); ++ i) + if (! rhs_vec->is_nil(i)) { + if (this->values.empty()) + this->values.resize(i + 1); + else + this->values.resize(i + 1, this->values.front()); + this->values[i] = rhs_vec->values[i]; + modified = true; + } + return modified; + } + +private: + friend class cereal::access; + template void serialize(Archive & ar) { ar(this->values); } }; class ConfigOptionFloat : public ConfigOptionSingle @@ -324,28 +415,47 @@ public: this->set(opt); return *this; } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; -class ConfigOptionFloats : public ConfigOptionVector +template +class ConfigOptionFloatsTempl : public ConfigOptionVector { public: - ConfigOptionFloats() : ConfigOptionVector() {} - explicit ConfigOptionFloats(size_t n, double value) : ConfigOptionVector(n, value) {} - explicit ConfigOptionFloats(std::initializer_list il) : ConfigOptionVector(std::move(il)) {} - explicit ConfigOptionFloats(const std::vector &vec) : ConfigOptionVector(vec) {} - explicit ConfigOptionFloats(std::vector &&vec) : ConfigOptionVector(std::move(vec)) {} + ConfigOptionFloatsTempl() : ConfigOptionVector() {} + explicit ConfigOptionFloatsTempl(size_t n, double value) : ConfigOptionVector(n, value) {} + explicit ConfigOptionFloatsTempl(std::initializer_list il) : ConfigOptionVector(std::move(il)) {} + explicit ConfigOptionFloatsTempl(const std::vector &vec) : ConfigOptionVector(vec) {} + explicit ConfigOptionFloatsTempl(std::vector &&vec) : ConfigOptionVector(std::move(vec)) {} static ConfigOptionType static_type() { return coFloats; } ConfigOptionType type() const override { return static_type(); } - ConfigOption* clone() const override { return new ConfigOptionFloats(*this); } - bool operator==(const ConfigOptionFloats &rhs) const { return this->values == rhs.values; } + ConfigOption* clone() const override { return new ConfigOptionFloatsTempl(*this); } + bool operator==(const ConfigOptionFloatsTempl &rhs) const { return vectors_equal(this->values, rhs.values); } + bool operator==(const ConfigOption &rhs) const override { + if (rhs.type() != this->type()) + throw std::runtime_error("ConfigOptionFloatsTempl: Comparing incompatible types"); + assert(dynamic_cast*>(&rhs)); + return vectors_equal(this->values, static_cast*>(&rhs)->values); + } + // Could a special "nil" value be stored inside the vector, indicating undefined value? + bool nullable() const override { return NULLABLE; } + // Special "nil" value to be stored into the vector if this->supports_nil(). + static double nil_value() { return std::numeric_limits::quiet_NaN(); } + // A scalar is nil, or all values of a vector are nil. + bool is_nil() const override { for (auto v : this->values) if (! std::isnan(v)) return false; return true; } + bool is_nil(size_t idx) const override { return std::isnan(this->values[idx]); } std::string serialize() const override { std::ostringstream ss; - for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { - if (it - this->values.begin() != 0) ss << ","; - ss << *it; + for (const double &v : this->values) { + if (&v != &this->values.front()) + ss << ","; + serialize_single_value(ss, v); } return ss.str(); } @@ -354,14 +464,14 @@ public: { std::vector vv; vv.reserve(this->values.size()); - for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { + for (const double v : this->values) { std::ostringstream ss; - ss << *it; + serialize_single_value(ss, v); vv.push_back(ss.str()); } return vv; } - + bool deserialize(const std::string &str, bool append = false) override { if (! append) @@ -369,21 +479,61 @@ public: std::istringstream is(str); std::string item_str; while (std::getline(is, item_str, ',')) { - std::istringstream iss(item_str); - double value; - iss >> value; - this->values.push_back(value); + boost::trim(item_str); + if (item_str == "nil") { + if (NULLABLE) + this->values.push_back(nil_value()); + else + std::runtime_error("Deserializing nil into a non-nullable object"); + } else { + std::istringstream iss(item_str); + double value; + iss >> value; + this->values.push_back(value); + } } return true; } - ConfigOptionFloats& operator=(const ConfigOption *opt) + ConfigOptionFloatsTempl& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + +protected: + void serialize_single_value(std::ostringstream &ss, const double v) const { + if (std::isfinite(v)) + ss << v; + else if (std::isnan(v)) { + if (NULLABLE) + ss << "nil"; + else + std::runtime_error("Serializing NaN"); + } else + std::runtime_error("Serializing invalid number"); + } + static bool vectors_equal(const std::vector &v1, const std::vector &v2) { + if (NULLABLE) { + if (v1.size() != v2.size()) + return false; + for (auto it1 = v1.begin(), it2 = v2.begin(); it1 != v1.end(); ++ it1, ++ it2) + if (! ((std::isnan(*it1) && std::isnan(*it2)) || *it1 == *it2)) + return false; + return true; + } else + // Not supporting nullable values, the default vector compare is cheaper. + return v1 == v2; + } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; +using ConfigOptionFloats = ConfigOptionFloatsTempl; +using ConfigOptionFloatsNullable = ConfigOptionFloatsTempl; + class ConfigOptionInt : public ConfigOptionSingle { public: @@ -418,37 +568,51 @@ public: this->set(opt); return *this; } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; -class ConfigOptionInts : public ConfigOptionVector +template +class ConfigOptionIntsTempl : public ConfigOptionVector { public: - ConfigOptionInts() : ConfigOptionVector() {} - explicit ConfigOptionInts(size_t n, int value) : ConfigOptionVector(n, value) {} - explicit ConfigOptionInts(std::initializer_list il) : ConfigOptionVector(std::move(il)) {} + ConfigOptionIntsTempl() : ConfigOptionVector() {} + explicit ConfigOptionIntsTempl(size_t n, int value) : ConfigOptionVector(n, value) {} + explicit ConfigOptionIntsTempl(std::initializer_list il) : ConfigOptionVector(std::move(il)) {} static ConfigOptionType static_type() { return coInts; } ConfigOptionType type() const override { return static_type(); } - ConfigOption* clone() const override { return new ConfigOptionInts(*this); } - ConfigOptionInts& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionInts &rhs) const { return this->values == rhs.values; } + ConfigOption* clone() const override { return new ConfigOptionIntsTempl(*this); } + ConfigOptionIntsTempl& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionIntsTempl &rhs) const { return this->values == rhs.values; } + // Could a special "nil" value be stored inside the vector, indicating undefined value? + bool nullable() const override { return NULLABLE; } + // Special "nil" value to be stored into the vector if this->supports_nil(). + static int nil_value() { return std::numeric_limits::max(); } + // A scalar is nil, or all values of a vector are nil. + bool is_nil() const override { for (auto v : this->values) if (v != nil_value()) return false; return true; } + bool is_nil(size_t idx) const override { return this->values[idx] == nil_value(); } - std::string serialize() const override { + std::string serialize() const override + { std::ostringstream ss; - for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { - if (it - this->values.begin() != 0) ss << ","; - ss << *it; + for (const int &v : this->values) { + if (&v != &this->values.front()) + ss << ","; + serialize_single_value(ss, v); } return ss.str(); } - std::vector vserialize() const override + std::vector vserialize() const override { std::vector vv; vv.reserve(this->values.size()); - for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { + for (const int v : this->values) { std::ostringstream ss; - ss << *it; + serialize_single_value(ss, v); vv.push_back(ss.str()); } return vv; @@ -461,15 +625,40 @@ public: std::istringstream is(str); std::string item_str; while (std::getline(is, item_str, ',')) { - std::istringstream iss(item_str); - int value; - iss >> value; - this->values.push_back(value); + boost::trim(item_str); + if (item_str == "nil") { + if (NULLABLE) + this->values.push_back(nil_value()); + else + std::runtime_error("Deserializing nil into a non-nullable object"); + } else { + std::istringstream iss(item_str); + int value; + iss >> value; + this->values.push_back(value); + } } return true; } + +private: + void serialize_single_value(std::ostringstream &ss, const int v) const { + if (v == nil_value()) { + if (NULLABLE) + ss << "nil"; + else + std::runtime_error("Serializing NaN"); + } else + ss << v; + } + + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; +using ConfigOptionInts = ConfigOptionIntsTempl; +using ConfigOptionIntsNullable = ConfigOptionIntsTempl; + class ConfigOptionString : public ConfigOptionSingle { public: @@ -492,6 +681,10 @@ public: UNUSED(append); return unescape_string_cstyle(str, this->value); } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; // semicolon-separated strings @@ -509,6 +702,7 @@ public: ConfigOption* clone() const override { return new ConfigOptionStrings(*this); } ConfigOptionStrings& operator=(const ConfigOption *opt) { this->set(opt); return *this; } bool operator==(const ConfigOptionStrings &rhs) const { return this->values == rhs.values; } + bool is_nil(size_t idx) const override { return false; } std::string serialize() const override { @@ -526,6 +720,10 @@ public: this->values.clear(); return unescape_strings_cstyle(str, this->values); } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; class ConfigOptionPercent : public ConfigOptionFloat @@ -558,62 +756,67 @@ public: iss >> this->value; return !iss.fail(); } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class(this)); } }; -class ConfigOptionPercents : public ConfigOptionFloats +template +class ConfigOptionPercentsTempl : public ConfigOptionFloatsTempl { public: - ConfigOptionPercents() : ConfigOptionFloats() {} - explicit ConfigOptionPercents(size_t n, double value) : ConfigOptionFloats(n, value) {} - explicit ConfigOptionPercents(std::initializer_list il) : ConfigOptionFloats(std::move(il)) {} + ConfigOptionPercentsTempl() : ConfigOptionFloatsTempl() {} + explicit ConfigOptionPercentsTempl(size_t n, double value) : ConfigOptionFloatsTempl(n, value) {} + explicit ConfigOptionPercentsTempl(std::initializer_list il) : ConfigOptionFloatsTempl(std::move(il)) {} + explicit ConfigOptionPercentsTempl(const std::vector& vec) : ConfigOptionFloatsTempl(vec) {} + explicit ConfigOptionPercentsTempl(std::vector&& vec) : ConfigOptionFloatsTempl(std::move(vec)) {} static ConfigOptionType static_type() { return coPercents; } ConfigOptionType type() const override { return static_type(); } - ConfigOption* clone() const override { return new ConfigOptionPercents(*this); } - ConfigOptionPercents& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionPercents &rhs) const { return this->values == rhs.values; } + ConfigOption* clone() const override { return new ConfigOptionPercentsTempl(*this); } + ConfigOptionPercentsTempl& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionPercentsTempl &rhs) const { return this->values == rhs.values; } std::string serialize() const override { std::ostringstream ss; - for (const auto &v : this->values) { - if (&v != &this->values.front()) ss << ","; - ss << v << "%"; + for (const double &v : this->values) { + if (&v != &this->values.front()) + ss << ","; + this->serialize_single_value(ss, v); + if (! std::isnan(v)) + ss << "%"; } std::string str = ss.str(); return str; } - + std::vector vserialize() const override { std::vector vv; vv.reserve(this->values.size()); - for (const auto v : this->values) { + for (const double v : this->values) { std::ostringstream ss; - ss << v; - std::string sout = ss.str() + "%"; - vv.push_back(sout); + this->serialize_single_value(ss, v); + if (! std::isnan(v)) + ss << "%"; + vv.push_back(ss.str()); } return vv; } - bool deserialize(const std::string &str, bool append = false) override - { - if (! append) - this->values.clear(); - std::istringstream is(str); - std::string item_str; - while (std::getline(is, item_str, ',')) { - std::istringstream iss(item_str); - double value; - // don't try to parse the trailing % since it's optional - iss >> value; - this->values.push_back(value); - } - return true; - } + // The float's deserialize function shall ignore the trailing optional %. + // bool deserialize(const std::string &str, bool append = false) override; + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; +using ConfigOptionPercents = ConfigOptionPercentsTempl; +using ConfigOptionPercentsNullable = ConfigOptionPercentsTempl; + class ConfigOptionFloatOrPercent : public ConfigOptionPercent { public: @@ -661,6 +864,10 @@ public: iss >> this->value; return !iss.fail(); } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class(this), percent); } }; class ConfigOptionPoint : public ConfigOptionSingle @@ -691,6 +898,10 @@ public: return sscanf(str.data(), " %lf , %lf %c", &this->value(0), &this->value(1), &dummy) == 2 || sscanf(str.data(), " %lf x %lf %c", &this->value(0), &this->value(1), &dummy) == 2; } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; class ConfigOptionPoints : public ConfigOptionVector @@ -706,6 +917,7 @@ public: ConfigOption* clone() const override { return new ConfigOptionPoints(*this); } ConfigOptionPoints& operator=(const ConfigOption *opt) { this->set(opt); return *this; } bool operator==(const ConfigOptionPoints &rhs) const { return this->values == rhs.values; } + bool is_nil(size_t idx) const override { return false; } std::string serialize() const override { @@ -750,8 +962,21 @@ public: } return true; } -}; +private: + friend class cereal::access; + template void save(Archive& archive) const { + size_t cnt = this->values.size(); + archive(cnt); + archive.saveBinary((const char*)this->values.data(), sizeof(Vec2d) * cnt); + } + template void load(Archive& archive) { + size_t cnt; + archive(cnt); + this->values.assign(cnt, Vec2d()); + archive.loadBinary((char*)this->values.data(), sizeof(Vec2d) * cnt); + } +}; class ConfigOptionPoint3 : public ConfigOptionSingle { @@ -783,6 +1008,10 @@ public: return sscanf(str.data(), " %lf , %lf , %lf %c", &this->value(0), &this->value(1), &this->value(2), &dummy) == 2 || sscanf(str.data(), " %lf x %lf x %lf %c", &this->value(0), &this->value(1), &this->value(2), &dummy) == 2; } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; class ConfigOptionBool : public ConfigOptionSingle @@ -809,20 +1038,35 @@ public: this->value = (str.compare("1") == 0); return true; } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; -class ConfigOptionBools : public ConfigOptionVector +template +class ConfigOptionBoolsTempl : public ConfigOptionVector { public: - ConfigOptionBools() : ConfigOptionVector() {} - explicit ConfigOptionBools(size_t n, bool value) : ConfigOptionVector(n, (unsigned char)value) {} - explicit ConfigOptionBools(std::initializer_list il) { values.reserve(il.size()); for (bool b : il) values.emplace_back((unsigned char)b); } + ConfigOptionBoolsTempl() : ConfigOptionVector() {} + explicit ConfigOptionBoolsTempl(size_t n, bool value) : ConfigOptionVector(n, (unsigned char)value) {} + explicit ConfigOptionBoolsTempl(std::initializer_list il) { values.reserve(il.size()); for (bool b : il) values.emplace_back((unsigned char)b); } + explicit ConfigOptionBoolsTempl(std::initializer_list il) { values.reserve(il.size()); for (unsigned char b : il) values.emplace_back(b); } + explicit ConfigOptionBoolsTempl(const std::vector& vec) : ConfigOptionVector(vec) {} + explicit ConfigOptionBoolsTempl(std::vector&& vec) : ConfigOptionVector(std::move(vec)) {} static ConfigOptionType static_type() { return coBools; } ConfigOptionType type() const override { return static_type(); } - ConfigOption* clone() const override { return new ConfigOptionBools(*this); } - ConfigOptionBools& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionBools &rhs) const { return this->values == rhs.values; } + ConfigOption* clone() const override { return new ConfigOptionBoolsTempl(*this); } + ConfigOptionBoolsTempl& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionBoolsTempl &rhs) const { return this->values == rhs.values; } + // Could a special "nil" value be stored inside the vector, indicating undefined value? + bool nullable() const override { return NULLABLE; } + // Special "nil" value to be stored into the vector if this->supports_nil(). + static unsigned char nil_value() { return std::numeric_limits::max(); } + // A scalar is nil, or all values of a vector are nil. + bool is_nil() const override { for (auto v : this->values) if (v != nil_value()) return false; return true; } + bool is_nil(size_t idx) const override { return this->values[idx] == nil_value(); } bool& get_at(size_t i) { assert(! this->values.empty()); @@ -835,19 +1079,20 @@ public: std::string serialize() const override { std::ostringstream ss; - for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { - if (it - this->values.begin() != 0) ss << ","; - ss << (*it ? "1" : "0"); - } + for (const unsigned char &v : this->values) { + if (&v != &this->values.front()) + ss << ","; + this->serialize_single_value(ss, v); + } return ss.str(); } std::vector vserialize() const override { std::vector vv; - for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { - std::ostringstream ss; - ss << (*it ? "1" : "0"); + for (const unsigned char v : this->values) { + std::ostringstream ss; + this->serialize_single_value(ss, v); vv.push_back(ss.str()); } return vv; @@ -860,12 +1105,37 @@ public: std::istringstream is(str); std::string item_str; while (std::getline(is, item_str, ',')) { - this->values.push_back(item_str.compare("1") == 0); + boost::trim(item_str); + if (item_str == "nil") { + if (NULLABLE) + this->values.push_back(nil_value()); + else + std::runtime_error("Deserializing nil into a non-nullable object"); + } else + this->values.push_back(item_str.compare("1") == 0); } return true; } + +protected: + void serialize_single_value(std::ostringstream &ss, const unsigned char v) const { + if (v == nil_value()) { + if (NULLABLE) + ss << "nil"; + else + std::runtime_error("Serializing NaN"); + } else + ss << (v ? "1" : "0"); + } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; +using ConfigOptionBools = ConfigOptionBoolsTempl; +using ConfigOptionBoolsNullable = ConfigOptionBoolsTempl; + // Map from an enum integer value to an enum name. typedef std::vector t_config_enum_names; // Map from an enum name to an enum integer value. @@ -1002,19 +1272,95 @@ public: this->value = it->second; return true; } + +private: + friend class cereal::access; + template void serialize(Archive& ar) { ar(cereal::base_class(this)); } }; // Definition of a configuration value for the purpose of GUI presentation, editing, value mapping and config file handling. class ConfigOptionDef { public: + // Identifier of this option. It is stored here so that it is accessible through the by_serialization_key_ordinal map. + t_config_option_key opt_key; // What type? bool, int, string etc. ConfigOptionType type = coNone; + // If a type is nullable, then it accepts a "nil" value (scalar) or "nil" values (vector). + bool nullable = false; // Default value of this option. The default value object is owned by ConfigDef, it is released in its destructor. Slic3r::clonable_ptr default_value; - void set_default_value(const ConfigOption* ptr) { this->default_value = Slic3r::clonable_ptr(ptr); } - template - const T* get_default_value() const { return static_cast(this->default_value.get()); } + void set_default_value(const ConfigOption* ptr) { this->default_value = Slic3r::clonable_ptr(ptr); } + template const T* get_default_value() const { return static_cast(this->default_value.get()); } + + // Create an empty option to be used as a base for deserialization of DynamicConfig. + ConfigOption* create_empty_option() const; + // Create a default option to be inserted into a DynamicConfig. + ConfigOption* create_default_option() const; + + template ConfigOption* load_option_from_archive(Archive &archive) const { + if (this->nullable) { + switch (this->type) { + case coFloats: { auto opt = new ConfigOptionFloatsNullable(); archive(*opt); return opt; } + case coInts: { auto opt = new ConfigOptionIntsNullable(); archive(*opt); return opt; } + case coPercents: { auto opt = new ConfigOptionPercentsNullable();archive(*opt); return opt; } + case coBools: { auto opt = new ConfigOptionBoolsNullable(); archive(*opt); return opt; } + default: throw std::runtime_error(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key); + } + } else { + switch (this->type) { + case coFloat: { auto opt = new ConfigOptionFloat(); archive(*opt); return opt; } + case coFloats: { auto opt = new ConfigOptionFloats(); archive(*opt); return opt; } + case coInt: { auto opt = new ConfigOptionInt(); archive(*opt); return opt; } + case coInts: { auto opt = new ConfigOptionInts(); archive(*opt); return opt; } + case coString: { auto opt = new ConfigOptionString(); archive(*opt); return opt; } + case coStrings: { auto opt = new ConfigOptionStrings(); archive(*opt); return opt; } + case coPercent: { auto opt = new ConfigOptionPercent(); archive(*opt); return opt; } + case coPercents: { auto opt = new ConfigOptionPercents(); archive(*opt); return opt; } + case coFloatOrPercent: { auto opt = new ConfigOptionFloatOrPercent(); archive(*opt); return opt; } + case coPoint: { auto opt = new ConfigOptionPoint(); archive(*opt); return opt; } + case coPoints: { auto opt = new ConfigOptionPoints(); archive(*opt); return opt; } + case coPoint3: { auto opt = new ConfigOptionPoint3(); archive(*opt); return opt; } + case coBool: { auto opt = new ConfigOptionBool(); archive(*opt); return opt; } + case coBools: { auto opt = new ConfigOptionBools(); archive(*opt); return opt; } + case coEnum: { auto opt = new ConfigOptionEnumGeneric(this->enum_keys_map); archive(*opt); return opt; } + default: throw std::runtime_error(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key); + } + } + } + + template ConfigOption* save_option_to_archive(Archive &archive, const ConfigOption *opt) const { + if (this->nullable) { + switch (this->type) { + case coFloats: archive(*static_cast(opt)); break; + case coInts: archive(*static_cast(opt)); break; + case coPercents: archive(*static_cast(opt));break; + case coBools: archive(*static_cast(opt)); break; + default: throw std::runtime_error(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key); + } + } else { + switch (this->type) { + case coFloat: archive(*static_cast(opt)); break; + case coFloats: archive(*static_cast(opt)); break; + case coInt: archive(*static_cast(opt)); break; + case coInts: archive(*static_cast(opt)); break; + case coString: archive(*static_cast(opt)); break; + case coStrings: archive(*static_cast(opt)); break; + case coPercent: archive(*static_cast(opt)); break; + case coPercents: archive(*static_cast(opt)); break; + case coFloatOrPercent: archive(*static_cast(opt)); break; + case coPoint: archive(*static_cast(opt)); break; + case coPoints: archive(*static_cast(opt)); break; + case coPoint3: archive(*static_cast(opt)); break; + case coBool: archive(*static_cast(opt)); break; + case coBools: archive(*static_cast(opt)); break; + case coEnum: archive(*static_cast(opt)); break; + default: throw std::runtime_error(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key); + } + } + // Make the compiler happy, shut up the warnings. + return nullptr; + } // Usually empty. // Special values - "i_enum_open", "f_enum_open" to provide combo box for int or float selection, @@ -1084,6 +1430,9 @@ public: return false; } + // 0 is an invalid key. + size_t serialization_key_ordinal = 0; + // Returns the alternative CLI arguments for the given option. // If there are no cli arguments defined, use the key and replace underscores with dashes. std::vector cli_args(const std::string &key) const; @@ -1103,7 +1452,8 @@ typedef std::map t_optiondef_map; class ConfigDef { public: - t_optiondef_map options; + t_optiondef_map options; + std::map by_serialization_key_ordinal; bool has(const t_config_option_key &opt_key) const { return this->options.count(opt_key) > 0; } const ConfigOptionDef* get(const t_config_option_key &opt_key) const { @@ -1124,11 +1474,8 @@ public: std::function filter = [](const ConfigOptionDef &){ return true; }) const; protected: - ConfigOptionDef* add(const t_config_option_key &opt_key, ConfigOptionType type) { - ConfigOptionDef* opt = &this->options[opt_key]; - opt->type = type; - return opt; - } + ConfigOptionDef* add(const t_config_option_key &opt_key, ConfigOptionType type); + ConfigOptionDef* add_nullable(const t_config_option_key &opt_key, ConfigOptionType type); }; // An abstract configuration store. @@ -1197,7 +1544,7 @@ public: bool equals(const ConfigBase &other) const { return this->diff(other).empty(); } t_config_option_keys diff(const ConfigBase &other) const; t_config_option_keys equal(const ConfigBase &other) const; - std::string serialize(const t_config_option_key &opt_key) const; + std::string opt_serialize(const t_config_option_key &opt_key) const; // Set a configuration value from a string, it will call an overridable handle_legacy() // to resolve renamed and removed configuration keys. bool set_deserialize(const t_config_option_key &opt_key, const std::string &str, bool append = false); @@ -1213,6 +1560,9 @@ public: void load(const boost::property_tree::ptree &tree); void save(const std::string &file) const; + // Set all the nullable values to nils. + void null_nullables(); + private: // Set a configuration value from a string. bool set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &str, bool append); @@ -1235,7 +1585,7 @@ public: assert(this->def() == nullptr || this->def() == rhs.def()); this->clear(); for (const auto &kvp : rhs.options) - this->options[kvp.first] = kvp.second->clone(); + this->options[kvp.first].reset(kvp.second->clone()); return *this; } @@ -1258,15 +1608,13 @@ public: for (const auto &kvp : rhs.options) { auto it = this->options.find(kvp.first); if (it == this->options.end()) - this->options[kvp.first] = kvp.second->clone(); + this->options[kvp.first].reset(kvp.second->clone()); else { assert(it->second->type() == kvp.second->type()); if (it->second->type() == kvp.second->type()) *it->second = *kvp.second; - else { - delete it->second; - it->second = kvp.second->clone(); - } + else + it->second.reset(kvp.second->clone()); } } return *this; @@ -1277,14 +1625,13 @@ public: DynamicConfig& operator+=(DynamicConfig &&rhs) { assert(this->def() == nullptr || this->def() == rhs.def()); - for (const auto &kvp : rhs.options) { + for (auto &kvp : rhs.options) { auto it = this->options.find(kvp.first); if (it == this->options.end()) { - this->options[kvp.first] = kvp.second; + this->options.insert(std::make_pair(kvp.first, std::move(kvp.second))); } else { assert(it->second->type() == kvp.second->type()); - delete it->second; - it->second = kvp.second; + it->second = std::move(kvp.second); } } rhs.options.clear(); @@ -1301,8 +1648,6 @@ public: void clear() { - for (auto &opt : this->options) - delete opt.second; this->options.clear(); } @@ -1311,14 +1656,16 @@ public: auto it = this->options.find(opt_key); if (it == this->options.end()) return false; - delete it->second; this->options.erase(it); return true; } + // Remove options with all nil values, those are optional and it does not help to hold them. + size_t remove_nil_options(); + // Allow DynamicConfig to be instantiated on ints own without a definition. // If the definition is not defined, the method requiring the definition will throw NoDefinitionException. - const ConfigDef* def() const override { return nullptr; }; + const ConfigDef* def() const override { return nullptr; } template T* opt(const t_config_option_key &opt_key, bool create = false) { return dynamic_cast(this->option(opt_key, create)); } template const T* opt(const t_config_option_key &opt_key) const @@ -1336,11 +1683,10 @@ public: { auto it = this->options.find(opt_key); if (it == this->options.end()) { - this->options[opt_key] = opt; + this->options[opt_key].reset(opt); return true; } else { - delete it->second; - it->second = opt; + it->second.reset(opt); return false; } } @@ -1370,12 +1716,15 @@ public: void read_cli(const std::vector &tokens, t_config_option_keys* extra, t_config_option_keys* keys = nullptr); bool read_cli(int argc, char** argv, t_config_option_keys* extra, t_config_option_keys* keys = nullptr); - typedef std::map t_options_map; - t_options_map::const_iterator cbegin() const { return options.cbegin(); } - t_options_map::const_iterator cend() const { return options.cend(); } + std::map>::const_iterator cbegin() const { return options.cbegin(); } + std::map>::const_iterator cend() const { return options.cend(); } + size_t size() const { return options.size(); } private: - t_options_map options; + std::map> options; + + friend class cereal::access; + template void serialize(Archive &ar) { ar(options); } }; /// Configuration store with a static definition of configuration values. diff --git a/src/libslic3r/EdgeGrid.cpp b/src/libslic3r/EdgeGrid.cpp index c34aca8f2c..f40d499de1 100644 --- a/src/libslic3r/EdgeGrid.cpp +++ b/src/libslic3r/EdgeGrid.cpp @@ -11,6 +11,7 @@ #include "libslic3r.h" #include "ClipperUtils.hpp" #include "EdgeGrid.hpp" +#include "Geometry.hpp" #include "SVG.hpp" #if 0 @@ -146,10 +147,10 @@ void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) coord_t iy = p1(1) / m_resolution; coord_t ixb = p2(0) / m_resolution; coord_t iyb = p2(1) / m_resolution; - assert(ix >= 0 && ix < m_cols); - assert(iy >= 0 && iy < m_rows); - assert(ixb >= 0 && ixb < m_cols); - assert(iyb >= 0 && iyb < m_rows); + assert(ix >= 0 && size_t(ix) < m_cols); + assert(iy >= 0 && size_t(iy) < m_rows); + assert(ixb >= 0 && size_t(ixb) < m_cols); + assert(iyb >= 0 && size_t(iyb) < m_rows); // Account for the end points. ++ m_cells[iy*m_cols+ix].end; if (ix == ixb && iy == iyb) @@ -275,134 +276,24 @@ void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) // 6) Finally fill in m_cell_data by rasterizing the lines once again. for (size_t i = 0; i < m_cells.size(); ++i) m_cells[i].end = m_cells[i].begin; - for (size_t i = 0; i < m_contours.size(); ++i) { - const Slic3r::Points &pts = *m_contours[i]; - for (size_t j = 0; j < pts.size(); ++j) { - // End points of the line segment. - Slic3r::Point p1(pts[j]); - Slic3r::Point p2 = pts[(j + 1 == pts.size()) ? 0 : j + 1]; - p1(0) -= m_bbox.min(0); - p1(1) -= m_bbox.min(1); - p2(0) -= m_bbox.min(0); - p2(1) -= m_bbox.min(1); - // Get the cells of the end points. - coord_t ix = p1(0) / m_resolution; - coord_t iy = p1(1) / m_resolution; - coord_t ixb = p2(0) / m_resolution; - coord_t iyb = p2(1) / m_resolution; - assert(ix >= 0 && ix < m_cols); - assert(iy >= 0 && iy < m_rows); - assert(ixb >= 0 && ixb < m_cols); - assert(iyb >= 0 && iyb < m_rows); - // Account for the end points. - m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair(i, j); - if (ix == ixb && iy == iyb) - // Both ends fall into the same cell. - continue; - // Raster the centeral part of the line. - coord_t dx = std::abs(p2(0) - p1(0)); - coord_t dy = std::abs(p2(1) - p1(1)); - if (p1(0) < p2(0)) { - int64_t ex = int64_t((ix + 1)*m_resolution - p1(0)) * int64_t(dy); - if (p1(1) < p2(1)) { - // x positive, y positive - int64_t ey = int64_t((iy + 1)*m_resolution - p1(1)) * int64_t(dx); - do { - assert(ix <= ixb && iy <= iyb); - if (ex < ey) { - ey -= ex; - ex = int64_t(dy) * m_resolution; - ix += 1; - } - else if (ex == ey) { - ex = int64_t(dy) * m_resolution; - ey = int64_t(dx) * m_resolution; - ix += 1; - iy += 1; - } - else { - assert(ex > ey); - ex -= ey; - ey = int64_t(dx) * m_resolution; - iy += 1; - } - m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair(i, j); - } while (ix != ixb || iy != iyb); - } - else { - // x positive, y non positive - int64_t ey = int64_t(p1(1) - iy*m_resolution) * int64_t(dx); - do { - assert(ix <= ixb && iy >= iyb); - if (ex <= ey) { - ey -= ex; - ex = int64_t(dy) * m_resolution; - ix += 1; - } - else { - ex -= ey; - ey = int64_t(dx) * m_resolution; - iy -= 1; - } - m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair(i, j); - } while (ix != ixb || iy != iyb); - } - } - else { - int64_t ex = int64_t(p1(0) - ix*m_resolution) * int64_t(dy); - if (p1(1) < p2(1)) { - // x non positive, y positive - int64_t ey = int64_t((iy + 1)*m_resolution - p1(1)) * int64_t(dx); - do { - assert(ix >= ixb && iy <= iyb); - if (ex < ey) { - ey -= ex; - ex = int64_t(dy) * m_resolution; - ix -= 1; - } - else { - assert(ex >= ey); - ex -= ey; - ey = int64_t(dx) * m_resolution; - iy += 1; - } - m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair(i, j); - } while (ix != ixb || iy != iyb); - } - else { - // x non positive, y non positive - int64_t ey = int64_t(p1(1) - iy*m_resolution) * int64_t(dx); - do { - assert(ix >= ixb && iy >= iyb); - if (ex < ey) { - ey -= ex; - ex = int64_t(dy) * m_resolution; - ix -= 1; - } - else if (ex == ey) { - // The lower edge of a grid cell belongs to the cell. - // Handle the case where the ray may cross the lower left corner of a cell in a general case, - // or a left or lower edge in a degenerate case (horizontal or vertical line). - if (dx > 0) { - ex = int64_t(dy) * m_resolution; - ix -= 1; - } - if (dy > 0) { - ey = int64_t(dx) * m_resolution; - iy -= 1; - } - } - else { - assert(ex > ey); - ex -= ey; - ey = int64_t(dx) * m_resolution; - iy -= 1; - } - m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair(i, j); - } while (ix != ixb || iy != iyb); - } - } - } + + struct Visitor { + Visitor(std::vector> &cell_data, std::vector &cells, size_t cols) : + cell_data(cell_data), cells(cells), cols(cols), i(0), j(0) {} + + void operator()(coord_t iy, coord_t ix) { cell_data[cells[iy*cols + ix].end++] = std::pair(i, j); } + + std::vector> &cell_data; + std::vector &cells; + size_t cols; + size_t i; + size_t j; + } visitor(m_cell_data, m_cells, m_cols); + + for (; visitor.i < m_contours.size(); ++ visitor.i) { + const Slic3r::Points &pts = *m_contours[visitor.i]; + for (; visitor.j < pts.size(); ++ visitor.j) + this->visit_cells_intersecting_line(pts[visitor.j], pts[(visitor.j + 1 == pts.size()) ? 0 : visitor.j + 1], visitor); } } @@ -775,11 +666,11 @@ void EdgeGrid::Grid::calculate_sdf() // For each corner of this cell and its 1 ring neighbours: for (int corner_y = -1; corner_y < 3; ++ corner_y) { coord_t corner_r = r + corner_y; - if (corner_r < 0 || corner_r >= nrows) + if (corner_r < 0 || (size_t)corner_r >= nrows) continue; for (int corner_x = -1; corner_x < 3; ++ corner_x) { coord_t corner_c = c + corner_x; - if (corner_c < 0 || corner_c >= ncols) + if (corner_c < 0 || (size_t)corner_c >= ncols) continue; float &d_min = m_signed_distance_field[corner_r * ncols + corner_c]; Slic3r::Point pt(m_bbox.min(0) + corner_c * m_resolution, m_bbox.min(1) + corner_r * m_resolution); @@ -1137,9 +1028,9 @@ bool EdgeGrid::Grid::signed_distance_edges(const Point &pt, coord_t search_radiu return false; bbox.max(0) /= m_resolution; bbox.max(1) /= m_resolution; - if (bbox.max(0) >= m_cols) + if ((size_t)bbox.max(0) >= m_cols) bbox.max(0) = m_cols - 1; - if (bbox.max(1) >= m_rows) + if ((size_t)bbox.max(1) >= m_rows) bbox.max(1) = m_rows - 1; // Lower boundary, round to grid and test validity. bbox.min(0) -= search_radius; @@ -1360,28 +1251,6 @@ Polygons EdgeGrid::Grid::contours_simplified(coord_t offset, bool fill_holes) co return out; } -inline int segments_could_intersect( - const Slic3r::Point &ip1, const Slic3r::Point &ip2, - const Slic3r::Point &jp1, const Slic3r::Point &jp2) -{ - Vec2i64 iv = (ip2 - ip1).cast(); - Vec2i64 vij1 = (jp1 - ip1).cast(); - Vec2i64 vij2 = (jp2 - ip1).cast(); - int64_t tij1 = cross2(iv, vij1); - int64_t tij2 = cross2(iv, vij2); - int sij1 = (tij1 > 0) ? 1 : ((tij1 < 0) ? -1 : 0); // signum - int sij2 = (tij2 > 0) ? 1 : ((tij2 < 0) ? -1 : 0); - return sij1 * sij2; -} - -inline bool segments_intersect( - const Slic3r::Point &ip1, const Slic3r::Point &ip2, - const Slic3r::Point &jp1, const Slic3r::Point &jp2) -{ - return segments_could_intersect(ip1, ip2, jp1, jp2) <= 0 && - segments_could_intersect(jp1, jp2, ip1, ip2) <= 0; -} - std::vector> EdgeGrid::Grid::intersecting_edges() const { std::vector> out; @@ -1405,7 +1274,7 @@ std::vector> if (&ipts == &jpts && (&ip1 == &jp2 || &jp1 == &ip2)) // Segments of the same contour share a common vertex. continue; - if (segments_intersect(ip1, ip2, jp1, jp2)) { + if (Geometry::segments_intersect(ip1, ip2, jp1, jp2)) { // The two segments intersect. Add them to the output. int jfirst = (&jpts < &ipts) || (&jpts == &ipts && jpt < ipt); out.emplace_back(jfirst ? @@ -1440,7 +1309,7 @@ bool EdgeGrid::Grid::has_intersecting_edges() const const Slic3r::Point &jp1 = jpts[jpt]; const Slic3r::Point &jp2 = jpts[(jpt + 1 == jpts.size()) ? 0 : jpt + 1]; if (! (&ipts == &jpts && (&ip1 == &jp2 || &jp1 == &ip2)) && - segments_intersect(ip1, ip2, jp1, jp2)) + Geometry::segments_intersect(ip1, ip2, jp1, jp2)) return true; } } diff --git a/src/libslic3r/EdgeGrid.hpp b/src/libslic3r/EdgeGrid.hpp index f99ab30c46..cad20e07bb 100644 --- a/src/libslic3r/EdgeGrid.hpp +++ b/src/libslic3r/EdgeGrid.hpp @@ -65,6 +65,145 @@ public: std::vector> intersecting_edges() const; bool has_intersecting_edges() const; + template void visit_cells_intersecting_line(Slic3r::Point p1, Slic3r::Point p2, FUNCTION func) const + { + // End points of the line segment. + p1(0) -= m_bbox.min(0); + p1(1) -= m_bbox.min(1); + p2(0) -= m_bbox.min(0); + p2(1) -= m_bbox.min(1); + // Get the cells of the end points. + coord_t ix = p1(0) / m_resolution; + coord_t iy = p1(1) / m_resolution; + coord_t ixb = p2(0) / m_resolution; + coord_t iyb = p2(1) / m_resolution; + assert(ix >= 0 && size_t(ix) < m_cols); + assert(iy >= 0 && size_t(iy) < m_rows); + assert(ixb >= 0 && size_t(ixb) < m_cols); + assert(iyb >= 0 && size_t(iyb) < m_rows); + // Account for the end points. + func(iy, ix); + if (ix == ixb && iy == iyb) + // Both ends fall into the same cell. + return; + // Raster the centeral part of the line. + coord_t dx = std::abs(p2(0) - p1(0)); + coord_t dy = std::abs(p2(1) - p1(1)); + if (p1(0) < p2(0)) { + int64_t ex = int64_t((ix + 1)*m_resolution - p1(0)) * int64_t(dy); + if (p1(1) < p2(1)) { + // x positive, y positive + int64_t ey = int64_t((iy + 1)*m_resolution - p1(1)) * int64_t(dx); + do { + assert(ix <= ixb && iy <= iyb); + if (ex < ey) { + ey -= ex; + ex = int64_t(dy) * m_resolution; + ix += 1; + } + else if (ex == ey) { + ex = int64_t(dy) * m_resolution; + ey = int64_t(dx) * m_resolution; + ix += 1; + iy += 1; + } + else { + assert(ex > ey); + ex -= ey; + ey = int64_t(dx) * m_resolution; + iy += 1; + } + func(iy, ix); + } while (ix != ixb || iy != iyb); + } + else { + // x positive, y non positive + int64_t ey = int64_t(p1(1) - iy*m_resolution) * int64_t(dx); + do { + assert(ix <= ixb && iy >= iyb); + if (ex <= ey) { + ey -= ex; + ex = int64_t(dy) * m_resolution; + ix += 1; + } + else { + ex -= ey; + ey = int64_t(dx) * m_resolution; + iy -= 1; + } + func(iy, ix); + } while (ix != ixb || iy != iyb); + } + } + else { + int64_t ex = int64_t(p1(0) - ix*m_resolution) * int64_t(dy); + if (p1(1) < p2(1)) { + // x non positive, y positive + int64_t ey = int64_t((iy + 1)*m_resolution - p1(1)) * int64_t(dx); + do { + assert(ix >= ixb && iy <= iyb); + if (ex < ey) { + ey -= ex; + ex = int64_t(dy) * m_resolution; + ix -= 1; + } + else { + assert(ex >= ey); + ex -= ey; + ey = int64_t(dx) * m_resolution; + iy += 1; + } + func(iy, ix); + } while (ix != ixb || iy != iyb); + } + else { + // x non positive, y non positive + int64_t ey = int64_t(p1(1) - iy*m_resolution) * int64_t(dx); + do { + assert(ix >= ixb && iy >= iyb); + if (ex < ey) { + ey -= ex; + ex = int64_t(dy) * m_resolution; + ix -= 1; + } + else if (ex == ey) { + // The lower edge of a grid cell belongs to the cell. + // Handle the case where the ray may cross the lower left corner of a cell in a general case, + // or a left or lower edge in a degenerate case (horizontal or vertical line). + if (dx > 0) { + ex = int64_t(dy) * m_resolution; + ix -= 1; + } + if (dy > 0) { + ey = int64_t(dx) * m_resolution; + iy -= 1; + } + } + else { + assert(ex > ey); + ex -= ey; + ey = int64_t(dx) * m_resolution; + iy -= 1; + } + func(iy, ix); + } while (ix != ixb || iy != iyb); + } + } + } + + std::pair>::const_iterator, std::vector>::const_iterator> cell_data_range(coord_t row, coord_t col) const + { + const EdgeGrid::Grid::Cell &cell = m_cells[row * m_cols + col]; + return std::make_pair(m_cell_data.begin() + cell.begin, m_cell_data.begin() + cell.end); + } + + std::pair segment(const std::pair &contour_and_segment_idx) const + { + const Slic3r::Points &ipts = *m_contours[contour_and_segment_idx.first]; + size_t ipt = contour_and_segment_idx.second; + return std::pair(ipts[ipt], ipts[(ipt + 1 == ipts.size()) ? 0 : ipt + 1]); + } + protected: struct Cell { Cell() : begin(0), end(0) {} @@ -78,8 +217,8 @@ protected: #endif bool cell_inside_or_crossing(int r, int c) const { - if (r < 0 || r >= m_rows || - c < 0 || c >= m_cols) + if (r < 0 || (size_t)r >= m_rows || + c < 0 || (size_t)c >= m_cols) // The cell is outside the domain. Hoping that the contours were correctly oriented, so // there is a CCW outmost contour so the out of domain cells are outside. return false; diff --git a/src/libslic3r/ExtrusionSimulator.cpp b/src/libslic3r/ExtrusionSimulator.cpp index fcb2fe825a..8c2ab037f7 100644 --- a/src/libslic3r/ExtrusionSimulator.cpp +++ b/src/libslic3r/ExtrusionSimulator.cpp @@ -660,7 +660,7 @@ void gcode_spread_points( for (ExtrusionPoints::const_iterator it = points.begin(); it != points.end(); ++ it) { const V2f ¢er = it->center; const float radius = it->radius; - const float radius2 = radius * radius; + //const float radius2 = radius * radius; const float height_target = it->height; B2f bbox(center - V2f(radius, radius), center + V2f(radius, radius)); B2i bboxi( @@ -774,8 +774,8 @@ void gcode_spread_points( } } #endif - float area_circle_total2 = float(M_PI) * sqr(radius); - float area_err = fabs(area_circle_total2 - area_circle_total) / area_circle_total2; +// float area_circle_total2 = float(M_PI) * sqr(radius); +// float area_err = fabs(area_circle_total2 - area_circle_total) / area_circle_total2; // printf("area_circle_total: %f, %f, %f\n", area_circle_total, area_circle_total2, area_err); float volume_full = float(M_PI) * sqr(radius) * height_target; // if (true) { @@ -905,8 +905,8 @@ void ExtrusionSimulator::set_image_size(const Point &image_size) // printf("Allocating image data, allocated\n"); //FIXME fill the image with red vertical lines. - for (size_t r = 0; r < image_size.y(); ++ r) { - for (size_t c = 0; c < image_size.x(); c += 2) { + for (size_t r = 0; r < size_t(image_size.y()); ++ r) { + for (size_t c = 0; c < size_t(image_size.x()); c += 2) { // Color red pimpl->image_data[r * image_size.x() * 4 + c * 4] = 255; // Opacity full @@ -958,7 +958,7 @@ void ExtrusionSimulator::extrude_to_accumulator(const ExtrusionPath &path, const float scalex = float(viewport.size().x()) / float(bbox.size().x()); float scaley = float(viewport.size().y()) / float(bbox.size().y()); float w = scale_(path.width) * scalex; - float h = scale_(path.height) * scalex; + //float h = scale_(path.height) * scalex; w = scale_(path.mm3_per_mm / path.height) * scalex; // printf("scalex: %f, scaley: %f\n", scalex, scaley); // printf("bbox: %d,%d %d,%d\n", bbox.min.x(), bbox.min.y, bbox.max.x(), bbox.max.y); @@ -993,8 +993,8 @@ void ExtrusionSimulator::evaluate_accumulator(ExtrusionSimulationType simulation for (int r = 0; r < sz.y(); ++r) { for (int c = 0; c < sz.x(); ++c) { float p = 0; - for (int j = 0; j < pimpl->bitmap_oversampled; ++ j) { - for (int i = 0; i < pimpl->bitmap_oversampled; ++ i) { + for (unsigned int j = 0; j < pimpl->bitmap_oversampled; ++ j) { + for (unsigned int i = 0; i < pimpl->bitmap_oversampled; ++ i) { if (pimpl->bitmap[r * pimpl->bitmap_oversampled + j][c * pimpl->bitmap_oversampled + i]) p += 1.f; } diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 75952e4c23..fbdef29b98 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -126,7 +126,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) Polygons surfaces_polygons = to_polygons(surfaces); Polygons collapsed = diff( surfaces_polygons, - offset2(surfaces_polygons, -distance_between_surfaces/2, +distance_between_surfaces/2), + offset2(surfaces_polygons, (float)-distance_between_surfaces/2, (float)+distance_between_surfaces/2), true); Polygons to_subtract; to_subtract.reserve(collapsed.size() + number_polygons(surfaces)); @@ -137,7 +137,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) surfaces_append( surfaces, intersection_ex( - offset(collapsed, distance_between_surfaces), + offset(collapsed, (float)distance_between_surfaces), to_subtract, true), stInternalSolid); @@ -219,14 +219,14 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) f->z = layerm.layer()->print_z; f->angle = float(Geometry::deg2rad(layerm.region()->config().fill_angle.value)); // Maximum length of the perimeter segment linking two infill lines. - f->link_max_length = scale_(link_max_length); + f->link_max_length = (coord_t)scale_(link_max_length); // Used by the concentric infill pattern to clip the loops to create extrusion paths. - f->loop_clipping = scale_(flow.nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER; + f->loop_clipping = coord_t(scale_(flow.nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER); // f->layer_height = h; // apply half spacing using this flow's own spacing and generate infill FillParams params; - params.density = 0.01 * density; + params.density = float(0.01 * density); // params.dont_adjust = true; params.dont_adjust = false; Polylines polylines = f->fill_surface(&surface, params); @@ -240,7 +240,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) // so we can safely ignore the slight variation that might have // been applied to $f->flow_spacing } else { - flow = Flow::new_from_spacing(f->spacing, flow.nozzle_diameter, h, is_bridge || f->use_bridge_flow()); + flow = Flow::new_from_spacing(f->spacing, flow.nozzle_diameter, (float)h, is_bridge || f->use_bridge_flow()); } // Save into layer. diff --git a/src/libslic3r/Fill/FillPlanePath.cpp b/src/libslic3r/Fill/FillPlanePath.cpp index c52353b02a..3b9266a0fe 100644 --- a/src/libslic3r/Fill/FillPlanePath.cpp +++ b/src/libslic3r/Fill/FillPlanePath.cpp @@ -130,14 +130,14 @@ static inline Point hilbert_n_to_xy(const size_t n) } } int state = (ndigits & 1) ? 4 : 0; - int dirstate = (ndigits & 1) ? 0 : 4; +// int dirstate = (ndigits & 1) ? 0 : 4; coord_t x = 0; coord_t y = 0; for (int i = (int)ndigits - 1; i >= 0; -- i) { int digit = (n >> (i * 2)) & 3; state += digit; - if (digit != 3) - dirstate = state; // lowest non-3 digit +// if (digit != 3) +// dirstate = state; // lowest non-3 digit x |= digit_to_x[state] << i; y |= digit_to_y[state] << i; state = next_state[state]; diff --git a/src/libslic3r/Fill/FillRectilinear2.cpp b/src/libslic3r/Fill/FillRectilinear2.cpp index 65440d0efe..8aea758860 100644 --- a/src/libslic3r/Fill/FillRectilinear2.cpp +++ b/src/libslic3r/Fill/FillRectilinear2.cpp @@ -287,7 +287,8 @@ public: assert(aoffset1 < 0); assert(aoffset2 < 0); assert(aoffset2 < aoffset1); - bool sticks_removed = remove_sticks(polygons_src); +// bool sticks_removed = + remove_sticks(polygons_src); // if (sticks_removed) printf("Sticks removed!\n"); polygons_outer = offset(polygons_src, aoffset1, ClipperLib::jtMiter, @@ -481,7 +482,7 @@ static inline IntersectionTypeOtherVLine intersection_type_on_prev_next_vertical { // This routine will propose a connecting line even if the connecting perimeter segment intersects // iVertical line multiple times before reaching iIntersectionOther. - if (iIntersectionOther == -1) + if (iIntersectionOther == size_t(-1)) return INTERSECTION_TYPE_OTHER_VLINE_UNDEFINED; assert(dir_is_next ? (iVerticalLine + 1 < segs.size()) : (iVerticalLine > 0)); const SegmentedIntersectionLine &il_this = segs[iVerticalLine]; @@ -858,8 +859,8 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP if (il > ir) // No vertical line intersects this segment. continue; - assert(il >= 0 && il < segs.size()); - assert(ir >= 0 && ir < segs.size()); + assert(il >= 0 && size_t(il) < segs.size()); + assert(ir >= 0 && size_t(ir) < segs.size()); for (int i = il; i <= ir; ++ i) { coord_t this_x = segs[i].pos; assert(this_x == i * line_spacing + x0); @@ -1159,8 +1160,8 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP int iSegAbove = -1; int iSegBelow = -1; { - SegmentIntersection::SegmentIntersectionType type_crossing = (intrsctn->type == SegmentIntersection::INNER_LOW) ? - SegmentIntersection::INNER_HIGH : SegmentIntersection::INNER_LOW; +// SegmentIntersection::SegmentIntersectionType type_crossing = (intrsctn->type == SegmentIntersection::INNER_LOW) ? +// SegmentIntersection::INNER_HIGH : SegmentIntersection::INNER_LOW; // Does the perimeter intersect the current vertical line above intrsctn? for (size_t i = i_intersection + 1; i + 1 < seg.intersections.size(); ++ i) // if (seg.intersections[i].iContour == intrsctn->iContour && seg.intersections[i].type == type_crossing) { diff --git a/src/libslic3r/Fill/FillRectilinear3.cpp b/src/libslic3r/Fill/FillRectilinear3.cpp index 8a0b90ead3..078feeae92 100644 --- a/src/libslic3r/Fill/FillRectilinear3.cpp +++ b/src/libslic3r/Fill/FillRectilinear3.cpp @@ -15,7 +15,7 @@ #include "FillRectilinear3.hpp" - #define SLIC3R_DEBUG +// #define SLIC3R_DEBUG // Make assert active if SLIC3R_DEBUG #ifdef SLIC3R_DEBUG @@ -849,7 +849,7 @@ static inline IntersectionTypeOtherVLine intersection_type_on_prev_next_vertical { // This routine will propose a connecting line even if the connecting perimeter segment intersects // iVertical line multiple times before reaching iIntersectionOther. - if (iIntersectionOther == -1) + if (iIntersectionOther == size_t(-1)) return INTERSECTION_TYPE_OTHER_VLINE_UNDEFINED; assert(dir_is_next ? (iVerticalLine + 1 < segs.size()) : (iVerticalLine > 0)); const SegmentedIntersectionLine &il_this = segs[iVerticalLine]; @@ -1284,8 +1284,8 @@ static bool fill_hatching_segments_legacy( int iSegAbove = -1; int iSegBelow = -1; { - SegmentIntersection::SegmentIntersectionType type_crossing = (intrsctn->type == SegmentIntersection::INNER_LOW) ? - SegmentIntersection::INNER_HIGH : SegmentIntersection::INNER_LOW; +// SegmentIntersection::SegmentIntersectionType type_crossing = (intrsctn->type == SegmentIntersection::INNER_LOW) ? +// SegmentIntersection::INNER_HIGH : SegmentIntersection::INNER_LOW; // Does the perimeter intersect the current vertical line above intrsctn? for (size_t i = i_intersection + 1; i + 1 < seg.intersections.size(); ++ i) // if (seg.intersections[i].iContour == intrsctn->iContour && seg.intersections[i].type == type_crossing) { diff --git a/src/libslic3r/Flow.cpp b/src/libslic3r/Flow.cpp index e71b935dbb..6069677a1b 100644 --- a/src/libslic3r/Flow.cpp +++ b/src/libslic3r/Flow.cpp @@ -76,10 +76,14 @@ float Flow::spacing() const return this->width + BRIDGE_EXTRA_SPACING; // rectangle with semicircles at the ends float min_flow_spacing = this->width - this->height * (1. - 0.25 * PI); - return this->width - PERIMETER_LINE_OVERLAP_FACTOR * (this->width - min_flow_spacing); + float res = this->width - PERIMETER_LINE_OVERLAP_FACTOR * (this->width - min_flow_spacing); #else - return float(this->bridge ? (this->width + BRIDGE_EXTRA_SPACING) : (this->width - this->height * (1. - 0.25 * PI))); + float res = float(this->bridge ? (this->width + BRIDGE_EXTRA_SPACING) : (this->width - this->height * (1. - 0.25 * PI))); #endif +// assert(res > 0.f); + if (res <= 0.f) + throw std::runtime_error("Flow::spacing() produced negative spacing. Did you set some extrusion width too small?"); + return res; } // This method returns the centerline spacing between an extrusion using this @@ -89,20 +93,26 @@ float Flow::spacing(const Flow &other) const { assert(this->height == other.height); assert(this->bridge == other.bridge); - return float(this->bridge ? + float res = float(this->bridge ? 0.5 * this->width + 0.5 * other.width + BRIDGE_EXTRA_SPACING : 0.5 * this->spacing() + 0.5 * other.spacing()); +// assert(res > 0.f); + if (res <= 0.f) + throw std::runtime_error("Flow::spacing() produced negative spacing. Did you set some extrusion width too small?"); + return res; } // This method returns extrusion volume per head move unit. double Flow::mm3_per_mm() const { - double res = this->bridge ? + float res = this->bridge ? // Area of a circle with dmr of this->width. (this->width * this->width) * 0.25 * PI : // Rectangle with semicircles at the ends. ~ h (w - 0.215 h) this->height * (this->width - this->height * (1. - 0.25 * PI)); - assert(res > 0.); + //assert(res > 0.); + if (res <= 0.) + throw std::runtime_error("Flow::mm3_per_mm() produced negative flow. Did you set some extrusion width too small?"); return res; } diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 7104391134..9da0d4fd99 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -11,13 +11,19 @@ #include #include #include +#include #include #include #include +#include +#include +#include +namespace pt = boost::property_tree; + #include #include -#include +#include "miniz_extension.hpp" // VERSION NUMBERS // 0 : .3mf, files saved by older slic3r or other applications. No version definition in them. @@ -33,6 +39,7 @@ const std::string RELATIONSHIPS_FILE = "_rels/.rels"; const std::string PRINT_CONFIG_FILE = "Metadata/Slic3r_PE.config"; const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config"; const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt"; +const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Prusa_Slicer_layer_config_ranges.xml"; const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt"; const char* MODEL_TAG = "model"; @@ -64,6 +71,7 @@ const char* V2_ATTR = "v2"; const char* V3_ATTR = "v3"; const char* OBJECTID_ATTR = "objectid"; const char* TRANSFORM_ATTR = "transform"; +const char* PRINTABLE_ATTR = "printable"; const char* KEY_ATTR = "key"; const char* VALUE_ATTR = "value"; @@ -124,6 +132,12 @@ int get_attribute_value_int(const char** attributes, unsigned int attributes_siz return (text != nullptr) ? ::atoi(text) : 0; } +bool get_attribute_value_bool(const char** attributes, unsigned int attributes_size, const char* attribute_key) +{ + const char* text = get_attribute_value_charptr(attributes, attributes_size, attribute_key); + return (text != nullptr) ? (bool)::atoi(text) : true; +} + Slic3r::Transform3d get_transform_from_string(const std::string& mat_str) { if (mat_str.empty()) @@ -188,24 +202,6 @@ bool is_valid_object_type(const std::string& type) namespace Slic3r { -namespace { -void close_archive_reader(mz_zip_archive *arch) { - if (arch) { - FILE *f = mz_zip_get_cfile(arch); - mz_zip_reader_end(arch); - if (f) fclose(f); - } -} - -void close_archive_writer(mz_zip_archive *arch) { - if (arch) { - FILE *f = mz_zip_get_cfile(arch); - mz_zip_writer_end(arch); - if (f) fclose(f); - } -} -} - // Base class with error messages management class _3MF_Base { @@ -349,10 +345,12 @@ void close_archive_writer(mz_zip_archive *arch) { typedef std::map IdToMetadataMap; typedef std::map IdToGeometryMap; typedef std::map> IdToLayerHeightsProfileMap; + typedef std::map IdToLayerConfigRangesMap; typedef std::map> IdToSlaSupportPointsMap; // Version of the 3mf file unsigned int m_version; + bool m_check_version; XML_Parser m_xml_parser; Model* m_model; @@ -365,6 +363,7 @@ void close_archive_writer(mz_zip_archive *arch) { CurrentConfig m_curr_config; IdToMetadataMap m_objects_metadata; IdToLayerHeightsProfileMap m_layer_heights_profiles; + IdToLayerConfigRangesMap m_layer_config_ranges; IdToSlaSupportPointsMap m_sla_support_points; std::string m_curr_metadata_name; std::string m_curr_characters; @@ -374,7 +373,7 @@ void close_archive_writer(mz_zip_archive *arch) { _3MF_Importer(); ~_3MF_Importer(); - bool load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config); + bool load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, bool check_version); private: void _destroy_xml_parser(); @@ -383,6 +382,7 @@ void close_archive_writer(mz_zip_archive *arch) { bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config); bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, const std::string& archive_filename); @@ -436,7 +436,7 @@ void close_archive_writer(mz_zip_archive *arch) { bool _handle_start_metadata(const char** attributes, unsigned int num_attributes); bool _handle_end_metadata(); - bool _create_object_instance(int object_id, const Transform3d& transform, unsigned int recur_counter); + bool _create_object_instance(int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter); void _apply_transform(ModelInstance& instance, const Transform3d& transform); @@ -466,6 +466,7 @@ void close_archive_writer(mz_zip_archive *arch) { _3MF_Importer::_3MF_Importer() : m_version(0) + , m_check_version(false) , m_xml_parser(nullptr) , m_model(nullptr) , m_unit_factor(1.0f) @@ -480,9 +481,10 @@ void close_archive_writer(mz_zip_archive *arch) { _destroy_xml_parser(); } - bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config) + bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, bool check_version) { m_version = 0; + m_check_version = check_version; m_model = &model; m_unit_factor = 1.0f; m_curr_object.reset(); @@ -494,6 +496,7 @@ void close_archive_writer(mz_zip_archive *arch) { m_curr_config.volume_id = -1; m_objects_metadata.clear(); m_layer_heights_profiles.clear(); + m_layer_config_ranges.clear(); m_sla_support_points.clear(); m_curr_metadata_name.clear(); m_curr_characters.clear(); @@ -522,10 +525,7 @@ void close_archive_writer(mz_zip_archive *arch) { mz_zip_archive archive; mz_zip_zero_struct(&archive); - FILE *f = boost::nowide::fopen(filename.c_str(), "rb"); - auto res = mz_bool(f != nullptr && mz_zip_reader_init_cfile(&archive, f, -1, 0)); - if (res == 0) - { + if (!open_zip_reader(&archive, filename)) { add_error("Unable to open the file"); return false; } @@ -546,12 +546,21 @@ void close_archive_writer(mz_zip_archive *arch) { if (boost::algorithm::istarts_with(name, MODEL_FOLDER) && boost::algorithm::iends_with(name, MODEL_EXTENSION)) { - // valid model name -> extract model - if (!_extract_model_from_archive(archive, stat)) + try { - close_archive_reader(&archive); - add_error("Archive does not contain a valid model"); - return false; + // valid model name -> extract model + if (!_extract_model_from_archive(archive, stat)) + { + close_zip_reader(&archive); + add_error("Archive does not contain a valid model"); + return false; + } + } + catch (const std::exception& e) + { + // ensure the zip archive is closed and rethrow the exception + close_zip_reader(&archive); + throw e; } } } @@ -567,9 +576,14 @@ void close_archive_writer(mz_zip_archive *arch) { if (boost::algorithm::iequals(name, LAYER_HEIGHTS_PROFILE_FILE)) { - // extract slic3r lazer heights profile file + // extract slic3r layer heights profile file _extract_layer_heights_profile_config_from_archive(archive, stat); } + if (boost::algorithm::iequals(name, LAYER_CONFIG_RANGES_FILE)) + { + // extract slic3r layer config ranges file + _extract_layer_config_ranges_from_archive(archive, stat); + } else if (boost::algorithm::iequals(name, SLA_SUPPORT_POINTS_FILE)) { // extract sla support points file @@ -585,7 +599,7 @@ void close_archive_writer(mz_zip_archive *arch) { // extract slic3r model config file if (!_extract_model_config_from_archive(archive, stat, model)) { - close_archive_reader(&archive); + close_zip_reader(&archive); add_error("Archive does not contain a valid model config"); return false; } @@ -593,7 +607,7 @@ void close_archive_writer(mz_zip_archive *arch) { } } - close_archive_reader(&archive); + close_zip_reader(&archive); for (const IdToModelObjectMap::value_type& object : m_objects) { @@ -613,6 +627,11 @@ void close_archive_writer(mz_zip_archive *arch) { if (obj_layer_heights_profile != m_layer_heights_profiles.end()) model_object->layer_height_profile = obj_layer_heights_profile->second; + // m_layer_config_ranges are indexed by a 1 based model object index. + IdToLayerConfigRangesMap::iterator obj_layer_config_ranges = m_layer_config_ranges.find(object.second + 1); + if (obj_layer_config_ranges != m_layer_config_ranges.end()) + model_object->layer_config_ranges = obj_layer_config_ranges->second; + // m_sla_support_points are indexed by a 1 based model object index. IdToSlaSupportPointsMap::iterator obj_sla_support_points = m_sla_support_points.find(object.second + 1); if (obj_sla_support_points != m_sla_support_points.end() && !obj_sla_support_points->second.empty()) { @@ -696,7 +715,7 @@ void close_archive_writer(mz_zip_archive *arch) { if (!XML_ParseBuffer(m_xml_parser, (int)stat.m_uncomp_size, 1)) { char error_buf[1024]; - ::sprintf(error_buf, "Error (%s) while parsing xml file at line %d", XML_ErrorString(XML_GetErrorCode(m_xml_parser)), XML_GetCurrentLineNumber(m_xml_parser)); + ::sprintf(error_buf, "Error (%s) while parsing xml file at line %d", XML_ErrorString(XML_GetErrorCode(m_xml_parser)), (int)XML_GetCurrentLineNumber(m_xml_parser)); add_error(error_buf); return false; } @@ -790,6 +809,66 @@ void close_archive_writer(mz_zip_archive *arch) { } } + void _3MF_Importer::_extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) + { + if (stat.m_uncomp_size > 0) + { + std::string buffer((size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) { + add_error("Error while reading layer config ranges data to buffer"); + return; + } + + std::istringstream iss(buffer); // wrap returned xml to istringstream + pt::ptree objects_tree; + pt::read_xml(iss, objects_tree); + + for (const auto& object : objects_tree.get_child("objects")) + { + pt::ptree object_tree = object.second; + int obj_idx = object_tree.get(".id", -1); + if (obj_idx <= 0) { + add_error("Found invalid object id"); + continue; + } + + IdToLayerConfigRangesMap::iterator object_item = m_layer_config_ranges.find(obj_idx); + if (object_item != m_layer_config_ranges.end()) { + add_error("Found duplicated layer config range"); + continue; + } + + t_layer_config_ranges config_ranges; + + for (const auto& range : object_tree) + { + if (range.first != "range") + continue; + pt::ptree range_tree = range.second; + double min_z = range_tree.get(".min_z"); + double max_z = range_tree.get(".max_z"); + + // get Z range information + DynamicPrintConfig& config = config_ranges[{ min_z, max_z }]; + + for (const auto& option : range_tree) + { + if (option.first != "option") + continue; + std::string opt_key = option.second.get(".opt_key"); + std::string value = option.second.data(); + + config.set_deserialize(opt_key, value); + } + } + + if (!config_ranges.empty()) + m_layer_config_ranges.insert(IdToLayerConfigRangesMap::value_type(obj_idx, config_ranges)); + } + } + } + void _3MF_Importer::_extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) { if (stat.m_uncomp_size > 0) @@ -916,7 +995,7 @@ void close_archive_writer(mz_zip_archive *arch) { if (!XML_ParseBuffer(m_xml_parser, (int)stat.m_uncomp_size, 1)) { char error_buf[1024]; - ::sprintf(error_buf, "Error (%s) while parsing xml file at line %d", XML_ErrorString(XML_GetErrorCode(m_xml_parser)), XML_GetCurrentLineNumber(m_xml_parser)); + ::sprintf(error_buf, "Error (%s) while parsing xml file at line %d", XML_ErrorString(XML_GetErrorCode(m_xml_parser)), (int)XML_GetCurrentLineNumber(m_xml_parser)); add_error(error_buf); return false; } @@ -1307,8 +1386,9 @@ void close_archive_writer(mz_zip_archive *arch) { int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR); Transform3d transform = get_transform_from_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR)); + int printable = get_attribute_value_bool(attributes, num_attributes, PRINTABLE_ATTR); - return _create_object_instance(object_id, transform, 1); + return _create_object_instance(object_id, transform, printable, 1); } bool _3MF_Importer::_handle_end_item() @@ -1331,12 +1411,20 @@ void close_archive_writer(mz_zip_archive *arch) { bool _3MF_Importer::_handle_end_metadata() { if (m_curr_metadata_name == SLIC3RPE_3MF_VERSION) + { m_version = (unsigned int)atoi(m_curr_characters.c_str()); + if (m_check_version && (m_version > VERSION_3MF)) + { + std::string msg = "The selected 3mf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatibile."; + throw std::runtime_error(msg.c_str()); + } + } + return true; } - bool _3MF_Importer::_create_object_instance(int object_id, const Transform3d& transform, unsigned int recur_counter) + bool _3MF_Importer::_create_object_instance(int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter) { static const unsigned int MAX_RECURSIONS = 10; @@ -1372,6 +1460,7 @@ void close_archive_writer(mz_zip_archive *arch) { add_error("Unable to add object instance"); return false; } + instance->printable = printable; m_instances.emplace_back(instance, transform); } @@ -1381,7 +1470,7 @@ void close_archive_writer(mz_zip_archive *arch) { // recursively process nested components for (const Component& component : it->second) { - if (!_create_object_instance(component.object_id, transform * component.transform, recur_counter + 1)) + if (!_create_object_instance(component.object_id, transform * component.transform, printable, recur_counter + 1)) return false; } } @@ -1473,7 +1562,7 @@ void close_archive_writer(mz_zip_archive *arch) { object->second.metadata.emplace_back(key, value); else if (type == VOLUME_TYPE) { - if (m_curr_config.volume_id < object->second.volumes.size()) + if (size_t(m_curr_config.volume_id) < object->second.volumes.size()) object->second.volumes[m_curr_config.volume_id].metadata.emplace_back(key, value); } else @@ -1510,10 +1599,10 @@ void close_archive_writer(mz_zip_archive *arch) { } // splits volume out of imported geometry - unsigned int triangles_count = volume_data.last_triangle_id - volume_data.first_triangle_id + 1; - ModelVolume* volume = object.add_volume(TriangleMesh()); - stl_file& stl = volume->mesh.stl; - stl.stats.type = inmemory; + TriangleMesh triangle_mesh; + stl_file &stl = triangle_mesh.stl; + unsigned int triangles_count = volume_data.last_triangle_id - volume_data.first_triangle_id + 1; + stl.stats.type = inmemory; stl.stats.number_of_facets = (uint32_t)triangles_count; stl.stats.original_num_facets = (int)stl.stats.number_of_facets; stl_allocate(&stl); @@ -1530,9 +1619,11 @@ void close_archive_writer(mz_zip_archive *arch) { } } - stl_get_size(&stl); - volume->mesh.repair(); - volume->center_geometry(); + stl_get_size(&stl); + triangle_mesh.repair(); + + ModelVolume* volume = object.add_volume(std::move(triangle_mesh)); + volume->center_geometry_after_creation(); volume->calculate_convex_hull(); // apply volume's name and config data @@ -1593,10 +1684,12 @@ void close_archive_writer(mz_zip_archive *arch) { { unsigned int id; Transform3d transform; + bool printable; - BuildItem(unsigned int id, const Transform3d& transform) + BuildItem(unsigned int id, const Transform3d& transform, const bool printable) : id(id) , transform(transform) + , printable(printable) { } }; @@ -1643,6 +1736,7 @@ void close_archive_writer(mz_zip_archive *arch) { bool _add_mesh_to_object_stream(std::stringstream& stream, ModelObject& object, VolumeToOffsetsMap& volumes_offsets); bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items); bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model); + bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config); bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data); @@ -1659,10 +1753,7 @@ void close_archive_writer(mz_zip_archive *arch) { mz_zip_archive archive; mz_zip_zero_struct(&archive); - FILE *f = boost::nowide::fopen(filename.c_str(), "wb"); - auto res = mz_bool(f != nullptr && mz_zip_writer_init_cfile(&archive, f, 0)); - if (res == 0) - { + if (!open_zip_writer(&archive, filename)) { add_error("Unable to open the file"); return false; } @@ -1671,7 +1762,7 @@ void close_archive_writer(mz_zip_archive *arch) { // The content of this file is the same for each PrusaSlicer 3mf. if (!_add_content_types_file_to_archive(archive)) { - close_archive_writer(&archive); + close_zip_writer(&archive); boost::filesystem::remove(filename); return false; } @@ -1681,7 +1772,7 @@ void close_archive_writer(mz_zip_archive *arch) { // The relationshis file contains a reference to the geometry file "3D/3dmodel.model", the name was chosen to be compatible with CURA. if (!_add_relationships_file_to_archive(archive)) { - close_archive_writer(&archive); + close_zip_writer(&archive); boost::filesystem::remove(filename); return false; } @@ -1691,7 +1782,7 @@ void close_archive_writer(mz_zip_archive *arch) { IdToObjectDataMap objects_data; if (!_add_model_file_to_archive(archive, model, objects_data)) { - close_archive_writer(&archive); + close_zip_writer(&archive); boost::filesystem::remove(filename); return false; } @@ -1701,7 +1792,17 @@ void close_archive_writer(mz_zip_archive *arch) { // The index differes from the index of an object ID of an object instance of a 3MF file! if (!_add_layer_height_profile_file_to_archive(archive, model)) { - close_archive_writer(&archive); + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + + // Adds layer config ranges file ("Metadata/Slic3r_PE_layer_config_ranges.txt"). + // All layer height profiles of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model. + // The index differes from the index of an object ID of an object instance of a 3MF file! + if (!_add_layer_config_ranges_file_to_archive(archive, model)) + { + close_zip_writer(&archive); boost::filesystem::remove(filename); return false; } @@ -1711,7 +1812,7 @@ void close_archive_writer(mz_zip_archive *arch) { // The index differes from the index of an object ID of an object instance of a 3MF file! if (!_add_sla_support_points_file_to_archive(archive, model)) { - close_archive_writer(&archive); + close_zip_writer(&archive); boost::filesystem::remove(filename); return false; } @@ -1722,7 +1823,7 @@ void close_archive_writer(mz_zip_archive *arch) { { if (!_add_print_config_file_to_archive(archive, *config)) { - close_archive_writer(&archive); + close_zip_writer(&archive); boost::filesystem::remove(filename); return false; } @@ -1734,20 +1835,20 @@ void close_archive_writer(mz_zip_archive *arch) { // is stored here as well. if (!_add_model_config_file_to_archive(archive, model, objects_data)) { - close_archive_writer(&archive); + close_zip_writer(&archive); boost::filesystem::remove(filename); return false; } if (!mz_zip_writer_finalize_archive(&archive)) { - close_archive_writer(&archive); + close_zip_writer(&archive); boost::filesystem::remove(filename); add_error("Unable to finalize the archive"); return false; } - close_archive_writer(&archive); + close_zip_writer(&archive); return true; } @@ -1881,7 +1982,7 @@ void close_archive_writer(mz_zip_archive *arch) { Transform3d t = instance->get_matrix(); // instance_id is just a 1 indexed index in build_items. assert(instance_id == build_items.size() + 1); - build_items.emplace_back(instance_id, t); + build_items.emplace_back(instance_id, t, instance->printable); stream << " \n"; @@ -1903,29 +2004,28 @@ void close_archive_writer(mz_zip_archive *arch) { if (volume == nullptr) continue; + if (!volume->mesh().repaired) + throw std::runtime_error("store_3mf() requires repair()"); + if (!volume->mesh().has_shared_vertices()) + throw std::runtime_error("store_3mf() requires shared vertices"); + volumes_offsets.insert(VolumeToOffsetsMap::value_type(volume, Offsets(vertices_count))).first; - if (!volume->mesh.repaired) - volume->mesh.repair(); - - stl_file& stl = volume->mesh.stl; - if (stl.v_shared == nullptr) - stl_generate_shared_vertices(&stl); - - if (stl.stats.shared_vertices == 0) + const indexed_triangle_set &its = volume->mesh().its; + if (its.vertices.empty()) { add_error("Found invalid mesh"); return false; } - vertices_count += stl.stats.shared_vertices; + vertices_count += (int)its.vertices.size(); const Transform3d& matrix = volume->get_matrix(); - for (int i = 0; i < stl.stats.shared_vertices; ++i) + for (size_t i = 0; i < its.vertices.size(); ++i) { stream << " <" << VERTEX_TAG << " "; - Vec3f v = (matrix * stl.v_shared[i].cast()).cast(); + Vec3f v = (matrix * its.vertices[i].cast()).cast(); stream << "x=\"" << v(0) << "\" "; stream << "y=\"" << v(1) << "\" "; stream << "z=\"" << v(2) << "\" />\n"; @@ -1944,19 +2044,19 @@ void close_archive_writer(mz_zip_archive *arch) { VolumeToOffsetsMap::iterator volume_it = volumes_offsets.find(volume); assert(volume_it != volumes_offsets.end()); - stl_file& stl = volume->mesh.stl; + const indexed_triangle_set &its = volume->mesh().its; // updates triangle offsets volume_it->second.first_triangle_id = triangles_count; - triangles_count += stl.stats.number_of_facets; + triangles_count += (int)its.indices.size(); volume_it->second.last_triangle_id = triangles_count - 1; - for (uint32_t i = 0; i < stl.stats.number_of_facets; ++i) + for (size_t i = 0; i < its.indices.size(); ++ i) { stream << " <" << TRIANGLE_TAG << " "; for (int j = 0; j < 3; ++j) { - stream << "v" << j + 1 << "=\"" << stl.v_indices[i].vertex[j] + volume_it->second.first_vertex_id << "\" "; + stream << "v" << j + 1 << "=\"" << its.indices[i][j] + volume_it->second.first_vertex_id << "\" "; } stream << "/>\n"; } @@ -1990,7 +2090,7 @@ void close_archive_writer(mz_zip_archive *arch) { stream << " "; } } - stream << "\" />\n"; + stream << "\" printable =\"" << item.printable << "\" />\n"; } stream << " \n"; @@ -2036,6 +2136,70 @@ void close_archive_writer(mz_zip_archive *arch) { return true; } + bool _3MF_Exporter::_add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model) + { + std::string out = ""; + pt::ptree tree; + + unsigned int object_cnt = 0; + for (const ModelObject* object : model.objects) + { + object_cnt++; + const t_layer_config_ranges& ranges = object->layer_config_ranges; + if (!ranges.empty()) + { + pt::ptree& obj_tree = tree.add("objects.object",""); + + obj_tree.put(".id", object_cnt); + + // Store the layer config ranges. + for (const auto& range : ranges) + { + pt::ptree& range_tree = obj_tree.add("range", ""); + + // store minX and maxZ + range_tree.put(".min_z", range.first.first); + range_tree.put(".max_z", range.first.second); + + // store range configuration + const DynamicPrintConfig& config = range.second; + for (const std::string& opt_key : config.keys()) + { + pt::ptree& opt_tree = range_tree.add("option", config.opt_serialize(opt_key)); + opt_tree.put(".opt_key", opt_key); + } + } + } + } + + if (!tree.empty()) + { + std::ostringstream oss; + boost::property_tree::write_xml(oss, tree); + out = oss.str(); + + // Post processing("beautification") of the output string for a better preview + boost::replace_all(out, ">\n \n \n ", ">\n "); + boost::replace_all(out, ">

", ">\n
"); + // OR just + boost::replace_all(out, "><", ">\n<"); + } + + if (!out.empty()) + { + if (!mz_zip_writer_add_mem(&archive, LAYER_CONFIG_RANGES_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) + { + add_error("Unable to add layer heights profile file to archive"); + return false; + } + } + + return true; + } + bool _3MF_Exporter::_add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model) { std::string out = ""; @@ -2083,7 +2247,7 @@ void close_archive_writer(mz_zip_archive *arch) { for (const std::string &key : config.keys()) if (key != "compatible_printers") - out += "; " + key + " = " + config.serialize(key) + "\n"; + out += "; " + key + " = " + config.opt_serialize(key) + "\n"; if (!out.empty()) { @@ -2117,7 +2281,7 @@ void close_archive_writer(mz_zip_archive *arch) { // stores object's config data for (const std::string& key : obj->config.keys()) { - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << OBJECT_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << obj->config.serialize(key) << "\"/>\n"; + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << OBJECT_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << obj->config.opt_serialize(key) << "\"/>\n"; } for (const ModelVolume* volume : obj_metadata.second.object->volumes) @@ -2147,7 +2311,7 @@ void close_archive_writer(mz_zip_archive *arch) { // stores volume's config data for (const std::string& key : volume->config.keys()) { - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << volume->config.serialize(key) << "\"/>\n"; + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << volume->config.opt_serialize(key) << "\"/>\n"; } stream << " \n"; @@ -2172,13 +2336,13 @@ void close_archive_writer(mz_zip_archive *arch) { return true; } - bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model) + bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version) { if ((path == nullptr) || (config == nullptr) || (model == nullptr)) return false; _3MF_Importer importer; - bool res = importer.load_model_from_file(path, *model, *config); + bool res = importer.load_model_from_file(path, *model, *config, check_version); importer.log_errors(); return res; } diff --git a/src/libslic3r/Format/3mf.hpp b/src/libslic3r/Format/3mf.hpp index b5927651ec..f387192ab5 100644 --- a/src/libslic3r/Format/3mf.hpp +++ b/src/libslic3r/Format/3mf.hpp @@ -24,7 +24,7 @@ namespace Slic3r { class DynamicPrintConfig; // Load the content of a 3mf file into the given model and preset bundle. - extern bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model); + extern bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version); // Save the given model and the config data contained in the given Print into a 3mf file. // The model could be modified during the export process if meshes are not repaired or have no shared vertices diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index c4b6901a80..be15f833b5 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include "miniz_extension.hpp" #if 0 // Enable debugging and assert in this file. @@ -44,7 +44,7 @@ namespace Slic3r struct AMFParserContext { - AMFParserContext(XML_Parser parser, DynamicPrintConfig *config, Model *model) : + AMFParserContext(XML_Parser parser, DynamicPrintConfig* config, Model* model) : m_version(0), m_parser(parser), m_model(*model), @@ -106,6 +106,9 @@ struct AMFParserContext // amf/material/metadata NODE_TYPE_OBJECT, // amf/object // amf/object/metadata + NODE_TYPE_LAYER_CONFIG, // amf/object/layer_config_ranges + NODE_TYPE_RANGE, // amf/object/layer_config_ranges/range + // amf/object/layer_config_ranges/range/metadata NODE_TYPE_MESH, // amf/object/mesh NODE_TYPE_VERTICES, // amf/object/mesh/vertices NODE_TYPE_VERTEX, // amf/object/mesh/vertices/vertex @@ -134,6 +137,7 @@ struct AMFParserContext NODE_TYPE_MIRRORX, // amf/constellation/instance/mirrorx NODE_TYPE_MIRRORY, // amf/constellation/instance/mirrory NODE_TYPE_MIRRORZ, // amf/constellation/instance/mirrorz + NODE_TYPE_PRINTABLE, // amf/constellation/instance/mirrorz NODE_TYPE_METADATA, // anywhere under amf/*/metadata }; @@ -142,7 +146,8 @@ struct AMFParserContext : deltax_set(false), deltay_set(false), deltaz_set(false) , rx_set(false), ry_set(false), rz_set(false) , scalex_set(false), scaley_set(false), scalez_set(false) - , mirrorx_set(false), mirrory_set(false), mirrorz_set(false) {} + , mirrorx_set(false), mirrory_set(false), mirrorz_set(false) + , printable(true) {} // Shift in the X axis. float deltax; bool deltax_set; @@ -175,6 +180,8 @@ struct AMFParserContext bool mirrory_set; float mirrorz; bool mirrorz_set; + // printable property + bool printable; bool anything_set() const { return deltax_set || deltay_set || deltaz_set || rx_set || ry_set || rz_set || @@ -189,7 +196,7 @@ struct AMFParserContext }; // Version of the amf file - unsigned int m_version; + unsigned int m_version; // Current Expat XML parser instance. XML_Parser m_parser; // Model to receive objects extracted from an AMF file. @@ -260,7 +267,9 @@ void AMFParserContext::startElement(const char *name, const char **atts) m_value[0] = get_attribute(atts, "type"); node_type_new = NODE_TYPE_METADATA; } - } else if (strcmp(name, "mesh") == 0) { + } else if (strcmp(name, "layer_config_ranges") == 0 && m_path[1] == NODE_TYPE_OBJECT) + node_type_new = NODE_TYPE_LAYER_CONFIG; + else if (strcmp(name, "mesh") == 0) { if (m_path[1] == NODE_TYPE_OBJECT) node_type_new = NODE_TYPE_MESH; } else if (strcmp(name, "instance") == 0) { @@ -316,6 +325,12 @@ void AMFParserContext::startElement(const char *name, const char **atts) node_type_new = NODE_TYPE_MIRRORY; else if (strcmp(name, "mirrorz") == 0) node_type_new = NODE_TYPE_MIRRORZ; + else if (strcmp(name, "printable") == 0) + node_type_new = NODE_TYPE_PRINTABLE; + } + else if (m_path[2] == NODE_TYPE_LAYER_CONFIG && strcmp(name, "range") == 0) { + assert(m_object); + node_type_new = NODE_TYPE_RANGE; } break; case 4: @@ -334,6 +349,10 @@ void AMFParserContext::startElement(const char *name, const char **atts) } else if (strcmp(name, "triangle") == 0) node_type_new = NODE_TYPE_TRIANGLE; } + else if (m_path[3] == NODE_TYPE_RANGE && strcmp(name, "metadata") == 0) { + m_value[0] = get_attribute(atts, "type"); + node_type_new = NODE_TYPE_METADATA; + } break; case 5: if (strcmp(name, "coordinates") == 0) { @@ -384,7 +403,8 @@ void AMFParserContext::characters(const XML_Char *s, int len) m_path.back() == NODE_TYPE_SCALE || m_path.back() == NODE_TYPE_MIRRORX || m_path.back() == NODE_TYPE_MIRRORY || - m_path.back() == NODE_TYPE_MIRRORZ) + m_path.back() == NODE_TYPE_MIRRORZ || + m_path.back() == NODE_TYPE_PRINTABLE) m_value[0].append(s, len); break; case 6: @@ -494,6 +514,11 @@ void AMFParserContext::endElement(const char * /* name */) m_instance->mirrorz_set = true; m_value[0].clear(); break; + case NODE_TYPE_PRINTABLE: + assert(m_instance); + m_instance->printable = bool(atoi(m_value[0].c_str())); + m_value[0].clear(); + break; // Object vertices: case NODE_TYPE_VERTEX: @@ -522,7 +547,8 @@ void AMFParserContext::endElement(const char * /* name */) case NODE_TYPE_VOLUME: { assert(m_object && m_volume); - stl_file &stl = m_volume->mesh.stl; + TriangleMesh mesh; + stl_file &stl = mesh.stl; stl.stats.type = inmemory; stl.stats.number_of_facets = int(m_volume_facets.size() / 3); stl.stats.original_num_facets = stl.stats.number_of_facets; @@ -533,8 +559,9 @@ void AMFParserContext::endElement(const char * /* name */) memcpy(facet.vertex[v].data(), &m_object_vertices[m_volume_facets[i ++] * 3], 3 * sizeof(float)); } stl_get_size(&stl); - m_volume->mesh.repair(); - m_volume->center_geometry(); + mesh.repair(); + m_volume->set_mesh(std::move(mesh)); + m_volume->center_geometry_after_creation(); m_volume->calculate_convex_hull(); m_volume_facets.clear(); m_volume = nullptr; @@ -569,8 +596,13 @@ void AMFParserContext::endElement(const char * /* name */) config = &m_material->config; else if (m_path[1] == NODE_TYPE_OBJECT && m_object) config = &m_object->config; - } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) + } + else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) config = &m_volume->config; + else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_RANGE && m_object && !m_object->layer_config_ranges.empty()) { + auto it = --m_object->layer_config_ranges.end(); + config = &it->second; + } if (config) config->set_deserialize(opt_key, m_value[1]); } else if (m_path.size() == 3 && m_path[1] == NODE_TYPE_OBJECT && m_object && strcmp(opt_key, "layer_height_profile") == 0) { @@ -596,7 +628,7 @@ void AMFParserContext::endElement(const char * /* name */) if (end != nullptr) *end = 0; - point(coord_idx) = atof(p); + point(coord_idx) = float(atof(p)); if (++coord_idx == 5) { m_object->sla_support_points.push_back(sla::SupportPoint(point)); coord_idx = 0; @@ -607,6 +639,16 @@ void AMFParserContext::endElement(const char * /* name */) } m_object->sla_points_status = sla::PointsStatus::UserModified; } + else if (m_path.size() == 5 && m_path[1] == NODE_TYPE_OBJECT && m_path[3] == NODE_TYPE_RANGE && + m_object && strcmp(opt_key, "layer_height_range") == 0) { + // Parse object's layer_height_range, a semicolon separated doubles. + char* p = const_cast(m_value[1].c_str()); + char* end = strchr(p, ';'); + *end = 0; + + const t_layer_height_range range = {double(atof(p)), double(atof(end + 1))}; + m_object->layer_config_ranges[range]; + } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) { if (strcmp(opt_key, "modifier") == 0) { // Is this volume a modifier volume? @@ -655,6 +697,7 @@ void AMFParserContext::endDocument() mi->set_rotation(Vec3d(instance.rx_set ? (double)instance.rx : 0.0, instance.ry_set ? (double)instance.ry : 0.0, instance.rz_set ? (double)instance.rz : 0.0)); mi->set_scaling_factor(Vec3d(instance.scalex_set ? (double)instance.scalex : 1.0, instance.scaley_set ? (double)instance.scaley : 1.0, instance.scalez_set ? (double)instance.scalez : 1.0)); mi->set_mirror(Vec3d(instance.mirrorx_set ? (double)instance.mirrorx : 1.0, instance.mirrory_set ? (double)instance.mirrory : 1.0, instance.mirrorz_set ? (double)instance.mirrorz : 1.0)); + mi->printable = instance.printable; } } } @@ -692,8 +735,8 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model) } int done = feof(pFile); if (XML_Parse(parser, buff, len, done) == XML_STATUS_ERROR) { - printf("AMF parser: Parse error at line %ul:\n%s\n", - XML_GetCurrentLineNumber(parser), + printf("AMF parser: Parse error at line %d:\n%s\n", + (int)XML_GetCurrentLineNumber(parser), XML_ErrorString(XML_GetErrorCode(parser))); break; } @@ -712,37 +755,19 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model) return result; } -namespace { -void close_archive_reader(mz_zip_archive *arch) { - if (arch) { - FILE *f = mz_zip_get_cfile(arch); - mz_zip_reader_end(arch); - if (f) fclose(f); - } -} - -void close_archive_writer(mz_zip_archive *arch) { - if (arch) { - FILE *f = mz_zip_get_cfile(arch); - mz_zip_writer_end(arch); - if (f) fclose(f); - } -} -} - -bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig* config, Model* model, unsigned int& version) +bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig* config, Model* model, bool check_version) { if (stat.m_uncomp_size == 0) { printf("Found invalid size\n"); - close_archive_reader(&archive); + close_zip_reader(&archive); return false; } XML_Parser parser = XML_ParserCreate(nullptr); // encoding if (!parser) { printf("Couldn't allocate memory for parser\n"); - close_archive_reader(&archive); + close_zip_reader(&archive); return false; } @@ -755,7 +780,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi if (parser_buffer == nullptr) { printf("Unable to create buffer\n"); - close_archive_reader(&archive); + close_zip_reader(&archive); return false; } @@ -763,39 +788,38 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi if (res == 0) { printf("Error while reading model data to buffer\n"); - close_archive_reader(&archive); + close_zip_reader(&archive); return false; } if (!XML_ParseBuffer(parser, (int)stat.m_uncomp_size, 1)) { - printf("Error (%s) while parsing xml file at line %d\n", XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser)); - close_archive_reader(&archive); + printf("Error (%s) while parsing xml file at line %d\n", XML_ErrorString(XML_GetErrorCode(parser)), (int)XML_GetCurrentLineNumber(parser)); + close_zip_reader(&archive); return false; } ctx.endDocument(); - version = ctx.m_version; + if (check_version && (ctx.m_version > VERSION_AMF)) + { + std::string msg = "The selected amf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatibile."; + throw std::runtime_error(msg.c_str()); + } return true; } // Load an AMF archive into a provided model. -bool load_amf_archive(const char *path, DynamicPrintConfig *config, Model *model) +bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model, bool check_version) { if ((path == nullptr) || (model == nullptr)) return false; - unsigned int version = 0; - mz_zip_archive archive; mz_zip_zero_struct(&archive); - FILE * f = boost::nowide::fopen(path, "rb"); - auto res = mz_bool(f == nullptr ? MZ_FALSE : MZ_TRUE); - res = res && mz_zip_reader_init_cfile(&archive, f, -1, 0); - if (res == MZ_FALSE) + if (!open_zip_reader(&archive, path)) { printf("Unable to init zip reader\n"); return false; @@ -811,11 +835,20 @@ bool load_amf_archive(const char *path, DynamicPrintConfig *config, Model *model { if (boost::iends_with(stat.m_filename, ".amf")) { - if (!extract_model_from_archive(archive, stat, config, model, version)) + try { - close_archive_reader(&archive); - printf("Archive does not contain a valid model"); - return false; + if (!extract_model_from_archive(archive, stat, config, model, check_version)) + { + close_zip_reader(&archive); + printf("Archive does not contain a valid model"); + return false; + } + } + catch (const std::exception& e) + { + // ensure the zip archive is closed and rethrow the exception + close_zip_reader(&archive); + throw e; } break; @@ -834,13 +867,13 @@ bool load_amf_archive(const char *path, DynamicPrintConfig *config, Model *model } #endif // forward compatibility - close_archive_reader(&archive); + close_zip_reader(&archive); return true; } // Load an AMF file into a provided model. // If config is not a null pointer, updates it if the amf file/archive contains config data -bool load_amf(const char *path, DynamicPrintConfig *config, Model *model) +bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version) { if (boost::iends_with(path, ".amf.xml")) // backward compatibility with older slic3r output @@ -855,7 +888,7 @@ bool load_amf(const char *path, DynamicPrintConfig *config, Model *model) file.read(const_cast(zip_mask.data()), 2); file.close(); - return (zip_mask == "PK") ? load_amf_archive(path, config, model) : load_amf_file(path, config, model); + return (zip_mask == "PK") ? load_amf_archive(path, config, model, check_version) : load_amf_file(path, config, model); } else return false; @@ -874,10 +907,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) mz_zip_archive archive; mz_zip_zero_struct(&archive); - FILE *f = boost::nowide::fopen(export_path.c_str(), "wb"); - auto res = mz_bool(f != nullptr && mz_zip_writer_init_cfile(&archive, f, 0)); - if (res == 0) - return false; + if (!open_zip_writer(&archive, export_path)) return false; std::stringstream stream; // https://en.cppreference.com/w/cpp/types/numeric_limits/max_digits10 @@ -895,7 +925,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) std::string str_config = "\n"; for (const std::string &key : config->keys()) if (key != "compatible_printers") - str_config += "; " + key + " = " + config->serialize(key) + "\n"; + str_config += "; " + key + " = " + config->opt_serialize(key) + "\n"; stream << "" << xml_escape(str_config) << "\n"; } @@ -907,7 +937,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) for (const auto &attr : material.second->attributes) stream << " " << attr.second << "\n"; for (const std::string &key : material.second->config.keys()) - stream << " " << material.second->config.serialize(key) << "\n"; + stream << " " << material.second->config.opt_serialize(key) << "\n"; stream << " \n"; } std::string instances; @@ -915,7 +945,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) ModelObject *object = model->objects[object_id]; stream << " \n"; for (const std::string &key : object->config.keys()) - stream << " " << object->config.serialize(key) << "\n"; + stream << " " << object->config.opt_serialize(key) << "\n"; if (!object->name.empty()) stream << " " << xml_escape(object->name) << "\n"; const std::vector &layer_height_profile = object->layer_height_profile; @@ -927,7 +957,30 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) stream << ";" << layer_height_profile[i]; stream << "\n \n"; } - //FIXME Store the layer height ranges (ModelObject::layer_height_ranges) + + // Export layer height ranges including the layer range specific config overrides. + const t_layer_config_ranges& config_ranges = object->layer_config_ranges; + if (!config_ranges.empty()) + { + // Store the layer config range as a single semicolon separated list. + stream << " \n"; + size_t layer_counter = 0; + for (auto range : config_ranges) { + stream << " \n"; + + stream << " "; + stream << range.first.first << ";" << range.first.second << "\n"; + + for (const std::string& key : range.second.keys()) + stream << " " << range.second.opt_serialize(key) << "\n"; + + stream << " \n"; + layer_counter++; + } + + stream << " \n"; + } + const std::vector& sla_support_points = object->sla_support_points; if (!sla_support_points.empty()) { @@ -947,23 +1000,23 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) int num_vertices = 0; for (ModelVolume *volume : object->volumes) { vertices_offsets.push_back(num_vertices); - if (! volume->mesh.repaired) + if (! volume->mesh().repaired) throw std::runtime_error("store_amf() requires repair()"); - auto &stl = volume->mesh.stl; - if (stl.v_shared == nullptr) - stl_generate_shared_vertices(&stl); + if (! volume->mesh().has_shared_vertices()) + throw std::runtime_error("store_amf() requires shared vertices"); + const indexed_triangle_set &its = volume->mesh().its; const Transform3d& matrix = volume->get_matrix(); - for (size_t i = 0; i < stl.stats.shared_vertices; ++i) { + for (size_t i = 0; i < its.vertices.size(); ++i) { stream << " \n"; stream << " \n"; - Vec3f v = (matrix * stl.v_shared[i].cast()).cast(); + Vec3f v = (matrix * its.vertices[i].cast()).cast(); stream << " " << v(0) << "\n"; stream << " " << v(1) << "\n"; stream << " " << v(2) << "\n"; stream << " \n"; stream << " \n"; } - num_vertices += stl.stats.shared_vertices; + num_vertices += (int)its.vertices.size(); } stream << " \n"; for (size_t i_volume = 0; i_volume < object->volumes.size(); ++i_volume) { @@ -974,16 +1027,17 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) else stream << " material_id() << "\">\n"; for (const std::string &key : volume->config.keys()) - stream << " " << volume->config.serialize(key) << "\n"; + stream << " " << volume->config.opt_serialize(key) << "\n"; if (!volume->name.empty()) stream << " " << xml_escape(volume->name) << "\n"; if (volume->is_modifier()) stream << " 1\n"; stream << " " << ModelVolume::type_to_string(volume->type()) << "\n"; - for (int i = 0; i < (int)volume->mesh.stl.stats.number_of_facets; ++i) { + const indexed_triangle_set &its = volume->mesh().its; + for (size_t i = 0; i < its.indices.size(); ++i) { stream << " \n"; for (int j = 0; j < 3; ++j) - stream << " " << volume->mesh.stl.v_indices[i].vertex[j] + vertices_offset << "\n"; + stream << " " << its.indices[i][j] + vertices_offset << "\n"; stream << " \n"; } stream << " \n"; @@ -1007,6 +1061,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) " %lf\n" " %lf\n" " %lf\n" + " %d\n" " \n", object_id, instance->get_offset(X), @@ -1020,7 +1075,8 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) instance->get_scaling_factor(Z), instance->get_mirror(X), instance->get_mirror(Y), - instance->get_mirror(Z)); + instance->get_mirror(Z), + instance->printable); //FIXME missing instance->scaling_factor instances.append(buf); @@ -1039,20 +1095,19 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) if (!mz_zip_writer_add_mem(&archive, internal_amf_filename.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { - close_archive_writer(&archive); - if (f) fclose(f); + close_zip_writer(&archive); boost::filesystem::remove(export_path); return false; } if (!mz_zip_writer_finalize_archive(&archive)) { - close_archive_writer(&archive); + close_zip_writer(&archive); boost::filesystem::remove(export_path); return false; } - close_archive_writer(&archive); + close_zip_writer(&archive); return true; } diff --git a/src/libslic3r/Format/AMF.hpp b/src/libslic3r/Format/AMF.hpp index e085ad22ea..1206d60d07 100644 --- a/src/libslic3r/Format/AMF.hpp +++ b/src/libslic3r/Format/AMF.hpp @@ -7,7 +7,7 @@ class Model; class DynamicPrintConfig; // Load the content of an amf file into the given model and configuration. -extern bool load_amf(const char *path, DynamicPrintConfig *config, Model *model); +extern bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version); // Save the given model and the config data into an amf file. // The model could be modified during the export process if meshes are not repaired or have no shared vertices diff --git a/src/libslic3r/Format/PRUS.cpp b/src/libslic3r/Format/PRUS.cpp index 802a8304c9..03ea71a83e 100644 --- a/src/libslic3r/Format/PRUS.cpp +++ b/src/libslic3r/Format/PRUS.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include "miniz_extension.hpp" #include @@ -161,16 +161,15 @@ static void extract_model_from_archive( else { // Header has been extracted. Now read the faces. stl_file &stl = mesh.stl; - stl.error = 0; stl.stats.type = inmemory; stl.stats.number_of_facets = header.nTriangles; stl.stats.original_num_facets = header.nTriangles; stl_allocate(&stl); if (header.nTriangles > 0 && data.size() == 50 * header.nTriangles + sizeof(StlHeader)) { - memcpy((char*)stl.facet_start, data.data() + sizeof(StlHeader), 50 * header.nTriangles); + memcpy((char*)stl.facet_start.data(), data.data() + sizeof(StlHeader), 50 * header.nTriangles); if (sizeof(stl_facet) > SIZEOF_STL_FACET) { // The stl.facet_start is not packed tightly. Unpack the array of stl_facets. - unsigned char *data = (unsigned char*)stl.facet_start; + unsigned char *data = (unsigned char*)stl.facet_start.data(); for (size_t i = header.nTriangles - 1; i > 0; -- i) memmove(data + i * sizeof(stl_facet), data + i * SIZEOF_STL_FACET, SIZEOF_STL_FACET); } @@ -257,7 +256,7 @@ static void extract_model_from_archive( stl.stats.number_of_facets = (uint32_t)facets.size(); stl.stats.original_num_facets = (int)facets.size(); stl_allocate(&stl); - memcpy((void*)stl.facet_start, facets.data(), facets.size() * 50); + memcpy((void*)stl.facet_start.data(), facets.data(), facets.size() * 50); stl_get_size(&stl); mesh.repair(); // Add a mesh to a model. @@ -300,11 +299,10 @@ bool load_prus(const char *path, Model *model) mz_zip_archive archive; mz_zip_zero_struct(&archive); - FILE *f = boost::nowide::fopen(path, "rb"); - auto res = mz_bool(f != nullptr && mz_zip_reader_init_cfile(&archive, f, -1, 0)); size_t n_models_initial = model->objects.size(); + mz_bool res = MZ_FALSE; try { - if (res == MZ_FALSE) + if (!open_zip_reader(&archive, path)) throw std::runtime_error(std::string("Unable to init zip reader for ") + path); std::vector scene_xml_data; // For grouping multiple STLs into a single ModelObject for multi-material prints. @@ -329,13 +327,11 @@ bool load_prus(const char *path, Model *model) } } } catch (std::exception &ex) { - mz_zip_reader_end(&archive); - if(f) fclose(f); + close_zip_reader(&archive); throw ex; } - mz_zip_reader_end(&archive); - if(f) fclose(f); + close_zip_reader(&archive); return model->objects.size() > n_models_initial; } diff --git a/src/libslic3r/Format/STL.cpp b/src/libslic3r/Format/STL.cpp index b00623d1d6..932906fe0e 100644 --- a/src/libslic3r/Format/STL.cpp +++ b/src/libslic3r/Format/STL.cpp @@ -17,8 +17,7 @@ namespace Slic3r { bool load_stl(const char *path, Model *model, const char *object_name_in) { TriangleMesh mesh; - mesh.ReadSTLFile(path); - if (mesh.stl.error) { + if (! mesh.ReadSTLFile(path)) { // die "Failed to open $file\n" if !-e $path; return false; } diff --git a/src/libslic3r/Format/objparser.cpp b/src/libslic3r/Format/objparser.cpp index 88dfae695a..62bcd306ee 100644 --- a/src/libslic3r/Format/objparser.cpp +++ b/src/libslic3r/Format/objparser.cpp @@ -176,8 +176,7 @@ static bool obj_parseline(const char *line, ObjData &data) EATWS(); if (*line == 0) return false; - // number of vertices of this face - int n = 0; + // current vertex to be parsed ObjVertex vertex; char *endptr = 0; @@ -266,7 +265,6 @@ static bool obj_parseline(const char *line, ObjData &data) { // o [object name] EATWS(); - const char *name = line; while (*line != ' ' && *line != '\t' && *line != 0) ++ line; // copy name to line. diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 9ef2b88963..008cad940b 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1,10 +1,11 @@ #include "libslic3r.h" +#include "I18N.hpp" #include "GCode.hpp" #include "ExtrusionEntity.hpp" #include "EdgeGrid.hpp" #include "Geometry.hpp" #include "GCode/PrintExtents.hpp" -#include "GCode/WipeTowerPrusaMM.hpp" +#include "GCode/WipeTower.hpp" #include "Utils.hpp" #include @@ -36,6 +37,11 @@ namespace Slic3r { +//! macro used to mark string used at localization, +//! return same string +#define L(s) (s) +#define _(s) Slic3r::I18N::translate(s) + // Only add a newline in case the current G-code does not end with a newline. static inline void check_add_eol(std::string &gcode) { @@ -162,59 +168,112 @@ std::string Wipe::wipe(GCode &gcodegen, bool toolchange) return gcode; } -static inline Point wipe_tower_point_to_object_point(GCode &gcodegen, const WipeTower::xy &wipe_tower_pt) +static inline Point wipe_tower_point_to_object_point(GCode &gcodegen, const Vec2f &wipe_tower_pt) { - return Point(scale_(wipe_tower_pt.x - gcodegen.origin()(0)), scale_(wipe_tower_pt.y - gcodegen.origin()(1))); + return Point(scale_(wipe_tower_pt.x() - gcodegen.origin()(0)), scale_(wipe_tower_pt.y() - gcodegen.origin()(1))); } std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const { + if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool) + throw std::invalid_argument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect."); + std::string gcode; // Toolchangeresult.gcode assumes the wipe tower corner is at the origin // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position float alpha = m_wipe_tower_rotation/180.f * float(M_PI); - WipeTower::xy start_pos = tcr.start_pos; - WipeTower::xy end_pos = tcr.end_pos; - start_pos.rotate(alpha); - start_pos.translate(m_wipe_tower_pos); - end_pos.rotate(alpha); - end_pos.translate(m_wipe_tower_pos); - std::string tcr_rotated_gcode = rotate_wipe_tower_moves(tcr.gcode, tcr.start_pos, m_wipe_tower_pos, alpha); - + Vec2f start_pos = tcr.start_pos; + Vec2f end_pos = tcr.end_pos; + if (!tcr.priming) { + start_pos = Eigen::Rotation2Df(alpha) * start_pos; + start_pos += m_wipe_tower_pos; + end_pos = Eigen::Rotation2Df(alpha) * end_pos; + end_pos += m_wipe_tower_pos; + } - // Disable linear advance for the wipe tower operations. - gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n")); - // Move over the wipe tower. - // Retract for a tool change, using the toolchange retract value and setting the priming extra length. - gcode += gcodegen.retract(true); - gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; - gcode += gcodegen.travel_to( - wipe_tower_point_to_object_point(gcodegen, start_pos), - erMixed, - "Travel to a Wipe Tower"); - gcode += gcodegen.unretract(); + Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos; + float wipe_tower_rotation = tcr.priming ? 0.f : alpha; + + std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation); + + if (!tcr.priming) { + // Move over the wipe tower. + // Retract for a tool change, using the toolchange retract value and setting the priming extra length. + gcode += gcodegen.retract(true); + gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; + gcode += gcodegen.travel_to( + wipe_tower_point_to_object_point(gcodegen, start_pos), + erMixed, + "Travel to a Wipe Tower"); + gcode += gcodegen.unretract(); + } + + + // Process the end filament gcode. + std::string end_filament_gcode_str; + if (gcodegen.writer().extruder() != nullptr) { + // Process the custom end_filament_gcode in case of single_extruder_multi_material. + unsigned int old_extruder_id = gcodegen.writer().extruder()->id(); + const std::string &end_filament_gcode = gcodegen.config().end_filament_gcode.get_at(old_extruder_id); + if (gcodegen.writer().extruder() != nullptr && ! end_filament_gcode.empty()) { + end_filament_gcode_str = gcodegen.placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id); + check_add_eol(end_filament_gcode_str); + } + } + + // Process the custom toolchange_gcode. If it is empty, provide a simple Tn command to change the filament. + // Otherwise, leave control to the user completely. + std::string toolchange_gcode_str; + if (true /*gcodegen.writer().extruder() != nullptr*/) { + const std::string& toolchange_gcode = gcodegen.config().toolchange_gcode.value; + if (!toolchange_gcode.empty()) { + DynamicConfig config; + int previous_extruder_id = gcodegen.writer().extruder() ? (int)gcodegen.writer().extruder()->id() : -1; + config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id)); + config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id)); + config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z)); + toolchange_gcode_str = gcodegen.placeholder_parser_process("toolchange_gcode", toolchange_gcode, new_extruder_id, &config); + check_add_eol(toolchange_gcode_str); + } + + std::string toolchange_command; + if (tcr.priming || (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id))) + toolchange_command = gcodegen.writer().toolchange(new_extruder_id); + if (toolchange_gcode.empty()) + toolchange_gcode_str = toolchange_command; + else { + // We have informed the m_writer about the current extruder_id, we can ignore the generated G-code. + } + } - // Let the tool change be executed by the wipe tower class. - // Inform the G-code writer about the changes done behind its back. - gcode += tcr_rotated_gcode; - // Let the m_writer know the current extruder_id, but ignore the generated G-code. - if (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)) - gcodegen.writer().toolchange(new_extruder_id); gcodegen.placeholder_parser().set("current_extruder", new_extruder_id); - // Always append the filament start G-code even if the extruder did not switch, - // because the wipe tower resets the linear advance and we want it to be re-enabled. + // Process the start filament gcode. + std::string start_filament_gcode_str; const std::string &start_filament_gcode = gcodegen.config().start_filament_gcode.get_at(new_extruder_id); if (! start_filament_gcode.empty()) { // Process the start_filament_gcode for the active filament only. DynamicConfig config; config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_extruder_id)); - gcode += gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id, &config); - check_add_eol(gcode); + start_filament_gcode_str = gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id, &config); + check_add_eol(start_filament_gcode_str); } + + // Insert the end filament, toolchange, and start filament gcode into the generated gcode. + DynamicConfig config; + config.set_key_value("end_filament_gcode", new ConfigOptionString(end_filament_gcode_str)); + config.set_key_value("toolchange_gcode", new ConfigOptionString(toolchange_gcode_str)); + config.set_key_value("start_filament_gcode", new ConfigOptionString(start_filament_gcode_str)); + std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config); + unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode); + gcode += tcr_gcode; + check_add_eol(toolchange_gcode_str); + + // A phony move to the end position at the wipe tower. - gcodegen.writer().travel_to_xy(Vec2d(end_pos.x, end_pos.y)); + gcodegen.writer().travel_to_xy(end_pos.cast()); gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos)); // Prepare a future wipe. @@ -224,8 +283,8 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, end_pos)); // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge. gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, - WipeTower::xy((std::abs(m_left - end_pos.x) < std::abs(m_right - end_pos.x)) ? m_right : m_left, - end_pos.y))); + Vec2f((std::abs(m_left - end_pos.x()) < std::abs(m_right - end_pos.x())) ? m_right : m_left, + end_pos.y()))); } // Let the planner know we are traveling between objects. @@ -235,17 +294,21 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T // This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode // Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate) -std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gcode_original, const WipeTower::xy& start_pos, const WipeTower::xy& translation, float angle) const +std::string WipeTowerIntegration::post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const { - std::istringstream gcode_str(gcode_original); + Vec2f extruder_offset = m_extruder_offsets[tcr.initial_tool].cast(); + + std::istringstream gcode_str(tcr.gcode); std::string gcode_out; std::string line; - WipeTower::xy pos = start_pos; - WipeTower::xy transformed_pos; - WipeTower::xy old_pos(-1000.1f, -1000.1f); + Vec2f pos = tcr.start_pos; + Vec2f transformed_pos = pos; + Vec2f old_pos(-1000.1f, -1000.1f); while (gcode_str) { std::getline(gcode_str, line); // we read the gcode line by line + + // All G1 commands should be translated and rotated if (line.find("G1 ") == 0) { std::ostringstream line_out; std::istringstream line_str(line); @@ -253,31 +316,48 @@ std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gco char ch = 0; while (line_str >> ch) { if (ch == 'X') - line_str >> pos.x; + line_str >> pos.x(); else if (ch == 'Y') - line_str >> pos.y; + line_str >> pos.y(); else line_out << ch; } transformed_pos = pos; - transformed_pos.rotate(angle); - transformed_pos.translate(translation); + transformed_pos = Eigen::Rotation2Df(angle) * transformed_pos; + transformed_pos += translation; if (transformed_pos != old_pos) { line = line_out.str(); - char buf[2048] = "G1"; - if (transformed_pos.x != old_pos.x) - sprintf(buf + strlen(buf), " X%.3f", transformed_pos.x); - if (transformed_pos.y != old_pos.y) - sprintf(buf + strlen(buf), " Y%.3f", transformed_pos.y); + std::ostringstream oss; + oss << std::fixed << std::setprecision(3) << "G1 "; + if (transformed_pos.x() != old_pos.x()) + oss << " X" << transformed_pos.x() - extruder_offset.x(); + if (transformed_pos.y() != old_pos.y()) + oss << " Y" << transformed_pos.y() - extruder_offset.y(); - line.replace(line.find("G1 "), 3, buf); + line.replace(line.find("G1 "), 3, oss.str()); old_pos = transformed_pos; } } + gcode_out += line + "\n"; + + // If this was a toolchange command, we should change current extruder offset + if (line == "[toolchange_gcode]") { + extruder_offset = m_extruder_offsets[tcr.new_tool].cast(); + + // If the extruder offset changed, add an extra move so everything is continuous + if (extruder_offset != m_extruder_offsets[tcr.initial_tool].cast()) { + std::ostringstream oss; + oss << std::fixed << std::setprecision(3) + << "G1 X" << transformed_pos.x() - extruder_offset.x() + << " Y" << transformed_pos.y() - extruder_offset.y() + << "\n"; + gcode_out += oss.str(); + } + } } return gcode_out; } @@ -288,27 +368,36 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen) assert(m_layer_idx == 0); std::string gcode; - if (&m_priming != nullptr && ! m_priming.extrusions.empty()) { + if (&m_priming != nullptr) { // Disable linear advance for the wipe tower operations. - gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n")); - // Let the tool change be executed by the wipe tower class. - // Inform the G-code writer about the changes done behind its back. - gcode += m_priming.gcode; - // Let the m_writer know the current extruder_id, but ignore the generated G-code. - unsigned int current_extruder_id = m_priming.extrusions.back().tool; - gcodegen.writer().toolchange(current_extruder_id); - gcodegen.placeholder_parser().set("current_extruder", current_extruder_id); + //gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n")); + + for (const WipeTower::ToolChangeResult& tcr : m_priming) { + if (!tcr.extrusions.empty()) + gcode += append_tcr(gcodegen, tcr, tcr.new_tool); + + + // Let the tool change be executed by the wipe tower class. + // Inform the G-code writer about the changes done behind its back. + //gcode += tcr.gcode; + // Let the m_writer know the current extruder_id, but ignore the generated G-code. + // unsigned int current_extruder_id = tcr.extrusions.back().tool; + // gcodegen.writer().toolchange(current_extruder_id); + // gcodegen.placeholder_parser().set("current_extruder", current_extruder_id); + + } + // A phony move to the end position at the wipe tower. - gcodegen.writer().travel_to_xy(Vec2d(m_priming.end_pos.x, m_priming.end_pos.y)); - gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.end_pos)); + /* gcodegen.writer().travel_to_xy(Vec2d(m_priming.back().end_pos.x, m_priming.back().end_pos.y)); + gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos)); // Prepare a future wipe. gcodegen.m_wipe.path.points.clear(); // Start the wipe at the current position. - gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, m_priming.end_pos)); + gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos)); // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge. - gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, - WipeTower::xy((std::abs(m_left - m_priming.end_pos.x) < std::abs(m_right - m_priming.end_pos.x)) ? m_right : m_left, - m_priming.end_pos.y))); + gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, + WipeTower::xy((std::abs(m_left - m_priming.back().end_pos.x) < std::abs(m_right - m_priming.back().end_pos.x)) ? m_right : m_left, + m_priming.back().end_pos.y)));*/ } return gcode; } @@ -316,10 +405,11 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen) std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, bool finish_layer) { std::string gcode; - assert(m_layer_idx >= 0 && m_layer_idx <= m_tool_changes.size()); + assert(m_layer_idx >= 0 && size_t(m_layer_idx) <= m_tool_changes.size()); if (! m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { - if (m_layer_idx < m_tool_changes.size()) { - assert(m_tool_change_idx < m_tool_changes[m_layer_idx].size()); + if (m_layer_idx < (int)m_tool_changes.size()) { + if (! (size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size())) + throw std::runtime_error("Wipe tower generation failed, possibly due to empty first layer."); gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id); } m_brim_done = true; @@ -349,6 +439,7 @@ std::vector GCode::collect_layers_to_print(const PrintObjec // Pair the object layers with the support layers by z. size_t idx_object_layer = 0; size_t idx_support_layer = 0; + const LayerToPrint* last_extrusion_layer = nullptr; while (idx_object_layer < object.layers().size() || idx_support_layer < object.support_layers().size()) { LayerToPrint layer_to_print; layer_to_print.object_layer = (idx_object_layer < object.layers().size()) ? object.layers()[idx_object_layer ++] : nullptr; @@ -362,7 +453,31 @@ std::vector GCode::collect_layers_to_print(const PrintObjec -- idx_object_layer; } } + layers_to_print.emplace_back(layer_to_print); + + // In case there are extrusions on this layer, check there is a layer to lay it on. + if ((layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) + || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions())) { + double support_contact_z = (last_extrusion_layer && last_extrusion_layer->support_layer) + ? object.config().support_material_contact_distance + : 0.; + double maximal_print_z = (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.) + + layer_to_print.layer()->height + + std::max(0., support_contact_z); + // Negative support_contact_z is not taken into account, it can result in false positives in cases + // where previous layer has object extrusions too (https://github.com/prusa3d/PrusaSlicer/issues/2752) + + + if (layer_to_print.print_z() > maximal_print_z + EPSILON) + throw std::runtime_error(_(L("Empty layers detected, the output would not be printable.")) + "\n\n" + + _(L("Object name: ")) + object.model_object()->name + "\n" + _(L("Print z: ")) + + std::to_string(layers_to_print.back().print_z()) + "\n\n" + _(L("This is " + "usually caused by negligibly small extrusions or by a faulty model. Try to repair " + " the model or change its orientation on the bed."))); + // Remember last layer with extrusions. + last_extrusion_layer = &layers_to_print.back(); + } } return layers_to_print; @@ -475,11 +590,11 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ } if (print->config().remaining_times.value) { - BOOST_LOG_TRIVIAL(debug) << "Processing remaining times for normal mode"; + BOOST_LOG_TRIVIAL(debug) << "Processing remaining times for normal mode" << log_memory_info(); m_normal_time_estimator.post_process_remaining_times(path_tmp, 60.0f); m_normal_time_estimator.reset(); if (m_silent_time_estimator_enabled) { - BOOST_LOG_TRIVIAL(debug) << "Processing remaining times for silent mode"; + BOOST_LOG_TRIVIAL(debug) << "Processing remaining times for silent mode" << log_memory_info(); m_silent_time_estimator.post_process_remaining_times(path_tmp, 60.0f); m_silent_time_estimator.reset(); } @@ -487,12 +602,12 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ // starts analyzer calculations if (m_enable_analyzer) { - BOOST_LOG_TRIVIAL(debug) << "Preparing G-code preview data"; + BOOST_LOG_TRIVIAL(debug) << "Preparing G-code preview data" << log_memory_info(); m_analyzer.calc_gcode_preview_data(*preview_data, [print]() { print->throw_if_canceled(); }); m_analyzer.reset(); } - if (rename_file(path_tmp, path) != 0) + if (rename_file(path_tmp, path)) throw std::runtime_error( std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' + "Is " + path_tmp + " locked?" + '\n'); @@ -586,6 +701,9 @@ void GCode::_do_export(Print &print, FILE *file) } m_analyzer.set_extruder_offsets(extruder_offsets); + // tell analyzer about the gcode flavor + m_analyzer.set_gcode_flavor(print.config().gcode_flavor); + // resets analyzer's tracking data m_last_mm3_per_mm = GCodeAnalyzer::Default_mm3_per_mm; m_last_width = GCodeAnalyzer::Default_Width; @@ -659,7 +777,7 @@ void GCode::_do_export(Print &print, FILE *file) mm3_per_mm.erase(std::remove_if(mm3_per_mm.begin(), mm3_per_mm.end(), [](double v) { return v < 0.000001; }), mm3_per_mm.end()); if (! mm3_per_mm.empty()) { // In order to honor max_print_speed we need to find a target volumetric - // speed that we can use throughout the print. So we define this target + // speed that we can use throughout the print. So we define this target // volumetric speed as the volumetric speed produced by printing the // smallest cross-section at the maximum speed: any larger cross-section // will need slower feedrates. @@ -726,7 +844,7 @@ void GCode::_do_export(Print &print, FILE *file) _writeln(file, GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag); } - // Prepare the helper object for replacing placeholders in custom G-code and output filename. + // Prepare the helper object for replacing placeholders in custom G-code and output filename. m_placeholder_parser = print.placeholder_parser(); m_placeholder_parser.update_timestamp(); print.update_object_placeholders(m_placeholder_parser.config_writable(), ".gcode"); @@ -781,6 +899,8 @@ void GCode::_do_export(Print &print, FILE *file) m_placeholder_parser.set("initial_tool", initial_extruder_id); m_placeholder_parser.set("initial_extruder", initial_extruder_id); m_placeholder_parser.set("current_extruder", initial_extruder_id); + //Set variable for total layer count so it can be used in custom gcode. + m_placeholder_parser.set("total_layer_count", m_layer_count); // Useful for sequential prints. m_placeholder_parser.set("current_object_idx", 0); // For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided. @@ -802,24 +922,16 @@ void GCode::_do_export(Print &print, FILE *file) // Write the custom start G-code _writeln(file, start_gcode); - // Process filament-specific gcode in extruder order. - if (print.config().single_extruder_multi_material) { - if (has_wipe_tower) { - // Wipe tower will control the extruder switching, it will call the start_filament_gcode. - } else { - // Only initialize the initial extruder. + + // Process filament-specific gcode. + /* if (has_wipe_tower) { + // Wipe tower will control the extruder switching, it will call the start_filament_gcode. + } else { DynamicConfig config; config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(initial_extruder_id))); - _writeln(file, this->placeholder_parser_process("start_filament_gcode", print.config().start_filament_gcode.values[initial_extruder_id], initial_extruder_id, &config)); - } - } else { - DynamicConfig config; - for (const std::string &start_gcode : print.config().start_filament_gcode.values) { - int extruder_id = (unsigned int)(&start_gcode - &print.config().start_filament_gcode.values.front()); - config.set_key_value("filament_extruder_id", new ConfigOptionInt(extruder_id)); - _writeln(file, this->placeholder_parser_process("start_filament_gcode", start_gcode, extruder_id, &config)); - } + _writeln(file, this->placeholder_parser_process("start_filament_gcode", print.config().start_filament_gcode.values[initial_extruder_id], initial_extruder_id, &config)); } +*/ this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, true); print.throw_if_canceled(); @@ -1055,6 +1167,10 @@ void GCode::_do_export(Print &print, FILE *file) print.m_print_statistics.clear(); print.m_print_statistics.estimated_normal_print_time = m_normal_time_estimator.get_time_dhms(); print.m_print_statistics.estimated_silent_print_time = m_silent_time_estimator_enabled ? m_silent_time_estimator.get_time_dhms() : "N/A"; + print.m_print_statistics.estimated_normal_color_print_times = m_normal_time_estimator.get_color_times_dhms(true); + if (m_silent_time_estimator_enabled) + print.m_print_statistics.estimated_silent_color_print_times = m_silent_time_estimator.get_color_times_dhms(true); + std::vector extruders = m_writer.extruders(); if (! extruders.empty()) { std::pair out_filament_used_mm ("; filament used [mm] = ", 0); @@ -1251,7 +1367,7 @@ void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, c int temp = print.config().first_layer_temperature.get_at(first_printing_extruder_id); if (temp_by_gcode >= 0 && temp_by_gcode < 1000) temp = temp_by_gcode; - m_writer.set_temperature(temp_by_gcode, wait, first_printing_extruder_id); + m_writer.set_temperature(temp, wait, first_printing_extruder_id); } else { // Custom G-code does not set the extruder temperature. Do it now. if (print.config().single_extruder_multi_material.value) { @@ -1342,11 +1458,11 @@ void GCode::process_layer( // Check whether it is possible to apply the spiral vase logic for this layer. // Just a reminder: A spiral vase mode is allowed for a single object, single material print only. if (m_spiral_vase && layers.size() == 1 && support_layer == nullptr) { - bool enable = (layer.id() > 0 || print.config().brim_width.value == 0.) && (layer.id() >= print.config().skirt_height.value && ! print.has_infinite_skirt()); + bool enable = (layer.id() > 0 || print.config().brim_width.value == 0.) && (layer.id() >= (size_t)print.config().skirt_height.value && ! print.has_infinite_skirt()); if (enable) { for (const LayerRegion *layer_region : layer.regions()) - if (layer_region->region()->config().bottom_solid_layers.value > layer.id() || - layer_region->perimeters.items_count() > 1 || + if (size_t(layer_region->region()->config().bottom_solid_layers.value) > layer.id() || + layer_region->perimeters.items_count() > 1u || layer_region->fills.items_count() > 0) { enable = false; break; @@ -1403,8 +1519,16 @@ void GCode::process_layer( m_colorprint_heights.erase(m_colorprint_heights.begin()); colorprint_change = true; } - if (colorprint_change && print.extruders().size()==1) + + // we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count + if (colorprint_change && print./*extruders()*/config().nozzle_diameter.size()==1) + { + // add tag for analyzer + gcode += "; " + GCodeAnalyzer::Color_Change_Tag + "\n"; + // add tag for time estimator + gcode += "; " + GCodeTimeEstimator::Color_Change_Tag + "\n"; gcode += "M600\n"; + } // Extrude skirt at the print_z of the raft layers and normal object layers @@ -1412,11 +1536,11 @@ void GCode::process_layer( bool extrude_skirt = ! print.skirt().entities.empty() && // Not enough skirt layers printed yet. - (m_skirt_done.size() < print.config().skirt_height.value || print.has_infinite_skirt()) && + (m_skirt_done.size() < (size_t)print.config().skirt_height.value || print.has_infinite_skirt()) && // This print_z has not been extruded yet (m_skirt_done.empty() ? 0. : m_skirt_done.back()) < print_z - EPSILON && // and this layer is the 1st layer, or it is an object layer, or it is a raft layer. - (first_layer || object_layer != nullptr || support_layer->id() < m_config.raft_layers.value); + (first_layer || object_layer != nullptr || support_layer->id() < (size_t)m_config.raft_layers.value); std::map> skirt_loops_per_extruder; coordf_t skirt_height = 0.; if (extrude_skirt) { @@ -1731,7 +1855,8 @@ void GCode::process_layer( ", time estimator memory: " << format_memsize_MB(m_normal_time_estimator.memory_used() + m_silent_time_estimator_enabled ? m_silent_time_estimator.memory_used() : 0) << ", analyzer memory: " << - format_memsize_MB(m_analyzer.memory_used()); + format_memsize_MB(m_analyzer.memory_used()) << + log_memory_info(); } void GCode::apply_print_config(const PrintConfig &print_config) @@ -1740,25 +1865,12 @@ void GCode::apply_print_config(const PrintConfig &print_config) m_config.apply(print_config); } -void GCode::append_full_config(const Print& print, std::string& str) +void GCode::append_full_config(const Print &print, std::string &str) { - const StaticPrintConfig *configs[] = { static_cast(&print.config()), &print.default_object_config(), &print.default_region_config() }; - for (size_t i = 0; i < sizeof(configs) / sizeof(configs[0]); ++i) { - const StaticPrintConfig *cfg = configs[i]; - for (const std::string &key : cfg->keys()) - if (key != "compatible_printers") - str += "; " + key + " = " + cfg->serialize(key) + "\n"; - } - const DynamicConfig &full_config = print.placeholder_parser().config(); - for (const char *key : { - "print_settings_id", "filament_settings_id", "sla_print_settings_id", "sla_material_settings_id", "printer_settings_id", - "printer_model", "printer_variant", - "default_print_profile", "default_filament_profile", "default_sla_print_profile", "default_sla_material_profile", - "compatible_prints_condition_cummulative", "compatible_printers_condition_cummulative", "inherits_cummulative" }) { - const ConfigOption *opt = full_config.option(key); - if (opt != nullptr) - str += std::string("; ") + key + " = " + opt->serialize() + "\n"; - } + const DynamicPrintConfig &cfg = print.full_print_config(); + for (const std::string &key : cfg.keys()) + if (key != "compatible_prints" && key != "compatible_printers" && ! cfg.option(key)->is_nil()) + str += "; " + key + " = " + cfg.opt_serialize(key) + "\n"; } void GCode::set_extruders(const std::vector &extruder_ids) @@ -2065,19 +2177,18 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // Retrieve the last start position for this object. float last_pos_weight = 1.f; - switch (seam_position) { - case spAligned: + + if (seam_position == spAligned) { // Seam is aligned to the seam at the preceding layer. if (m_layer != NULL && m_seam_position.count(m_layer->object()) > 0) { last_pos = m_seam_position[m_layer->object()]; last_pos_weight = 1.f; } - break; - case spRear: + } + else if (seam_position == spRear) { last_pos = m_layer->object()->bounding_box().center(); last_pos(1) += coord_t(3. * m_layer->object()->bounding_box().radius()); last_pos_weight = 5.f; - break; } // Insert a projection of last_pos into the polygon. @@ -2086,7 +2197,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou Points::iterator it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r); last_pos_proj_idx = it - polygon.points.begin(); } - Point last_pos_proj = polygon.points[last_pos_proj_idx]; + // Parametrize the polygon by its length. std::vector lengths = polygon_parameter_by_length(polygon); @@ -2096,7 +2207,6 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // No penalty for reflex points, slight penalty for convex points, high penalty for flat surfaces. const float penaltyConvexVertex = 1.f; const float penaltyFlatSurface = 5.f; - const float penaltySeam = 1.3f; const float penaltyOverhangHalf = 10.f; // Penalty for visible seams. for (size_t i = 0; i < polygon.points.size(); ++ i) { @@ -2141,10 +2251,14 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // Signed distance is positive outside the object, negative inside the object. // The point is considered at an overhang, if it is more than nozzle radius // outside of the lower layer contour. - bool found = (*lower_layer_edge_grid)->signed_distance(p, search_r, dist); + #ifdef NDEBUG // to suppress unused variable warning in release mode + (*lower_layer_edge_grid)->signed_distance(p, search_r, dist); + #else + bool found = (*lower_layer_edge_grid)->signed_distance(p, search_r, dist); + #endif // If the approximate Signed Distance Field was initialized over lower_layer_edge_grid, // then the signed distnace shall always be known. - assert(found); + assert(found); penalties[i] += extrudate_overlap_penalty(float(nozzle_r), penaltyOverhangHalf, float(dist)); } } @@ -2520,7 +2634,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, EXTRUDER_CONFIG(filament_max_volumetric_speed) / path.mm3_per_mm ); } - double F = speed * 60; // convert mm/sec to mm/min + double F = speed * 60; // convert mm/sec to mm/min // extrude arc or line if (m_enable_extrusion_role_markers) @@ -2552,7 +2666,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, gcode += buf; } - if (m_last_mm3_per_mm != path.mm3_per_mm) + if (last_was_wipe_tower || (m_last_mm3_per_mm != path.mm3_per_mm)) { m_last_mm3_per_mm = path.mm3_per_mm; @@ -2720,7 +2834,17 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) // if we are running a single-extruder setup, just set the extruder and return nothing if (!m_writer.multiple_extruders) { m_placeholder_parser.set("current_extruder", extruder_id); - return m_writer.toolchange(extruder_id); + + std::string gcode; + // Append the filament start G-code. + const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id); + if (! start_filament_gcode.empty()) { + // Process the start_filament_gcode for the filament. + gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id); + check_add_eol(gcode); + } + gcode += m_writer.toolchange(extruder_id); + return gcode; } // prepend retraction on the current extruder @@ -2730,38 +2854,57 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) m_wipe.reset_path(); if (m_writer.extruder() != nullptr) { - // Process the custom end_filament_gcode in case of single_extruder_multi_material. + // Process the custom end_filament_gcode. set_extruder() is only called if there is no wipe tower + // so it should not be injected twice. unsigned int old_extruder_id = m_writer.extruder()->id(); const std::string &end_filament_gcode = m_config.end_filament_gcode.get_at(old_extruder_id); - if (m_config.single_extruder_multi_material && ! end_filament_gcode.empty()) { + if (! end_filament_gcode.empty()) { gcode += placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id); check_add_eol(gcode); } } - m_placeholder_parser.set("current_extruder", extruder_id); - - if (m_writer.extruder() != nullptr && ! m_config.toolchange_gcode.value.empty()) { - // Process the custom toolchange_gcode. - DynamicConfig config; - config.set_key_value("previous_extruder", new ConfigOptionInt((int)m_writer.extruder()->id())); - config.set_key_value("next_extruder", new ConfigOptionInt((int)extruder_id)); - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); - config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); - gcode += placeholder_parser_process("toolchange_gcode", m_config.toolchange_gcode.value, extruder_id, &config); - check_add_eol(gcode); - } // If ooze prevention is enabled, park current extruder in the nearest // standby point and set it to the standby temperature. if (m_ooze_prevention.enable && m_writer.extruder() != nullptr) gcode += m_ooze_prevention.pre_toolchange(*this); - // Append the toolchange command. - gcode += m_writer.toolchange(extruder_id); - // Append the filament start G-code for single_extruder_multi_material. + + const std::string& toolchange_gcode = m_config.toolchange_gcode.value; + + // Process the custom toolchange_gcode. If it is empty, insert just a Tn command. + if (!toolchange_gcode.empty()) { + DynamicConfig config; + config.set_key_value("previous_extruder", new ConfigOptionInt((int)(m_writer.extruder() != nullptr ? m_writer.extruder()->id() : -1 ))); + config.set_key_value("next_extruder", new ConfigOptionInt((int)extruder_id)); + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); + gcode += placeholder_parser_process("toolchange_gcode", toolchange_gcode, extruder_id, &config); + check_add_eol(gcode); + } + + // We inform the writer about what is happening, but we may not use the resulting gcode. + std::string toolchange_command = m_writer.toolchange(extruder_id); + if (toolchange_gcode.empty()) + gcode += toolchange_command; + else { + // user provided his own toolchange gcode, no need to do anything + } + + // Set the temperature if the wipe tower didn't (not needed for non-single extruder MM) + if (m_config.single_extruder_multi_material && !m_config.wipe_tower) { + int temp = (m_layer_index <= 0 ? m_config.first_layer_temperature.get_at(extruder_id) : + m_config.temperature.get_at(extruder_id)); + + gcode += m_writer.set_temperature(temp, false); + } + + m_placeholder_parser.set("current_extruder", extruder_id); + + // Append the filament start G-code. const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id); - if (m_config.single_extruder_multi_material && ! start_filament_gcode.empty()) { - // Process the start_filament_gcode for the active filament only. + if (! start_filament_gcode.empty()) { + // Process the start_filament_gcode for the new filament. gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id); check_add_eol(gcode); } diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 21957d32c0..f2a67f600b 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -83,13 +83,14 @@ class WipeTowerIntegration { public: WipeTowerIntegration( const PrintConfig &print_config, - const WipeTower::ToolChangeResult &priming, + const std::vector &priming, const std::vector> &tool_changes, const WipeTower::ToolChangeResult &final_purge) : m_left(/*float(print_config.wipe_tower_x.value)*/ 0.f), m_right(float(/*print_config.wipe_tower_x.value +*/ print_config.wipe_tower_width.value)), m_wipe_tower_pos(float(print_config.wipe_tower_x.value), float(print_config.wipe_tower_y.value)), m_wipe_tower_rotation(float(print_config.wipe_tower_rotation_angle)), + m_extruder_offsets(print_config.extruder_offset.values), m_priming(priming), m_tool_changes(tool_changes), m_final_purge(final_purge), @@ -107,16 +108,18 @@ private: WipeTowerIntegration& operator=(const WipeTowerIntegration&); std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const; - // Postprocesses gcode: rotates and moves all G1 extrusions and returns result - std::string rotate_wipe_tower_moves(const std::string& gcode_original, const WipeTower::xy& start_pos, const WipeTower::xy& translation, float angle) const; + // Postprocesses gcode: rotates and moves G1 extrusions and returns result + std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const; // Left / right edges of the wipe tower, for the planning of wipe moves. const float m_left; const float m_right; - const WipeTower::xy m_wipe_tower_pos; + const Vec2f m_wipe_tower_pos; const float m_wipe_tower_rotation; + const std::vector m_extruder_offsets; + // Reference to cached values at the Printer class. - const WipeTower::ToolChangeResult &m_priming; + const std::vector &m_priming; const std::vector> &m_tool_changes; const WipeTower::ToolChangeResult &m_final_purge; // Current layer index. diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp index 321d9a3427..b28aa558e5 100644 --- a/src/libslic3r/GCode/Analyzer.cpp +++ b/src/libslic3r/GCode/Analyzer.cpp @@ -25,6 +25,7 @@ const std::string GCodeAnalyzer::Extrusion_Role_Tag = "_ANALYZER_EXTR_ROLE:"; const std::string GCodeAnalyzer::Mm3_Per_Mm_Tag = "_ANALYZER_MM3_PER_MM:"; const std::string GCodeAnalyzer::Width_Tag = "_ANALYZER_WIDTH:"; const std::string GCodeAnalyzer::Height_Tag = "_ANALYZER_HEIGHT:"; +const std::string GCodeAnalyzer::Color_Change_Tag = "_ANALYZER_COLOR_CHANGE"; const double GCodeAnalyzer::Default_mm3_per_mm = 0.0; const float GCodeAnalyzer::Default_Width = 0.0f; @@ -106,6 +107,11 @@ void GCodeAnalyzer::set_extruder_offsets(const GCodeAnalyzer::ExtruderOffsetsMap m_extruder_offsets = extruder_offsets; } +void GCodeAnalyzer::set_gcode_flavor(const GCodeFlavor& flavor) +{ + m_gcode_flavor = flavor; +} + void GCodeAnalyzer::reset() { _set_units(Millimeters); @@ -121,6 +127,7 @@ void GCodeAnalyzer::reset() _set_start_position(DEFAULT_START_POSITION); _set_start_extrusion(DEFAULT_START_EXTRUSION); _reset_axes_position(); + _reset_cached_position(); m_moves_map.clear(); m_extruder_offsets.clear(); @@ -234,11 +241,6 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi { switch (::atoi(&cmd[1])) { - case 600: // Set color change - { - _processM600(line); - break; - } case 82: // Set extruder to absolute mode { _processM82(line); @@ -249,6 +251,24 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi _processM83(line); break; } + case 108: + case 135: + { + // these are used by MakerWare and Sailfish firmwares + // for tool changing - we can process it in one place + _processM108orM135(line); + break; + } + case 401: // Repetier: Store x, y and z position + { + _processM401(line); + break; + } + case 402: // Repetier: Go to stored position + { + _processM402(line); + break; + } } break; @@ -420,15 +440,81 @@ void GCodeAnalyzer::_processM83(const GCodeReader::GCodeLine& line) _set_e_local_positioning_type(Relative); } -void GCodeAnalyzer::_processM600(const GCodeReader::GCodeLine& line) +void GCodeAnalyzer::_processM108orM135(const GCodeReader::GCodeLine& line) { - m_state.cur_cp_color_id++; - _set_cp_color_id(m_state.cur_cp_color_id); + // These M-codes are used by MakerWare and Sailfish to change active tool. + // They have to be processed otherwise toolchanges will be unrecognised + // by the analyzer - see https://github.com/prusa3d/PrusaSlicer/issues/2566 + + size_t code = ::atoi(&(line.cmd()[1])); + if ((code == 108 && m_gcode_flavor == gcfSailfish) + || (code == 135 && m_gcode_flavor == gcfMakerWare)) { + + std::string cmd = line.raw(); + size_t T_pos = cmd.find("T"); + if (T_pos != std::string::npos) { + cmd = cmd.substr(T_pos); + _processT(cmd); + } + } } -void GCodeAnalyzer::_processT(const GCodeReader::GCodeLine& line) +void GCodeAnalyzer::_processM401(const GCodeReader::GCodeLine& line) +{ + if (m_gcode_flavor != gcfRepetier) + return; + + for (unsigned char a = 0; a <= 3; ++a) + { + _set_cached_position(a, _get_axis_position((EAxis)a)); + } + _set_cached_position(4, _get_feedrate()); +} + +void GCodeAnalyzer::_processM402(const GCodeReader::GCodeLine& line) +{ + if (m_gcode_flavor != gcfRepetier) + return; + + // see for reference: + // https://github.com/repetier/Repetier-Firmware/blob/master/src/ArduinoAVR/Repetier/Printer.cpp + // void Printer::GoToMemoryPosition(bool x, bool y, bool z, bool e, float feed) + + bool has_xyz = !(line.has_x() || line.has_y() || line.has_z()); + + float p = FLT_MAX; + for (unsigned char a = X; a <= Z; ++a) + { + if (has_xyz || line.has(a)) + { + p = _get_cached_position(a); + if (p != FLT_MAX) + _set_axis_position((EAxis)a, p); + } + } + + p = _get_cached_position(E); + if (p != FLT_MAX) + _set_axis_position(E, p); + + p = FLT_MAX; + if (!line.has_value(4, p)) + p = _get_cached_position(4); + + if (p != FLT_MAX) + _set_feedrate(p); +} + +void GCodeAnalyzer::_reset_cached_position() +{ + for (unsigned char a = 0; a <= 4; ++a) + { + m_state.cached_position[a] = FLT_MAX; + } +} + +void GCodeAnalyzer::_processT(const std::string& cmd) { - std::string cmd = line.cmd(); if (cmd.length() > 1) { unsigned int id = (unsigned int)::strtol(cmd.substr(1).c_str(), nullptr, 10); @@ -442,6 +528,11 @@ void GCodeAnalyzer::_processT(const GCodeReader::GCodeLine& line) } } +void GCodeAnalyzer::_processT(const GCodeReader::GCodeLine& line) +{ + _processT(line.cmd()); +} + bool GCodeAnalyzer::_process_tags(const GCodeReader::GCodeLine& line) { std::string comment = line.comment(); @@ -478,6 +569,14 @@ bool GCodeAnalyzer::_process_tags(const GCodeReader::GCodeLine& line) return true; } + // color change tag + pos = comment.find(Color_Change_Tag); + if (pos != comment.npos) + { + _process_color_change_tag(); + return true; + } + return false; } @@ -507,6 +606,12 @@ void GCodeAnalyzer::_process_height_tag(const std::string& comment, size_t pos) _set_height((float)::strtod(comment.substr(pos + Height_Tag.length()).c_str(), nullptr)); } +void GCodeAnalyzer::_process_color_change_tag() +{ + m_state.cur_cp_color_id++; + _set_cp_color_id(m_state.cur_cp_color_id); +} + void GCodeAnalyzer::_set_units(GCodeAnalyzer::EUnits units) { m_state.units = units; @@ -632,6 +737,17 @@ const Vec3d& GCodeAnalyzer::_get_start_position() const return m_state.start_position; } +void GCodeAnalyzer::_set_cached_position(unsigned char axis, float position) +{ + if ((0 <= axis) || (axis <= 4)) + m_state.cached_position[axis] = position; +} + +float GCodeAnalyzer::_get_cached_position(unsigned char axis) const +{ + return ((0 <= axis) || (axis <= 4)) ? m_state.cached_position[axis] : FLT_MAX; +} + void GCodeAnalyzer::_set_start_extrusion(float extrusion) { m_state.start_extrusion = extrusion; @@ -833,12 +949,12 @@ void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data, s polyline = Polyline3(); // add both vertices of the move - polyline.append(Vec3crd(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z()))); - polyline.append(Vec3crd(scale_(move.end_position.x()), scale_(move.end_position.y()), scale_(move.end_position.z()))); + polyline.append(Vec3crd((int)scale_(move.start_position.x()), (int)scale_(move.start_position.y()), (int)scale_(move.start_position.z()))); + polyline.append(Vec3crd((int)scale_(move.end_position.x()), (int)scale_(move.end_position.y()), (int)scale_(move.end_position.z()))); } else // append end vertex of the move to current polyline - polyline.append(Vec3crd(scale_(move.end_position.x()), scale_(move.end_position.y()), scale_(move.end_position.z()))); + polyline.append(Vec3crd((int)scale_(move.end_position.x()), (int)scale_(move.end_position.y()), (int)scale_(move.end_position.z()))); // update current values position = move.end_position; @@ -882,7 +998,7 @@ void GCodeAnalyzer::_calc_gcode_preview_retractions(GCodePreviewData& preview_da cancel_callback(); // store position - Vec3crd position(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z())); + Vec3crd position((int)scale_(move.start_position.x()), (int)scale_(move.start_position.y()), (int)scale_(move.start_position.z())); preview_data.retraction.positions.emplace_back(position, move.data.width, move.data.height); } @@ -909,7 +1025,7 @@ void GCodeAnalyzer::_calc_gcode_preview_unretractions(GCodePreviewData& preview_ cancel_callback(); // store position - Vec3crd position(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z())); + Vec3crd position((int)scale_(move.start_position.x()), (int)scale_(move.start_position.y()), (int)scale_(move.start_position.z())); preview_data.unretraction.positions.emplace_back(position, move.data.width, move.data.height); } diff --git a/src/libslic3r/GCode/Analyzer.hpp b/src/libslic3r/GCode/Analyzer.hpp index 4c201c6403..47f4e92aa5 100644 --- a/src/libslic3r/GCode/Analyzer.hpp +++ b/src/libslic3r/GCode/Analyzer.hpp @@ -19,6 +19,7 @@ public: static const std::string Mm3_Per_Mm_Tag; static const std::string Width_Tag; static const std::string Height_Tag; + static const std::string Color_Change_Tag; static const double Default_mm3_per_mm; static const float Default_Width; @@ -96,6 +97,7 @@ private: EPositioningType e_local_positioning_type; Metadata data; Vec3d start_position = Vec3d::Zero(); + float cached_position[5]; float start_extrusion; float position[Num_Axis]; unsigned int cur_cp_color_id = 0; @@ -106,6 +108,7 @@ private: GCodeReader m_parser; TypeToMovesMap m_moves_map; ExtruderOffsetsMap m_extruder_offsets; + GCodeFlavor m_gcode_flavor; // The output of process_layer() std::string m_process_output; @@ -115,6 +118,8 @@ public: void set_extruder_offsets(const ExtruderOffsetsMap& extruder_offsets); + void set_gcode_flavor(const GCodeFlavor& flavor); + // Reinitialize the analyzer void reset(); @@ -164,10 +169,17 @@ private: // Set extruder to relative mode void _processM83(const GCodeReader::GCodeLine& line); - // Set color change - void _processM600(const GCodeReader::GCodeLine& line); + // Set tool (MakerWare and Sailfish flavor) + void _processM108orM135(const GCodeReader::GCodeLine& line); + + // Repetier: Store x, y and z position + void _processM401(const GCodeReader::GCodeLine& line); + + // Repetier: Go to stored position + void _processM402(const GCodeReader::GCodeLine& line); // Processes T line (Select Tool) + void _processT(const std::string& command); void _processT(const GCodeReader::GCodeLine& line); // Processes the tags @@ -186,6 +198,9 @@ private: // Processes height tag void _process_height_tag(const std::string& comment, size_t pos); + // Processes color change tag + void _process_color_change_tag(); + void _set_units(EUnits units); EUnits _get_units() const; @@ -225,6 +240,11 @@ private: void _set_start_position(const Vec3d& position); const Vec3d& _get_start_position() const; + void _set_cached_position(unsigned char axis, float position); + float _get_cached_position(unsigned char axis) const; + + void _reset_cached_position(); + void _set_start_extrusion(float extrusion); float _get_start_extrusion() const; float _get_delta_extrusion() const; diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index 552fbf88c8..1aa15ef331 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -672,7 +672,7 @@ std::string CoolingBuffer::apply_layer_cooldown( #define EXTRUDER_CONFIG(OPT) config.OPT.get_at(m_current_extruder) int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed); int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0; - if (layer_id >= EXTRUDER_CONFIG(disable_fan_first_layers)) { + if (layer_id >= (size_t)EXTRUDER_CONFIG(disable_fan_first_layers)) { int max_fan_speed = EXTRUDER_CONFIG(max_fan_speed); float slowdown_below_layer_time = float(EXTRUDER_CONFIG(slowdown_below_layer_time)); float fan_below_layer_time = float(EXTRUDER_CONFIG(fan_below_layer_time)); diff --git a/src/libslic3r/GCode/PreviewData.cpp b/src/libslic3r/GCode/PreviewData.cpp index 8eba6801e4..55f07998bd 100644 --- a/src/libslic3r/GCode/PreviewData.cpp +++ b/src/libslic3r/GCode/PreviewData.cpp @@ -404,6 +404,8 @@ std::string GCodePreviewData::get_legend_title() const return L("Tool"); case Extrusion::ColorPrint: return L("Color Print"); + case Extrusion::Num_View_Types: + break; // just to supress warning about non-handled value } return ""; @@ -466,7 +468,7 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: } case Extrusion::Tool: { - unsigned int tools_colors_count = tool_colors.size() / 4; + unsigned int tools_colors_count = (unsigned int)tool_colors.size() / 4; items.reserve(tools_colors_count); for (unsigned int i = 0; i < tools_colors_count; ++i) { @@ -491,20 +493,25 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: items.emplace_back(Slic3r::I18N::translate(L("Default print color")), color); break; } + + std::string id_str = std::to_string(i + 1) + ": "; + if (i == 0) { - items.emplace_back((boost::format(Slic3r::I18N::translate(L("up to %.2f mm"))) % cp_values[0].first).str(), color); + items.emplace_back(id_str + (boost::format(Slic3r::I18N::translate(L("up to %.2f mm"))) % cp_values[0].first).str(), color); break; } if (i == color_print_cnt) { - items.emplace_back((boost::format(Slic3r::I18N::translate(L("above %.2f mm"))) % cp_values[i-1].second).str(), color); + items.emplace_back(id_str + (boost::format(Slic3r::I18N::translate(L("above %.2f mm"))) % cp_values[i - 1].second).str(), color); continue; } // items.emplace_back((boost::format(Slic3r::I18N::translate(L("%.2f - %.2f mm"))) % cp_values[i-1] % cp_values[i]).str(), color); - items.emplace_back((boost::format(Slic3r::I18N::translate(L("%.2f - %.2f mm"))) % cp_values[i-1].second % cp_values[i].first).str(), color); + items.emplace_back(id_str + (boost::format(Slic3r::I18N::translate(L("%.2f - %.2f mm"))) % cp_values[i - 1].second% cp_values[i].first).str(), color); } break; } + case Extrusion::Num_View_Types: + break; // just to supress warning about non-handled value } return items; diff --git a/src/libslic3r/GCode/PrintExtents.cpp b/src/libslic3r/GCode/PrintExtents.cpp index 92a58fdf06..07a71a0ea1 100644 --- a/src/libslic3r/GCode/PrintExtents.cpp +++ b/src/libslic3r/GCode/PrintExtents.cpp @@ -149,8 +149,8 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_ const WipeTower::Extrusion &e = tcr.extrusions[i]; if (e.width > 0) { Vec2d delta = 0.5 * Vec2d(e.width, e.width); - Vec2d p1 = trafo * Vec2d((&e - 1)->pos.x, (&e - 1)->pos.y); - Vec2d p2 = trafo * Vec2d(e.pos.x, e.pos.y); + Vec2d p1 = trafo * (&e - 1)->pos.cast(); + Vec2d p2 = trafo * e.pos.cast(); bbox.merge(p1.cwiseMin(p2) - delta); bbox.merge(p1.cwiseMax(p2) + delta); } @@ -165,18 +165,19 @@ BoundingBoxf get_wipe_tower_priming_extrusions_extents(const Print &print) { BoundingBoxf bbox; if (print.wipe_tower_data().priming != nullptr) { - const WipeTower::ToolChangeResult &tcr = *print.wipe_tower_data().priming; - for (size_t i = 1; i < tcr.extrusions.size(); ++ i) { - const WipeTower::Extrusion &e = tcr.extrusions[i]; - if (e.width > 0) { - Vec2d p1((&e - 1)->pos.x, (&e - 1)->pos.y); - Vec2d p2(e.pos.x, e.pos.y); - bbox.merge(p1); - coordf_t radius = 0.5 * e.width; - bbox.min(0) = std::min(bbox.min(0), std::min(p1(0), p2(0)) - radius); - bbox.min(1) = std::min(bbox.min(1), std::min(p1(1), p2(1)) - radius); - bbox.max(0) = std::max(bbox.max(0), std::max(p1(0), p2(0)) + radius); - bbox.max(1) = std::max(bbox.max(1), std::max(p1(1), p2(1)) + radius); + for (const WipeTower::ToolChangeResult &tcr : *print.wipe_tower_data().priming) { + for (size_t i = 1; i < tcr.extrusions.size(); ++ i) { + const WipeTower::Extrusion &e = tcr.extrusions[i]; + if (e.width > 0) { + const Vec2d& p1 = (&e - 1)->pos.cast(); + const Vec2d& p2 = e.pos.cast(); + bbox.merge(p1); + coordf_t radius = 0.5 * e.width; + bbox.min(0) = std::min(bbox.min(0), std::min(p1(0), p2(0)) - radius); + bbox.min(1) = std::min(bbox.min(1), std::min(p1(1), p2(1)) - radius); + bbox.max(0) = std::max(bbox.max(0), std::max(p1(0), p2(0)) + radius); + bbox.max(1) = std::max(bbox.max(1), std::max(p1(1), p2(1)) + radius); + } } } } diff --git a/src/libslic3r/GCode/SpiralVase.cpp b/src/libslic3r/GCode/SpiralVase.cpp index 8e8ae3075c..a4ae42b318 100644 --- a/src/libslic3r/GCode/SpiralVase.cpp +++ b/src/libslic3r/GCode/SpiralVase.cpp @@ -24,7 +24,7 @@ std::string SpiralVase::process_layer(const std::string &gcode) // Get total XY length for this layer by summing all extrusion moves. float total_layer_length = 0; float layer_height = 0; - float z; + float z = 0.f; bool set_z = false; { diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index e25ad91fe4..9280aa33a4 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -78,8 +78,13 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool zs.emplace_back(layer->print_z); for (auto layer : object->support_layers()) zs.emplace_back(layer->print_z); - if (! object->layers().empty()) - object_bottom_z = object->layers().front()->print_z - object->layers().front()->height; + + // Find first object layer that is not empty and save its print_z + for (const Layer* layer : object->layers()) + if (layer->has_extrusions()) { + object_bottom_z = layer->print_z - layer->height; + break; + } } this->initialize_layers(zs); } @@ -96,23 +101,6 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool this->collect_extruder_statistics(prime_multi_material); } - -LayerTools& ToolOrdering::tools_for_layer(coordf_t print_z) -{ - auto it_layer_tools = std::lower_bound(m_layer_tools.begin(), m_layer_tools.end(), LayerTools(print_z - EPSILON)); - assert(it_layer_tools != m_layer_tools.end()); - coordf_t dist_min = std::abs(it_layer_tools->print_z - print_z); - for (++ it_layer_tools; it_layer_tools != m_layer_tools.end(); ++it_layer_tools) { - coordf_t d = std::abs(it_layer_tools->print_z - print_z); - if (d >= dist_min) - break; - dist_min = d; - } - -- it_layer_tools; - assert(dist_min < EPSILON); - return *it_layer_tools; -} - void ToolOrdering::initialize_layers(std::vector &zs) { sort_remove_duplicates(zs); @@ -151,7 +139,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object) LayerTools &layer_tools = this->tools_for_layer(layer->print_z); // What extruders are required to print this object layer? for (size_t region_id = 0; region_id < object.region_volumes.size(); ++ region_id) { - const LayerRegion *layerm = (region_id < layer->regions().size()) ? layer->regions()[region_id] : nullptr; + const LayerRegion *layerm = (region_id < layer->regions().size()) ? layer->regions()[region_id] : nullptr; if (layerm == nullptr) continue; const PrintRegion ®ion = *object.print()->regions()[region_id]; @@ -324,17 +312,17 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ m_layer_tools[j].has_wipe_tower = true; } else { LayerTools <_extra = *m_layer_tools.insert(m_layer_tools.begin() + j, lt_new); - LayerTools <_prev = m_layer_tools[j - 1]; + //LayerTools <_prev = m_layer_tools[j]; LayerTools <_next = m_layer_tools[j + 1]; - assert(! lt_prev.extruders.empty() && ! lt_next.extruders.empty()); + assert(! m_layer_tools[j - 1].extruders.empty() && ! lt_next.extruders.empty()); // FIXME: Following assert tripped when running combine_infill.t. I decided to comment it out for now. // If it is a bug, it's likely not critical, because this code is unchanged for a long time. It might // still be worth looking into it more and decide if it is a bug or an obsolete assert. //assert(lt_prev.extruders.back() == lt_next.extruders.front()); - lt_extra.has_wipe_tower = true; + lt_extra.has_wipe_tower = true; lt_extra.extruders.push_back(lt_next.extruders.front()); - lt_extra.wipe_tower_partitions = lt_next.wipe_tower_partitions; - } + lt_extra.wipe_tower_partitions = lt_next.wipe_tower_partitions; + } } } break; @@ -376,7 +364,7 @@ void ToolOrdering::collect_extruder_statistics(bool prime_multi_material) // Reorder m_all_printing_extruders in the sequence they will be primed, the last one will be m_first_printing_extruder. // Then set m_first_printing_extruder to the 1st extruder primed. m_all_printing_extruders.erase( - std::remove_if(m_all_printing_extruders.begin(), m_all_printing_extruders.end(), + std::remove_if(m_all_printing_extruders.begin(), m_all_printing_extruders.end(), [ this ](const unsigned int eid) { return eid == m_first_printing_extruder; }), m_all_printing_extruders.end()); m_all_printing_extruders.emplace_back(m_first_printing_extruder); @@ -495,9 +483,6 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int if (!is_overriddable(*fill, print.config(), *object, region)) continue; - // What extruder would this normally be printed with? - unsigned int correct_extruder = Print::get_extruder(*fill, region); - if (volume_to_wipe<=0) continue; @@ -627,6 +612,6 @@ const std::vector* WipingExtrusions::get_extruder_overrides(const Extrusion return &(entity_map_it->second); } - + } // namespace Slic3r diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index 538127810d..d4006120d2 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -102,45 +102,60 @@ private: class ToolOrdering { public: - ToolOrdering() {} + ToolOrdering() {} - // For the use case when each object is printed separately - // (print.config.complete_objects is true). - ToolOrdering(const PrintObject &object, unsigned int first_extruder = (unsigned int)-1, bool prime_multi_material = false); + // For the use case when each object is printed separately + // (print.config.complete_objects is true). + ToolOrdering(const PrintObject &object, unsigned int first_extruder = (unsigned int)-1, bool prime_multi_material = false); - // For the use case when all objects are printed at once. - // (print.config.complete_objects is false). - ToolOrdering(const Print &print, unsigned int first_extruder = (unsigned int)-1, bool prime_multi_material = false); + // For the use case when all objects are printed at once. + // (print.config.complete_objects is false). + ToolOrdering(const Print &print, unsigned int first_extruder = (unsigned int)-1, bool prime_multi_material = false); - void clear() { m_layer_tools.clear(); } + void clear() { m_layer_tools.clear(); } - // Get the first extruder printing, including the extruder priming areas, returns -1 if there is no layer printed. - unsigned int first_extruder() const { return m_first_printing_extruder; } + // Get the first extruder printing, including the extruder priming areas, returns -1 if there is no layer printed. + unsigned int first_extruder() const { return m_first_printing_extruder; } - // Get the first extruder printing the layer_tools, returns -1 if there is no layer printed. - unsigned int last_extruder() const { return m_last_printing_extruder; } + // Get the first extruder printing the layer_tools, returns -1 if there is no layer printed. + unsigned int last_extruder() const { return m_last_printing_extruder; } - // For a multi-material print, the printing extruders are ordered in the order they shall be primed. - const std::vector& all_extruders() const { return m_all_printing_extruders; } + // For a multi-material print, the printing extruders are ordered in the order they shall be primed. + const std::vector& all_extruders() const { return m_all_printing_extruders; } - // Find LayerTools with the closest print_z. - LayerTools& tools_for_layer(coordf_t print_z); - const LayerTools& tools_for_layer(coordf_t print_z) const - { return *const_cast(&const_cast(this)->tools_for_layer(print_z)); } + template static auto tools_for_layer(Self& self, coordf_t print_z) -> decltype (*self.m_layer_tools.begin()) + { + auto it_layer_tools = std::lower_bound(self.m_layer_tools.begin(), self.m_layer_tools.end(), LayerTools(print_z - EPSILON)); + assert(it_layer_tools != self.m_layer_tools.end()); + coordf_t dist_min = std::abs(it_layer_tools->print_z - print_z); + for (++ it_layer_tools; it_layer_tools != self.m_layer_tools.end(); ++it_layer_tools) { + coordf_t d = std::abs(it_layer_tools->print_z - print_z); + if (d >= dist_min) + break; + dist_min = d; + } + -- it_layer_tools; + assert(dist_min < EPSILON); + return *it_layer_tools; + } - const LayerTools& front() const { return m_layer_tools.front(); } - const LayerTools& back() const { return m_layer_tools.back(); } - std::vector::const_iterator begin() const { return m_layer_tools.begin(); } - std::vector::const_iterator end() const { return m_layer_tools.end(); } - bool empty() const { return m_layer_tools.empty(); } - std::vector& layer_tools() { return m_layer_tools; } - bool has_wipe_tower() const { return ! m_layer_tools.empty() && m_first_printing_extruder != (unsigned int)-1 && m_layer_tools.front().wipe_tower_partitions > 0; } + // Find LayerTools with the closest print_z. + LayerTools& tools_for_layer(coordf_t print_z) { return tools_for_layer(*this, print_z); } + const LayerTools& tools_for_layer(coordf_t print_z) const { return tools_for_layer(*this, print_z); } + + const LayerTools& front() const { return m_layer_tools.front(); } + const LayerTools& back() const { return m_layer_tools.back(); } + std::vector::const_iterator begin() const { return m_layer_tools.begin(); } + std::vector::const_iterator end() const { return m_layer_tools.end(); } + bool empty() const { return m_layer_tools.empty(); } + std::vector& layer_tools() { return m_layer_tools; } + bool has_wipe_tower() const { return ! m_layer_tools.empty() && m_first_printing_extruder != (unsigned int)-1 && m_layer_tools.front().wipe_tower_partitions > 0; } private: - void initialize_layers(std::vector &zs); - void collect_extruders(const PrintObject &object); - void reorder_extruders(unsigned int last_extruder_id); - void fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z); + void initialize_layers(std::vector &zs); + void collect_extruders(const PrintObject &object); + void reorder_extruders(unsigned int last_extruder_id); + void fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z); void collect_extruder_statistics(bool prime_multi_material); std::vector m_layer_tools; diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/src/libslic3r/GCode/WipeTower.cpp similarity index 60% rename from src/libslic3r/GCode/WipeTowerPrusaMM.cpp rename to src/libslic3r/GCode/WipeTower.cpp index edfe475b56..6f09f034fd 100644 --- a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -13,7 +13,7 @@ TODO LIST */ -#include "WipeTowerPrusaMM.hpp" +#include "WipeTower.hpp" #include #include @@ -22,6 +22,7 @@ TODO LIST #include #include "Analyzer.hpp" +#include "BoundingBox.hpp" #if defined(__linux) || defined(__GNUC__ ) #include @@ -35,12 +36,23 @@ TODO LIST namespace Slic3r { -namespace PrusaMultiMaterial { +// Rotate the point around center of the wipe tower about given angle (in degrees) +static Vec2f rotate(const Vec2f& pt, float width, float depth, float angle) +{ + Vec2f out(0,0); + float temp_x = pt(0) - width / 2.f; + float temp_y = pt(1) - depth / 2.f; + angle *= float(M_PI/180.); + out.x() += temp_x * cos(angle) - temp_y * sin(angle) + width / 2.f; + out.y() += temp_x * sin(angle) + temp_y * cos(angle) + depth / 2.f; + return out; +} -class Writer + +class WipeTowerWriter { public: - Writer(float layer_height, float line_width, GCodeFlavor flavor) : + WipeTowerWriter(float layer_height, float line_width, GCodeFlavor flavor, const std::vector& filament_parameters) : m_current_pos(std::numeric_limits::max(), std::numeric_limits::max()), m_current_z(0.f), m_current_feedrate(0.f), @@ -49,7 +61,8 @@ public: m_preview_suppressed(false), m_elapsed_time(0.f), m_default_analyzer_line_width(line_width), - m_gcode_flavor(flavor) + m_gcode_flavor(flavor), + m_filpar(filament_parameters) { // adds tag for analyzer: char buf[64]; @@ -60,7 +73,7 @@ public: change_analyzer_line_width(line_width); } - Writer& change_analyzer_line_width(float line_width) { + WipeTowerWriter& change_analyzer_line_width(float line_width) { // adds tag for analyzer: char buf[64]; sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), line_width); @@ -68,36 +81,51 @@ public: return *this; } - Writer& set_initial_position(const WipeTower::xy &pos, float width = 0.f, float depth = 0.f, float internal_angle = 0.f) { + WipeTowerWriter& change_analyzer_mm3_per_mm(float len, float e) { + static const float area = M_PI * 1.75f * 1.75f / 4.f; + float mm3_per_mm = (len == 0.f ? 0.f : area * e / len); + // adds tag for analyzer: + char buf[64]; + sprintf(buf, ";%s%f\n", GCodeAnalyzer::Mm3_Per_Mm_Tag.c_str(), mm3_per_mm); + m_gcode += buf; + return *this; + } + + WipeTowerWriter& set_initial_position(const Vec2f &pos, float width = 0.f, float depth = 0.f, float internal_angle = 0.f) { m_wipe_tower_width = width; m_wipe_tower_depth = depth; m_internal_angle = internal_angle; - m_start_pos = WipeTower::xy(pos,0.f,m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); + m_start_pos = rotate(pos + Vec2f(0.f,m_y_shift), m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); m_current_pos = pos; return *this; } - Writer& set_initial_tool(const unsigned int tool) { m_current_tool = tool; return *this; } + WipeTowerWriter& set_initial_tool(const unsigned int tool) { m_current_tool = tool; return *this; } - Writer& set_z(float z) + WipeTowerWriter& set_z(float z) { m_current_z = z; return *this; } - Writer& set_extrusion_flow(float flow) + WipeTowerWriter& set_extrusion_flow(float flow) { m_extrusion_flow = flow; return *this; } - Writer& set_y_shift(float shift) { - m_current_pos.y -= shift-m_y_shift; + WipeTowerWriter& set_y_shift(float shift) { + m_current_pos.y() -= shift-m_y_shift; m_y_shift = shift; return (*this); } + WipeTowerWriter& disable_linear_advance() { + m_gcode += (m_gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n")); + return *this; + } + // Suppress / resume G-code preview in Slic3r. Slic3r will have difficulty to differentiate the various // filament loading and cooling moves from normal extrusion moves. Therefore the writer // is asked to suppres output of some lines, which look like extrusions. - Writer& suppress_preview() { change_analyzer_line_width(0.f); m_preview_suppressed = true; return *this; } - Writer& resume_preview() { change_analyzer_line_width(m_default_analyzer_line_width); m_preview_suppressed = false; return *this; } + WipeTowerWriter& suppress_preview() { change_analyzer_line_width(0.f); m_preview_suppressed = true; return *this; } + WipeTowerWriter& resume_preview() { change_analyzer_line_width(m_default_analyzer_line_width); m_preview_suppressed = false; return *this; } - Writer& feedrate(float f) + WipeTowerWriter& feedrate(float f) { if (f != m_current_feedrate) m_gcode += "G1" + set_format_F(f) + "\n"; @@ -106,59 +134,64 @@ public: const std::string& gcode() const { return m_gcode; } const std::vector& extrusions() const { return m_extrusions; } - float x() const { return m_current_pos.x; } - float y() const { return m_current_pos.y; } - const WipeTower::xy& pos() const { return m_current_pos; } - const WipeTower::xy start_pos_rotated() const { return m_start_pos; } - const WipeTower::xy pos_rotated() const { return WipeTower::xy(m_current_pos, 0.f, m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); } + float x() const { return m_current_pos.x(); } + float y() const { return m_current_pos.y(); } + const Vec2f& pos() const { return m_current_pos; } + const Vec2f start_pos_rotated() const { return m_start_pos; } + const Vec2f pos_rotated() const { return rotate(m_current_pos + Vec2f(0.f, m_y_shift), m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); } float elapsed_time() const { return m_elapsed_time; } float get_and_reset_used_filament_length() { float temp = m_used_filament_length; m_used_filament_length = 0.f; return temp; } // Extrude with an explicitely provided amount of extrusion. - Writer& extrude_explicit(float x, float y, float e, float f = 0.f, bool record_length = false) + WipeTowerWriter& extrude_explicit(float x, float y, float e, float f = 0.f, bool record_length = false, bool limit_volumetric_flow = true) { - if (x == m_current_pos.x && y == m_current_pos.y && e == 0.f && (f == 0.f || f == m_current_feedrate)) + if (x == m_current_pos.x() && y == m_current_pos.y() && e == 0.f && (f == 0.f || f == m_current_feedrate)) // Neither extrusion nor a travel move. return *this; - float dx = x - m_current_pos.x; - float dy = y - m_current_pos.y; + float dx = x - m_current_pos.x(); + float dy = y - m_current_pos.y(); double len = sqrt(dx*dx+dy*dy); if (record_length) m_used_filament_length += e; - // Now do the "internal rotation" with respect to the wipe tower center - WipeTower::xy rotated_current_pos(WipeTower::xy(m_current_pos,0.f,m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we are - WipeTower::xy rot(WipeTower::xy(x,y+m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we want to go + Vec2f rotated_current_pos(rotate(m_current_pos + Vec2f(0.f,m_y_shift), m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we are + Vec2f rot(rotate(Vec2f(x,y+m_y_shift), m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we want to go if (! m_preview_suppressed && e > 0.f && len > 0.) { + change_analyzer_mm3_per_mm(len, e); // Width of a squished extrusion, corrected for the roundings of the squished extrusions. // This is left zero if it is a travel move. - float width = float(double(e) * /*Filament_Area*/2.40528 / (len * m_layer_height)); + float width = float(double(e) * m_filpar[0].filament_area / (len * m_layer_height)); // Correct for the roundings of a squished extrusion. width += m_layer_height * float(1. - M_PI / 4.); if (m_extrusions.empty() || m_extrusions.back().pos != rotated_current_pos) m_extrusions.emplace_back(WipeTower::Extrusion(rotated_current_pos, 0, m_current_tool)); - m_extrusions.emplace_back(WipeTower::Extrusion(WipeTower::xy(rot.x, rot.y), width, m_current_tool)); + m_extrusions.emplace_back(WipeTower::Extrusion(rot, width, m_current_tool)); } m_gcode += "G1"; - if (std::abs(rot.x - rotated_current_pos.x) > EPSILON) - m_gcode += set_format_X(rot.x); + if (std::abs(rot.x() - rotated_current_pos.x()) > EPSILON) + m_gcode += set_format_X(rot.x()); - if (std::abs(rot.y - rotated_current_pos.y) > EPSILON) - m_gcode += set_format_Y(rot.y); + if (std::abs(rot.y() - rotated_current_pos.y()) > EPSILON) + m_gcode += set_format_Y(rot.y()); if (e != 0.f) m_gcode += set_format_E(e); - if (f != 0.f && f != m_current_feedrate) + if (f != 0.f && f != m_current_feedrate) { + if (limit_volumetric_flow) { + float e_speed = e / (((len == 0) ? std::abs(e) : len) / f * 60.f); + f /= std::max(1.f, e_speed / m_filpar[m_current_tool].max_e_speed); + } m_gcode += set_format_F(f); + } - m_current_pos.x = x; - m_current_pos.y = y; + m_current_pos.x() = x; + m_current_pos.y() = y; // Update the elapsed time with a rough estimate. m_elapsed_time += ((len == 0) ? std::abs(e) : len) / m_current_feedrate * 60.f; @@ -166,42 +199,42 @@ public: return *this; } - Writer& extrude_explicit(const WipeTower::xy &dest, float e, float f = 0.f, bool record_length = false) - { return extrude_explicit(dest.x, dest.y, e, f, record_length); } + WipeTowerWriter& extrude_explicit(const Vec2f &dest, float e, float f = 0.f, bool record_length = false, bool limit_volumetric_flow = true) + { return extrude_explicit(dest.x(), dest.y(), e, f, record_length); } // Travel to a new XY position. f=0 means use the current value. - Writer& travel(float x, float y, float f = 0.f) + WipeTowerWriter& travel(float x, float y, float f = 0.f) { return extrude_explicit(x, y, 0.f, f); } - Writer& travel(const WipeTower::xy &dest, float f = 0.f) - { return extrude_explicit(dest.x, dest.y, 0.f, f); } + WipeTowerWriter& travel(const Vec2f &dest, float f = 0.f) + { return extrude_explicit(dest.x(), dest.y(), 0.f, f); } // Extrude a line from current position to x, y with the extrusion amount given by m_extrusion_flow. - Writer& extrude(float x, float y, float f = 0.f) + WipeTowerWriter& extrude(float x, float y, float f = 0.f) { - float dx = x - m_current_pos.x; - float dy = y - m_current_pos.y; + float dx = x - m_current_pos.x(); + float dy = y - m_current_pos.y(); return extrude_explicit(x, y, sqrt(dx*dx+dy*dy) * m_extrusion_flow, f, true); } - Writer& extrude(const WipeTower::xy &dest, const float f = 0.f) - { return extrude(dest.x, dest.y, f); } + WipeTowerWriter& extrude(const Vec2f &dest, const float f = 0.f) + { return extrude(dest.x(), dest.y(), f); } - Writer& rectangle(const WipeTower::xy& ld,float width,float height,const float f = 0.f) + WipeTowerWriter& rectangle(const Vec2f& ld,float width,float height,const float f = 0.f) { - WipeTower::xy corners[4]; + Vec2f corners[4]; corners[0] = ld; - corners[1] = WipeTower::xy(ld,width,0.f); - corners[2] = WipeTower::xy(ld,width,height); - corners[3] = WipeTower::xy(ld,0.f,height); + corners[1] = ld + Vec2f(width,0.f); + corners[2] = ld + Vec2f(width,height); + corners[3] = ld + Vec2f(0.f,height); int index_of_closest = 0; - if (x()-ld.x > ld.x+width-x()) // closer to the right + if (x()-ld.x() > ld.x()+width-x()) // closer to the right index_of_closest = 1; - if (y()-ld.y > ld.y+height-y()) // closer to the top + if (y()-ld.y() > ld.y()+height-y()) // closer to the top index_of_closest = (index_of_closest==0 ? 3 : 2); - travel(corners[index_of_closest].x, y()); // travel to the closest corner - travel(x(),corners[index_of_closest].y); + travel(corners[index_of_closest].x(), y()); // travel to the closest corner + travel(x(),corners[index_of_closest].y()); int i = index_of_closest; do { @@ -212,7 +245,7 @@ public: return (*this); } - Writer& load(float e, float f = 0.f) + WipeTowerWriter& load(float e, float f = 0.f) { if (e == 0.f && (f == 0.f || f == m_current_feedrate)) return *this; @@ -224,29 +257,29 @@ public: m_gcode += "\n"; return *this; } - - // Derectract while moving in the X direction. - // If |x| > 0, the feed rate relates to the x distance, - // otherwise the feed rate relates to the e distance. - Writer& load_move_x(float x, float e, float f = 0.f) - { return extrude_explicit(x, m_current_pos.y, e, f); } - Writer& retract(float e, float f = 0.f) + WipeTowerWriter& retract(float e, float f = 0.f) { return load(-e, f); } // Loads filament while also moving towards given points in x-axis (x feedrate is limited by cutting the distance short if necessary) - Writer& load_move_x_advanced(float farthest_x, float loading_dist, float loading_speed, float max_x_speed = 50.f) + WipeTowerWriter& load_move_x_advanced(float farthest_x, float loading_dist, float loading_speed, float max_x_speed = 50.f) { - float time = std::abs(loading_dist / loading_speed); - float x_speed = std::min(max_x_speed, std::abs(farthest_x - x()) / time); - float feedrate = 60.f * std::hypot(x_speed, loading_speed); + float time = std::abs(loading_dist / loading_speed); // time that the move must take + float x_distance = std::abs(farthest_x - x()); // max x-distance that we can travel + float x_speed = x_distance / time; // x-speed to do it in that time - float end_point = x() + (farthest_x > x() ? 1.f : -1.f) * x_speed * time; - return extrude_explicit(end_point, y(), loading_dist, feedrate); + if (x_speed > max_x_speed) { + // Necessary x_speed is too high - we must shorten the distance to achieve max_x_speed and still respect the time. + x_distance = max_x_speed * time; + x_speed = max_x_speed; + } + + float end_point = x() + (farthest_x > x() ? 1.f : -1.f) * x_distance; + return extrude_explicit(end_point, y(), loading_dist, x_speed * 60.f, false, false); } // Elevate the extruder head above the current print_z position. - Writer& z_hop(float hop, float f = 0.f) + WipeTowerWriter& z_hop(float hop, float f = 0.f) { m_gcode += std::string("G1") + set_format_Z(m_current_z + hop); if (f != 0 && f != m_current_feedrate) @@ -256,39 +289,36 @@ public: } // Lower the extruder head back to the current print_z position. - Writer& z_hop_reset(float f = 0.f) + WipeTowerWriter& z_hop_reset(float f = 0.f) { return z_hop(0, f); } // Move to x1, +y_increment, // extrude quickly amount e to x2 with feed f. - Writer& ram(float x1, float x2, float dy, float e0, float e, float f) + WipeTowerWriter& ram(float x1, float x2, float dy, float e0, float e, float f) { - extrude_explicit(x1, m_current_pos.y + dy, e0, f, true); - extrude_explicit(x2, m_current_pos.y, e, 0.f, true); + extrude_explicit(x1, m_current_pos.y() + dy, e0, f, true, false); + extrude_explicit(x2, m_current_pos.y(), e, 0.f, true, false); return *this; } // Let the end of the pulled out filament cool down in the cooling tube // by moving up and down and moving the print head left / right // at the current Y position to spread the leaking material. - Writer& cool(float x1, float x2, float e1, float e2, float f) + WipeTowerWriter& cool(float x1, float x2, float e1, float e2, float f) { - extrude_explicit(x1, m_current_pos.y, e1, f); - extrude_explicit(x2, m_current_pos.y, e2); + extrude_explicit(x1, m_current_pos.y(), e1, f, false, false); + extrude_explicit(x2, m_current_pos.y(), e2, false, false); return *this; } - Writer& set_tool(int tool) + WipeTowerWriter& set_tool(int tool) { - char buf[64]; - sprintf(buf, "T%d\n", tool); - m_gcode += buf; m_current_tool = tool; return *this; } // Set extruder temperature, don't wait by default. - Writer& set_extruder_temp(int temperature, bool wait = false) + WipeTowerWriter& set_extruder_temp(int temperature, bool wait = false) { char buf[128]; sprintf(buf, "M%d S%d\n", wait ? 109 : 104, temperature); @@ -297,7 +327,7 @@ public: }; // Wait for a period of time (seconds). - Writer& wait(float time) + WipeTowerWriter& wait(float time) { if (time==0) return *this; @@ -308,7 +338,7 @@ public: }; // Set speed factor override percentage. - Writer& speed_override(int speed) + WipeTowerWriter& speed_override(int speed) { char buf[128]; sprintf(buf, "M220 S%d\n", speed); @@ -317,21 +347,21 @@ public: }; // Let the firmware back up the active speed override value. - Writer& speed_override_backup() + WipeTowerWriter& speed_override_backup() { m_gcode += "M220 B\n"; return *this; }; // Let the firmware restore the active speed override value. - Writer& speed_override_restore() + WipeTowerWriter& speed_override_restore() { m_gcode += "M220 R\n"; return *this; }; // Set digital trimpot motor - Writer& set_extruder_trimpot(int current) + WipeTowerWriter& set_extruder_trimpot(int current) { char buf[128]; if (m_gcode_flavor == gcfRepRap) @@ -342,20 +372,20 @@ public: return *this; }; - Writer& flush_planner_queue() + WipeTowerWriter& flush_planner_queue() { m_gcode += "G4 S0\n"; return *this; } // Reset internal extruder counter. - Writer& reset_extruder() + WipeTowerWriter& reset_extruder() { m_gcode += "G92 E0\n"; return *this; } - Writer& comment_with_value(const char *comment, int value) + WipeTowerWriter& comment_with_value(const char *comment, int value) { char strvalue[64]; sprintf(strvalue, "%d", value); @@ -364,7 +394,7 @@ public: }; - Writer& set_fan(unsigned int speed) + WipeTowerWriter& set_fan(unsigned int speed) { if (speed == m_last_fan_speed) return *this; @@ -382,18 +412,11 @@ public: return *this; } - Writer& comment_material(WipeTowerPrusaMM::material_type material) - { - m_gcode += "; material : "; - m_gcode += WipeTowerPrusaMM::to_string(material) + "\n"; - return *this; - }; - - Writer& append(const char *text) { m_gcode += text; return *this; } + WipeTowerWriter& append(const std::string& text) { m_gcode += text; return *this; } private: - WipeTower::xy m_start_pos; - WipeTower::xy m_current_pos; + Vec2f m_start_pos; + Vec2f m_current_pos; float m_current_z; float m_current_feedrate; unsigned int m_current_tool; @@ -412,19 +435,20 @@ private: const float m_default_analyzer_line_width; float m_used_filament_length = 0.f; GCodeFlavor m_gcode_flavor; + const std::vector& m_filpar; std::string set_format_X(float x) { char buf[64]; sprintf(buf, " X%.3f", x); - m_current_pos.x = x; + m_current_pos.x() = x; return buf; } std::string set_format_Y(float y) { char buf[64]; sprintf(buf, " Y%.3f", y); - m_current_pos.y = y; + m_current_pos.y() = y; return buf; } @@ -447,58 +471,90 @@ private: return buf; } - Writer& operator=(const Writer &rhs); -}; // class Writer - -}; // namespace PrusaMultiMaterial + WipeTowerWriter& operator=(const WipeTowerWriter &rhs); +}; // class WipeTowerWriter -WipeTowerPrusaMM::material_type WipeTowerPrusaMM::parse_material(const char *name) +WipeTower::WipeTower(const PrintConfig& config, const std::vector>& wiping_matrix, size_t initial_tool) : + m_semm(config.single_extruder_multi_material.value), + m_wipe_tower_pos(config.wipe_tower_x, config.wipe_tower_y), + m_wipe_tower_width(config.wipe_tower_width), + m_wipe_tower_rotation_angle(config.wipe_tower_rotation_angle), + m_y_shift(0.f), + m_z_pos(0.f), + m_is_first_layer(false), + m_bridging(config.wipe_tower_bridging), + m_gcode_flavor(config.gcode_flavor), + m_current_tool(initial_tool), + wipe_volumes(wiping_matrix) { - if (strcasecmp(name, "PLA") == 0) - return PLA; - if (strcasecmp(name, "ABS") == 0) - return ABS; - if (strcasecmp(name, "PET") == 0) - return PET; - if (strcasecmp(name, "HIPS") == 0) - return HIPS; - if (strcasecmp(name, "FLEX") == 0) - return FLEX; - if (strcasecmp(name, "SCAFF") == 0) - return SCAFF; - if (strcasecmp(name, "EDGE") == 0) - return EDGE; - if (strcasecmp(name, "NGEN") == 0) - return NGEN; - if (strcasecmp(name, "PVA") == 0) - return PVA; - if (strcasecmp(name, "PC") == 0) - return PC; - return INVALID; + // If this is a single extruder MM printer, we will use all the SE-specific config values. + // Otherwise, the defaults will be used to turn off the SE stuff. + if (m_semm) { + m_cooling_tube_retraction = config.cooling_tube_retraction; + m_cooling_tube_length = config.cooling_tube_length; + m_parking_pos_retraction = config.parking_pos_retraction; + m_extra_loading_move = config.extra_loading_move; + m_set_extruder_trimpot = config.high_current_on_filament_swap; + } + // Calculate where the priming lines should be - very naive test not detecting parallelograms or custom shapes + const std::vector& bed_points = config.bed_shape.values; + m_bed_shape = (bed_points.size() == 4 ? RectangularBed : CircularBed); + m_bed_width = BoundingBoxf(bed_points).size().x(); } -std::string WipeTowerPrusaMM::to_string(material_type material) + + +void WipeTower::set_extruder(size_t idx, const PrintConfig& config) { - switch (material) { - case PLA: return "PLA"; - case ABS: return "ABS"; - case PET: return "PET"; - case HIPS: return "HIPS"; - case FLEX: return "FLEX"; - case SCAFF: return "SCAFF"; - case EDGE: return "EDGE"; - case NGEN: return "NGEN"; - case PVA: return "PVA"; - case PC: return "PC"; - case INVALID: - default: return "INVALID"; - } + //while (m_filpar.size() < idx+1) // makes sure the required element is in the vector + m_filpar.push_back(FilamentParameters()); + + m_filpar[idx].material = config.filament_type.get_at(idx); + m_filpar[idx].temperature = config.temperature.get_at(idx); + m_filpar[idx].first_layer_temperature = config.first_layer_temperature.get_at(idx); + + // If this is a single extruder MM printer, we will use all the SE-specific config values. + // Otherwise, the defaults will be used to turn off the SE stuff. + if (m_semm) { + m_filpar[idx].loading_speed = config.filament_loading_speed.get_at(idx); + m_filpar[idx].loading_speed_start = config.filament_loading_speed_start.get_at(idx); + m_filpar[idx].unloading_speed = config.filament_unloading_speed.get_at(idx); + m_filpar[idx].unloading_speed_start = config.filament_unloading_speed_start.get_at(idx); + m_filpar[idx].delay = config.filament_toolchange_delay.get_at(idx); + m_filpar[idx].cooling_moves = config.filament_cooling_moves.get_at(idx); + m_filpar[idx].cooling_initial_speed = config.filament_cooling_initial_speed.get_at(idx); + m_filpar[idx].cooling_final_speed = config.filament_cooling_final_speed.get_at(idx); + } + + m_filpar[idx].filament_area = float((M_PI/4.f) * pow(config.filament_diameter.get_at(idx), 2)); // all extruders are assumed to have the same filament diameter at this point + float nozzle_diameter = config.nozzle_diameter.get_at(idx); + m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM + + float max_vol_speed = config.filament_max_volumetric_speed.get_at(idx); + if (max_vol_speed!= 0.f) + m_filpar[idx].max_e_speed = (max_vol_speed / filament_area()); + + m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter + + if (m_semm) { + std::istringstream stream{config.filament_ramming_parameters.get_at(idx)}; + float speed = 0.f; + stream >> m_filpar[idx].ramming_line_width_multiplicator >> m_filpar[idx].ramming_step_multiplicator; + m_filpar[idx].ramming_line_width_multiplicator /= 100; + m_filpar[idx].ramming_step_multiplicator /= 100; + while (stream >> speed) + m_filpar[idx].ramming_speed.push_back(speed); + } + + m_used_filament_length.resize(std::max(m_used_filament_length.size(), idx + 1)); // makes sure that the vector is big enough so we don't have to check later } + + // Returns gcode to prime the nozzles at the front edge of the print bed. -WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( +std::vector WipeTower::prime( // print_z of the first layer. float first_layer_height, // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. @@ -515,26 +571,39 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( // therefore the homing position is shifted inside the bed by 0.2 in the firmware to [0.2, -2.0]. // box_coordinates cleaning_box(xy(0.5f, - 1.5f), m_wipe_tower_width, wipe_area); - const float prime_section_width = std::min(240.f / tools.size(), 60.f); - box_coordinates cleaning_box(xy(5.f, 0.01f + m_perimeter_width/2.f), prime_section_width, 100.f); + float prime_section_width = std::min(0.9f * m_bed_width / tools.size(), 60.f); + box_coordinates cleaning_box(Vec2f(0.02f * m_bed_width, 0.01f + m_perimeter_width/2.f), prime_section_width, 100.f); + // In case of a circular bed, place it so it goes across the diameter and hope it will fit + if (m_bed_shape == CircularBed) + cleaning_box.translate(-m_bed_width/2 + m_bed_width * 0.03f, -m_bed_width * 0.12f); - PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width, m_gcode_flavor); - writer.set_extrusion_flow(m_extrusion_flow) - .set_z(m_z_pos) - .set_initial_tool(m_current_tool) - .append(";--------------------\n" - "; CP PRIMING START\n") - .append(";--------------------\n"); - if (m_retain_speed_override) - writer.speed_override_backup(); - writer.speed_override(100); - - writer.set_initial_position(xy(0.f, 0.f)) // Always move to the starting position - .travel(cleaning_box.ld, 7200); - if (m_set_extruder_trimpot) - writer.set_extruder_trimpot(750); // Increase the extruder driver current to allow fast ramming. + std::vector results; + // Iterate over all priming toolchanges and push respective ToolChangeResults into results vector. for (size_t idx_tool = 0; idx_tool < tools.size(); ++ idx_tool) { + int old_tool = m_current_tool; + + WipeTowerWriter writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); + writer.set_extrusion_flow(m_extrusion_flow) + .set_z(m_z_pos) + .set_initial_tool(m_current_tool); + + // This is the first toolchange - initiate priming + if (idx_tool == 0) { + writer.append(";--------------------\n" + "; CP PRIMING START\n") + .append(";--------------------\n") + .speed_override_backup() + .speed_override(100) + .set_initial_position(Vec2f::Zero()) // Always move to the starting position + .travel(cleaning_box.ld, 7200); + if (m_set_extruder_trimpot) + writer.set_extruder_trimpot(750); // Increase the extruder driver current to allow fast ramming. + } + else + writer.set_initial_position(results.back().end_pos); + + unsigned int tool = tools[idx_tool]; m_left_to_right = true; toolchange_Change(writer, tool, m_filpar[tool].material); // Select the tool, set a speed override for soluble and flex materials. @@ -547,53 +616,63 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( //writer.travel(writer.x(), writer.y() + m_perimeter_width, 7200); toolchange_Wipe(writer, cleaning_box , 20.f); box_coordinates box = cleaning_box; - box.translate(0.f, writer.y() - cleaning_box.ld.y + m_perimeter_width); + box.translate(0.f, writer.y() - cleaning_box.ld.y() + m_perimeter_width); toolchange_Unload(writer, box , m_filpar[m_current_tool].material, m_filpar[tools[idx_tool + 1]].first_layer_temperature); cleaning_box.translate(prime_section_width, 0.f); writer.travel(cleaning_box.ld, 7200); } ++ m_num_tool_changes; + + + // Ask our writer about how much material was consumed: + if (m_current_tool < m_used_filament_length.size()) + m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + + ToolChangeResult result; + result.priming = true; + result.initial_tool = old_tool; + result.new_tool = m_current_tool; + result.print_z = this->m_z_pos; + result.layer_height = this->m_layer_height; + result.gcode = writer.gcode(); + result.elapsed_time = writer.elapsed_time(); + result.extrusions = writer.extrusions(); + result.start_pos = writer.start_pos_rotated(); + result.end_pos = writer.pos(); + + results.push_back(std::move(result)); + + // This is the last priming toolchange - finish priming + if (idx_tool+1 == tools.size()) { + // Reset the extruder current to a normal value. + if (m_set_extruder_trimpot) + writer.set_extruder_trimpot(550); + writer.speed_override_restore() + .feedrate(6000) + .flush_planner_queue() + .reset_extruder() + .append("; CP PRIMING END\n" + ";------------------\n" + "\n\n"); + } } m_old_temperature = -1; // If the priming is turned off in config, the temperature changing commands will not actually appear // in the output gcode - we should not remember emitting them (we will output them twice in the worst case) - // Reset the extruder current to a normal value. - if (m_set_extruder_trimpot) - writer.set_extruder_trimpot(550); - if (m_retain_speed_override) - writer.speed_override_restore(); - writer.feedrate(6000) - .flush_planner_queue() - .reset_extruder() - .append("; CP PRIMING END\n" - ";------------------\n" - "\n\n"); - // so that tool_change() will know to extrude the wipe tower brim: m_print_brim = true; - // Ask our writer about how much material was consumed: - if (m_current_tool < m_used_filament_length.size()) - m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); - - ToolChangeResult result; - result.priming = true; - result.print_z = this->m_z_pos; - result.layer_height = this->m_layer_height; - result.gcode = writer.gcode(); - result.elapsed_time = writer.elapsed_time(); - result.extrusions = writer.extrusions(); - result.start_pos = writer.start_pos_rotated(); - result.end_pos = writer.pos_rotated(); - return result; + return results; } -WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, bool last_in_layer) +WipeTower::ToolChangeResult WipeTower::tool_change(unsigned int tool, bool last_in_layer) { if ( m_print_brim ) return toolchange_Brim(); + int old_tool = m_current_tool; + float wipe_area = 0.f; bool last_change_in_layer = false; float wipe_volume = 0.f; @@ -615,26 +694,28 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo } box_coordinates cleaning_box( - xy(m_perimeter_width / 2.f, m_perimeter_width / 2.f), + Vec2f(m_perimeter_width / 2.f, m_perimeter_width / 2.f), m_wipe_tower_width - m_perimeter_width, (tool != (unsigned int)(-1) ? /*m_layer_info->depth*/wipe_area+m_depth_traversed-0.5*m_perimeter_width : m_wipe_tower_depth-m_perimeter_width)); - PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width, m_gcode_flavor); + WipeTowerWriter writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_initial_tool(m_current_tool) .set_y_shift(m_y_shift + (tool!=(unsigned int)(-1) && (m_current_shape == SHAPE_REVERSED && !m_peters_wipe_tower) ? m_layer_info->depth - m_layer_info->toolchanges_depth(): 0.f)) .append(";--------------------\n" "; CP TOOLCHANGE START\n") - .comment_with_value(" toolchange #", m_num_tool_changes + 1) // the number is zero-based - .comment_material(m_filpar[m_current_tool].material) - .append(";--------------------\n"); - if (m_retain_speed_override) - writer.speed_override_backup(); + .comment_with_value(" toolchange #", m_num_tool_changes + 1); // the number is zero-based + + if (tool != (unsigned)(-1)) + writer.append(std::string("; material : " + (m_current_tool < m_filpar.size() ? m_filpar[m_current_tool].material : "(NONE)") + " -> " + m_filpar[tool].material + "\n").c_str()) + .append(";--------------------\n"); + + writer.speed_override_backup(); writer.speed_override(100); - xy initial_position = cleaning_box.ld + WipeTower::xy(0.f,m_depth_traversed); + Vec2f initial_position = cleaning_box.ld + Vec2f(0.f, m_depth_traversed); writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); // Increase the extruder driver current to allow fast ramming. @@ -647,7 +728,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo m_is_first_layer ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature); toolchange_Change(writer, tool, m_filpar[tool].material); // Change the tool, set a speed override for soluble and flex materials. toolchange_Load(writer, cleaning_box); - writer.travel(writer.x(),writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road + writer.travel(writer.x(), writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road toolchange_Wipe(writer, cleaning_box, wipe_volume); // Wipe the newly loaded filament until the end of the assigned wipe area. ++ m_num_tool_changes; } else @@ -658,9 +739,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo if (last_change_in_layer) {// draw perimeter line writer.set_y_shift(m_y_shift); if (m_peters_wipe_tower) - writer.rectangle(WipeTower::xy(0.f, 0.f),m_layer_info->depth + 3*m_perimeter_width,m_wipe_tower_depth); + writer.rectangle(Vec2f::Zero(), m_layer_info->depth + 3*m_perimeter_width, m_wipe_tower_depth); else { - writer.rectangle(WipeTower::xy(0.f, 0.f),m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); + writer.rectangle(Vec2f::Zero(), m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); if (layer_finished()) { // no finish_layer will be called, we must wipe the nozzle writer.travel(writer.x()> m_wipe_tower_width / 2.f ? 0.f : m_wipe_tower_width, writer.y()); } @@ -669,8 +750,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo if (m_set_extruder_trimpot) writer.set_extruder_trimpot(550); // Reset the extruder current to a normal value. - if (m_retain_speed_override) - writer.speed_override_restore(); + writer.speed_override_restore(); writer.feedrate(6000) .flush_planner_queue() .reset_extruder() @@ -684,6 +764,8 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo ToolChangeResult result; result.priming = false; + result.initial_tool = old_tool; + result.new_tool = m_current_tool; result.print_z = this->m_z_pos; result.layer_height = this->m_layer_height; result.gcode = writer.gcode(); @@ -694,25 +776,27 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo return result; } -WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, float y_offset) +WipeTower::ToolChangeResult WipeTower::toolchange_Brim(bool sideOnly, float y_offset) { + int old_tool = m_current_tool; + const box_coordinates wipeTower_box( - WipeTower::xy(0.f, 0.f), + Vec2f::Zero(), m_wipe_tower_width, m_wipe_tower_depth); - PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width, m_gcode_flavor); + WipeTowerWriter writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); writer.set_extrusion_flow(m_extrusion_flow * 1.1f) .set_z(m_z_pos) // Let the writer know the current Z position as a base for Z-hop. .set_initial_tool(m_current_tool) .append(";-------------------------------------\n" "; CP WIPE TOWER FIRST LAYER BRIM START\n"); - xy initial_position = wipeTower_box.lu - xy(m_perimeter_width * 6.f, 0); + Vec2f initial_position = wipeTower_box.lu - Vec2f(m_perimeter_width * 6.f, 0); writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); - writer.extrude_explicit(wipeTower_box.ld - xy(m_perimeter_width * 6.f, 0), // Prime the extruder left of the wipe tower. - 1.5f * m_extrusion_flow * (wipeTower_box.lu.y - wipeTower_box.ld.y), 2400); + writer.extrude_explicit(wipeTower_box.ld - Vec2f(m_perimeter_width * 6.f, 0), // Prime the extruder left of the wipe tower. + 1.5f * m_extrusion_flow * (wipeTower_box.lu.y() - wipeTower_box.ld.y()), 2400); // The tool is supposed to be active and primed at the time when the wipe tower brim is extruded. // Extrude 4 rounds of a brim around the future wipe tower. @@ -738,6 +822,8 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo ToolChangeResult result; result.priming = false; + result.initial_tool = old_tool; + result.new_tool = m_current_tool; result.print_z = this->m_z_pos; result.layer_height = this->m_layer_height; result.gcode = writer.gcode(); @@ -751,14 +837,14 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. -void WipeTowerPrusaMM::toolchange_Unload( - PrusaMultiMaterial::Writer &writer, +void WipeTower::toolchange_Unload( + WipeTowerWriter &writer, const box_coordinates &cleaning_box, - const material_type current_material, + const std::string& current_material, const int new_temperature) { - float xl = cleaning_box.ld.x + 1.f * m_perimeter_width; - float xr = cleaning_box.rd.x - 1.f * m_perimeter_width; + float xl = cleaning_box.ld.x() + 1.f * m_perimeter_width; + float xr = cleaning_box.rd.x() - 1.f * m_perimeter_width; const float line_width = m_perimeter_width * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing; // spacing between lines in mm @@ -771,7 +857,7 @@ void WipeTowerPrusaMM::toolchange_Unload( float remaining = xr - xl ; // keeps track of distance to the next turnaround float e_done = 0; // measures E move done from each segment - writer.travel(xl, cleaning_box.ld.y + m_depth_traversed + y_step/2.f ); // move to starting position + writer.travel(xl, cleaning_box.ld.y() + m_depth_traversed + y_step/2.f ); // move to starting position // if the ending point of the ram would end up in mid air, align it with the end of the wipe tower: if (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion )) { @@ -817,14 +903,16 @@ void WipeTowerPrusaMM::toolchange_Unload( } } + writer.disable_linear_advance(); + // now the ramming itself: while (i < m_filpar[m_current_tool].ramming_speed.size()) { const float x = volume_to_length(m_filpar[m_current_tool].ramming_speed[i] * 0.25f, line_width, m_layer_height); - const float e = m_filpar[m_current_tool].ramming_speed[i] * 0.25f / Filament_Area; // transform volume per sec to E move; + const float e = m_filpar[m_current_tool].ramming_speed[i] * 0.25f / filament_area(); // transform volume per sec to E move; const float dist = std::min(x - e_done, remaining); // distance to travel for either the next 0.25s, or to the next turnaround const float actual_time = dist/x * 0.25; - writer.ram(writer.x(), writer.x() + (m_left_to_right ? 1.f : -1.f) * dist, 0, 0, e * (dist / x), std::hypot(dist, e * (dist / x)) / (actual_time / 60.)); + writer.ram(writer.x(), writer.x() + (m_left_to_right ? 1.f : -1.f) * dist, 0, 0, e * (dist / x), dist / (actual_time / 60.)); remaining -= dist; if (remaining < WT_EPSILON) { // we reached a turning point @@ -838,25 +926,27 @@ void WipeTowerPrusaMM::toolchange_Unload( e_done = 0; } } - WipeTower::xy end_of_ramming(writer.x(),writer.y()); + Vec2f end_of_ramming(writer.x(),writer.y()); writer.change_analyzer_line_width(m_perimeter_width); // so the next lines are not affected by ramming_line_width_multiplier // Retraction: float old_x = writer.x(); float turning_point = (!m_left_to_right ? xl : xr ); - float total_retraction_distance = m_cooling_tube_retraction + m_cooling_tube_length/2.f - 15.f; // the 15mm is reserved for the first part after ramming - writer.suppress_preview() - .retract(15.f, m_filpar[m_current_tool].unloading_speed_start * 60.f) // feedrate 5000mm/min = 83mm/s - .retract(0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed * 60.f) - .retract(0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed * 60.f) - .retract(0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed * 60.f) - - /*.load_move_x_advanced(turning_point, -15.f, 83.f, 50.f) // this is done at fixed speed - .load_move_x_advanced(old_x, -0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed) - .load_move_x_advanced(turning_point, -0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed) - .load_move_x_advanced(old_x, -0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed) - .travel(old_x, writer.y()) // in case previous move was shortened to limit feedrate*/ - .resume_preview(); + if (m_semm && (m_cooling_tube_retraction != 0 || m_cooling_tube_length != 0)) { + float total_retraction_distance = m_cooling_tube_retraction + m_cooling_tube_length/2.f - 15.f; // the 15mm is reserved for the first part after ramming + writer.suppress_preview() + .retract(15.f, m_filpar[m_current_tool].unloading_speed_start * 60.f) // feedrate 5000mm/min = 83mm/s + .retract(0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed * 60.f) + .retract(0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed * 60.f) + .retract(0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed * 60.f) + + /*.load_move_x_advanced(turning_point, -15.f, 83.f, 50.f) // this is done at fixed speed + .load_move_x_advanced(old_x, -0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed) + .load_move_x_advanced(turning_point, -0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed) + .load_move_x_advanced(old_x, -0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed) + .travel(old_x, writer.y()) // in case previous move was shortened to limit feedrate*/ + .resume_preview(); + } if (new_temperature != 0 && (new_temperature != m_old_temperature || m_is_first_layer) ) { // Set the extruder temperature, but don't wait. // If the required temperature is the same as last time, don't emit the M104 again (if user adjusted the value, it would be reset) // However, always change temperatures on the first layer (this is to avoid issues with priming lines turned off). @@ -891,73 +981,72 @@ void WipeTowerPrusaMM::toolchange_Unload( // this is to align ramming and future wiping extrusions, so the future y-steps can be uniform from the start: // the perimeter_width will later be subtracted, it is there to not load while moving over just extruded material - writer.travel(end_of_ramming.x, end_of_ramming.y + (y_step/m_extra_spacing-m_perimeter_width) / 2.f + m_perimeter_width, 2400.f); + writer.travel(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing-m_perimeter_width) / 2.f + m_perimeter_width, 2400.f); writer.resume_preview() .flush_planner_queue(); } // Change the tool, set a speed override for soluble and flex materials. -void WipeTowerPrusaMM::toolchange_Change( - PrusaMultiMaterial::Writer &writer, +void WipeTower::toolchange_Change( + WipeTowerWriter &writer, const unsigned int new_tool, - material_type new_material) + const std::string& new_material) { // Ask the writer about how much of the old filament we consumed: if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); - // Speed override for the material. Go slow for flex and soluble materials. - int speed_override; - switch (new_material) { - case PVA: speed_override = (m_z_pos < 0.80f) ? 60 : 80; break; - case SCAFF: speed_override = 35; break; - case FLEX: speed_override = 35; break; - default: speed_override = 100; - } - writer.set_tool(new_tool); - if (m_retain_speed_override) - assert(speed_override == 100); - else - writer.speed_override(speed_override); + // This is where we want to place the custom gcodes. We will use placeholders for this. + // These will be substituted by the actual gcodes when the gcode is generated. + writer.append("[end_filament_gcode]\n"); + writer.append("[toolchange_gcode]\n"); + + // The toolchange Tn command will be inserted later, only in case that the user does + // not provide a custom toolchange gcode. + writer.set_tool(new_tool); // This outputs nothing, the writer just needs to know the tool has changed. + writer.append("[start_filament_gcode]\n"); + writer.flush_planner_queue(); m_current_tool = new_tool; } -void WipeTowerPrusaMM::toolchange_Load( - PrusaMultiMaterial::Writer &writer, +void WipeTower::toolchange_Load( + WipeTowerWriter &writer, const box_coordinates &cleaning_box) -{ - float xl = cleaning_box.ld.x + m_perimeter_width * 0.75f; - float xr = cleaning_box.rd.x - m_perimeter_width * 0.75f; - float oldx = writer.x(); // the nozzle is in place to do the first wiping moves, we will remember the position +{ + if (m_semm && (m_parking_pos_retraction != 0 || m_extra_loading_move != 0)) { + float xl = cleaning_box.ld.x() + m_perimeter_width * 0.75f; + float xr = cleaning_box.rd.x() - m_perimeter_width * 0.75f; + float oldx = writer.x(); // the nozzle is in place to do the first wiping moves, we will remember the position - // Load the filament while moving left / right, so the excess material will not create a blob at a single position. - float turning_point = ( oldx-xl < xr-oldx ? xr : xl ); - float edist = m_parking_pos_retraction+m_extra_loading_move; + // Load the filament while moving left / right, so the excess material will not create a blob at a single position. + float turning_point = ( oldx-xl < xr-oldx ? xr : xl ); + float edist = m_parking_pos_retraction+m_extra_loading_move; - writer.append("; CP TOOLCHANGE LOAD\n") - .suppress_preview() - /*.load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed) // Acceleration - .load_move_x_advanced(oldx, 0.5f * edist, m_filpar[m_current_tool].loading_speed) // Fast phase - .load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed) // Slowing down - .load_move_x_advanced(oldx, 0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed) // Super slow*/ + writer.append("; CP TOOLCHANGE LOAD\n") + .suppress_preview() + /*.load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed) // Acceleration + .load_move_x_advanced(oldx, 0.5f * edist, m_filpar[m_current_tool].loading_speed) // Fast phase + .load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed) // Slowing down + .load_move_x_advanced(oldx, 0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed) // Super slow*/ - .load(0.2f * edist, 60.f * m_filpar[m_current_tool].loading_speed_start) - .load_move_x_advanced(turning_point, 0.7f * edist, m_filpar[m_current_tool].loading_speed) // Fast phase - .load_move_x_advanced(oldx, 0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed) // Super slow*/ + .load(0.2f * edist, 60.f * m_filpar[m_current_tool].loading_speed_start) + .load_move_x_advanced(turning_point, 0.7f * edist, m_filpar[m_current_tool].loading_speed) // Fast phase + .load_move_x_advanced(oldx, 0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed) // Super slow*/ - .travel(oldx, writer.y()) // in case last move was shortened to limit x feedrate - .resume_preview(); + .travel(oldx, writer.y()) // in case last move was shortened to limit x feedrate + .resume_preview(); - // Reset the extruder current to the normal value. - if (m_set_extruder_trimpot) - writer.set_extruder_trimpot(550); + // Reset the extruder current to the normal value. + if (m_set_extruder_trimpot) + writer.set_extruder_trimpot(550); + } } // Wipe the newly loaded filament until the end of the assigned wipe area. -void WipeTowerPrusaMM::toolchange_Wipe( - PrusaMultiMaterial::Writer &writer, +void WipeTower::toolchange_Wipe( + WipeTowerWriter &writer, const box_coordinates &cleaning_box, float wipe_volume) { @@ -965,8 +1054,8 @@ void WipeTowerPrusaMM::toolchange_Wipe( writer.set_extrusion_flow(m_extrusion_flow * (m_is_first_layer ? 1.18f : 1.f)) .append("; CP TOOLCHANGE WIPE\n"); float wipe_coeff = m_is_first_layer ? 0.5f : 1.f; - const float& xl = cleaning_box.ld.x; - const float& xr = cleaning_box.rd.x; + const float& xl = cleaning_box.ld.x(); + const float& xr = cleaning_box.rd.x(); // Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least // the ordered volume, even if it means violating the box. This can later be removed and simply @@ -997,7 +1086,7 @@ void WipeTowerPrusaMM::toolchange_Wipe( else writer.extrude(xl + (i % 4 == 1 ? 0 : 1.5*m_perimeter_width), writer.y(), wipe_speed * wipe_coeff); - if (writer.y()+EPSILON > cleaning_box.lu.y-0.5f*m_perimeter_width) + if (writer.y()+EPSILON > cleaning_box.lu.y()-0.5f*m_perimeter_width) break; // in case next line would not fit traversed_x -= writer.x(); @@ -1024,13 +1113,15 @@ void WipeTowerPrusaMM::toolchange_Wipe( -WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() +WipeTower::ToolChangeResult WipeTower::finish_layer() { // This should only be called if the layer is not finished yet. // Otherwise the caller would likely travel to the wipe tower in vain. assert(! this->layer_finished()); - PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width, m_gcode_flavor); + int old_tool = m_current_tool; + + WipeTowerWriter writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_initial_tool(m_current_tool) @@ -1042,7 +1133,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() // Slow down on the 1st layer. float speed_factor = m_is_first_layer ? 0.5f : 1.f; float current_depth = m_layer_info->depth - m_layer_info->toolchanges_depth(); - box_coordinates fill_box(xy(m_perimeter_width, m_depth_traversed + m_perimeter_width), + box_coordinates fill_box(Vec2f(m_perimeter_width, m_depth_traversed + m_perimeter_width), m_wipe_tower_width - 2 * m_perimeter_width, current_depth-m_perimeter_width); @@ -1056,44 +1147,44 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() else box.expand(-m_perimeter_width); } else i=2; // only draw the inner perimeter, outer has been already drawn by tool_change(...) - writer.rectangle(box.ld,box.rd.x-box.ld.x,box.ru.y-box.rd.y,2900*speed_factor); + writer.rectangle(box.ld, box.rd.x()-box.ld.x(), box.ru.y()-box.rd.y(), 2900*speed_factor); } // we are in one of the corners, travel to ld along the perimeter: - if (writer.x() > fill_box.ld.x+EPSILON) writer.travel(fill_box.ld.x,writer.y()); - if (writer.y() > fill_box.ld.y+EPSILON) writer.travel(writer.x(),fill_box.ld.y); + if (writer.x() > fill_box.ld.x()+EPSILON) writer.travel(fill_box.ld.x(),writer.y()); + if (writer.y() > fill_box.ld.y()+EPSILON) writer.travel(writer.x(),fill_box.ld.y()); if (m_is_first_layer && m_adhesion) { // Extrude a dense infill at the 1st layer to improve 1st layer adhesion of the wipe tower. box.expand(-m_perimeter_width/2.f); - int nsteps = int(floor((box.lu.y - box.ld.y) / (2*m_perimeter_width))); - float step = (box.lu.y - box.ld.y) / nsteps; - writer.travel(box.ld-xy(m_perimeter_width/2.f,m_perimeter_width/2.f)); + int nsteps = int(floor((box.lu.y() - box.ld.y()) / (2*m_perimeter_width))); + float step = (box.lu.y() - box.ld.y()) / nsteps; + writer.travel(box.ld - Vec2f(m_perimeter_width/2.f, m_perimeter_width/2.f)); if (nsteps >= 0) for (int i = 0; i < nsteps; ++i) { - writer.extrude(box.ld.x+m_perimeter_width/2.f, writer.y() + 0.5f * step); - writer.extrude(box.rd.x - m_perimeter_width / 2.f, writer.y()); - writer.extrude(box.rd.x - m_perimeter_width / 2.f, writer.y() + 0.5f * step); - writer.extrude(box.ld.x + m_perimeter_width / 2.f, writer.y()); + writer.extrude(box.ld.x()+m_perimeter_width/2.f, writer.y() + 0.5f * step); + writer.extrude(box.rd.x() - m_perimeter_width / 2.f, writer.y()); + writer.extrude(box.rd.x() - m_perimeter_width / 2.f, writer.y() + 0.5f * step); + writer.extrude(box.ld.x() + m_perimeter_width / 2.f, writer.y()); } - writer.travel(box.rd.x-m_perimeter_width/2.f,writer.y()); // wipe the nozzle + writer.travel(box.rd.x()-m_perimeter_width/2.f,writer.y()); // wipe the nozzle } else { // Extrude a sparse infill to support the material to be printed above. - const float dy = (fill_box.lu.y - fill_box.ld.y - m_perimeter_width); - const float left = fill_box.lu.x+2*m_perimeter_width; - const float right = fill_box.ru.x - 2 * m_perimeter_width; + const float dy = (fill_box.lu.y() - fill_box.ld.y() - m_perimeter_width); + const float left = fill_box.lu.x() + 2*m_perimeter_width; + const float right = fill_box.ru.x() - 2 * m_perimeter_width; if (dy > m_perimeter_width) { // Extrude an inverse U at the left of the region. - writer.travel(fill_box.ld + xy(m_perimeter_width * 2, 0.f)) - .extrude(fill_box.lu + xy(m_perimeter_width * 2, 0.f), 2900 * speed_factor); + writer.travel(fill_box.ld + Vec2f(m_perimeter_width * 2, 0.f)) + .extrude(fill_box.lu + Vec2f(m_perimeter_width * 2, 0.f), 2900 * speed_factor); const int n = 1+(right-left)/(m_bridging); const float dx = (right-left)/n; for (int i=1;i<=n;++i) { float x=left+dx*i; writer.travel(x,writer.y()); - writer.extrude(x,i%2 ? fill_box.rd.y : fill_box.ru.y); + writer.extrude(x,i%2 ? fill_box.rd.y() : fill_box.ru.y()); } writer.travel(left,writer.y(),7200); // wipes the nozzle before moving away from the wipe tower } @@ -1111,6 +1202,8 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() ToolChangeResult result; result.priming = false; + result.initial_tool = old_tool; + result.new_tool = m_current_tool; result.print_z = this->m_z_pos; result.layer_height = this->m_layer_height; result.gcode = writer.gcode(); @@ -1122,7 +1215,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() } // Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box -void WipeTowerPrusaMM::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, bool brim, float wipe_volume) +void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, bool brim, float wipe_volume) { assert(m_plan.empty() || m_plan.back().z <= z_par + WT_EPSILON); // refuses to add a layer below the last one @@ -1158,7 +1251,7 @@ void WipeTowerPrusaMM::plan_toolchange(float z_par, float layer_height_par, unsi -void WipeTowerPrusaMM::plan_tower() +void WipeTower::plan_tower() { // Calculate m_wipe_tower_depth (maximum depth for all the layers) and propagate depths downwards m_wipe_tower_depth = 0.f; @@ -1181,7 +1274,7 @@ void WipeTowerPrusaMM::plan_tower() } } -void WipeTowerPrusaMM::save_on_last_wipe() +void WipeTower::save_on_last_wipe() { for (m_layer_info=m_plan.begin();m_layer_infoz, m_layer_info->height, 0, m_layer_info->z == m_plan.front().z, m_layer_info->z == m_plan.back().z); @@ -1206,7 +1299,7 @@ void WipeTowerPrusaMM::save_on_last_wipe() // Processes vector m_plan and calls respective functions to generate G-code for the wipe tower // Resulting ToolChangeResults are appended into vector "result" -void WipeTowerPrusaMM::generate(std::vector> &result) +void WipeTower::generate(std::vector> &result) { if (m_plan.empty()) @@ -1224,10 +1317,20 @@ void WipeTowerPrusaMM::generate(std::vector layer_result; for (auto layer : m_plan) { @@ -1240,11 +1343,8 @@ void WipeTowerPrusaMM::generate(std::vectordepth < m_wipe_tower_depth - m_perimeter_width) m_y_shift = (m_wipe_tower_depth-m_layer_info->depth-m_perimeter_width)/2.f; - for (const auto &toolchange : layer.tool_changes) { - if (m_current_tool == (unsigned int)(-2)) - m_current_tool = toolchange.old_tool; + for (const auto &toolchange : layer.tool_changes) layer_result.emplace_back(tool_change(toolchange.new_tool, false)); - } if (! layer_finished()) { auto finish_layer_toolchange = finish_layer(); @@ -1252,7 +1352,7 @@ void WipeTowerPrusaMM::generate(std::vector -#include +#include #include -#include +#include +#include +#include + +#include "libslic3r/PrintConfig.hpp" + namespace Slic3r { -// A pure virtual WipeTower definition. +class WipeTowerWriter; + + + class WipeTower { public: - // Internal point class, to make the wipe tower independent from other slic3r modules. - // This is important for Prusa Research as we want to build the wipe tower post-processor independently from slic3r. - struct xy + struct Extrusion { - xy(float x = 0.f, float y = 0.f) : x(x), y(y) {} - xy(const xy& pos,float xp,float yp) : x(pos.x+xp), y(pos.y+yp) {} - xy operator+(const xy &rhs) const { xy out(*this); out.x += rhs.x; out.y += rhs.y; return out; } - xy operator-(const xy &rhs) const { xy out(*this); out.x -= rhs.x; out.y -= rhs.y; return out; } - xy& operator+=(const xy &rhs) { x += rhs.x; y += rhs.y; return *this; } - xy& operator-=(const xy &rhs) { x -= rhs.x; y -= rhs.y; return *this; } - bool operator==(const xy &rhs) const { return x == rhs.x && y == rhs.y; } - bool operator!=(const xy &rhs) const { return x != rhs.x || y != rhs.y; } - - // Rotate the point around center of the wipe tower about given angle (in degrees) - xy rotate(float width, float depth, float angle) const { - xy out(0,0); - float temp_x = x - width / 2.f; - float temp_y = y - depth / 2.f; - angle *= float(M_PI/180.); - out.x += temp_x * cos(angle) - temp_y * sin(angle) + width / 2.f; - out.y += temp_x * sin(angle) + temp_y * cos(angle) + depth / 2.f; - return out; - } - - // Rotate the point around origin about given angle in degrees - void rotate(float angle) { - float temp_x = x * cos(angle) - y * sin(angle); - y = x * sin(angle) + y * cos(angle); - x = temp_x; - } - - void translate(const xy& vect) { - x += vect.x; - y += vect.y; - } - - float x; - float y; - }; - - WipeTower() {} - virtual ~WipeTower() {} - - // Return the wipe tower position. - virtual const xy& position() const = 0; - - // Return the wipe tower width. - virtual float width() const = 0; - - // The wipe tower is finished, there should be no more tool changes or wipe tower prints. - virtual bool finished() const = 0; - - // Switch to a next layer. - virtual void set_layer( - // Print height of this layer. - float print_z, - // Layer height, used to calculate extrusion the rate. - float layer_height, - // Maximum number of tool changes on this layer or the layers below. - size_t max_tool_changes, - // Is this the first layer of the print? In that case print the brim first. - bool is_first_layer, - // Is this the last layer of the wipe tower? - bool is_last_layer) = 0; - - enum Purpose { - PURPOSE_MOVE_TO_TOWER, - PURPOSE_EXTRUDE, - PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE, - }; - - // Extrusion path of the wipe tower, for 3D preview of the generated tool paths. - struct Extrusion - { - Extrusion(const xy &pos, float width, unsigned int tool) : pos(pos), width(width), tool(tool) {} + Extrusion(const Vec2f &pos, float width, unsigned int tool) : pos(pos), width(width), tool(tool) {} // End position of this extrusion. - xy pos; + Vec2f pos; // Width of a squished extrusion, corrected for the roundings of the squished extrusions. // This is left zero if it is a travel move. float width; @@ -108,10 +43,10 @@ public: std::vector extrusions; // Initial position, at which the wipe tower starts its action. // At this position the extruder is loaded and there is no Z-hop applied. - xy start_pos; + Vec2f start_pos; // Last point, at which the normal G-code generator of Slic3r shall continue. // At this position the extruder is loaded and there is no Z-hop applied. - xy end_pos; + Vec2f end_pos; // Time elapsed over this tool change. // This is useful not only for the print time estimation, but also for the control of layer cooling. float elapsed_time; @@ -119,50 +54,318 @@ public: // Is this a priming extrusion? (If so, the wipe tower rotation & translation will not be applied later) bool priming; + // Initial tool + int initial_tool; + + // New tool + int new_tool; + // Sum the total length of the extrusion. float total_extrusion_length_in_plane() { float e_length = 0.f; for (size_t i = 1; i < this->extrusions.size(); ++ i) { const Extrusion &e = this->extrusions[i]; if (e.width > 0) { - xy v = e.pos - (&e - 1)->pos; - e_length += sqrt(v.x*v.x+v.y*v.y); + Vec2f v = e.pos - (&e - 1)->pos; + e_length += v.norm(); } } return e_length; } }; + // x -- x coordinates of wipe tower in mm ( left bottom corner ) + // y -- y coordinates of wipe tower in mm ( left bottom corner ) + // width -- width of wipe tower in mm ( default 60 mm - leave as it is ) + // wipe_area -- space available for one toolchange in mm + WipeTower(const PrintConfig& config, const std::vector>& wiping_matrix, size_t initial_tool); + virtual ~WipeTower() {} + + + // Set the extruder properties. + void set_extruder(size_t idx, const PrintConfig& config); + + // Appends into internal structure m_plan containing info about the future wipe tower + // to be used before building begins. The entries must be added ordered in z. + void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, bool brim, float wipe_volume = 0.f); + + // Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result" + void generate(std::vector> &result); + + float get_depth() const { return m_wipe_tower_depth; } + + + + // Switch to a next layer. + void set_layer( + // Print height of this layer. + float print_z, + // Layer height, used to calculate extrusion the rate. + float layer_height, + // Maximum number of tool changes on this layer or the layers below. + size_t max_tool_changes, + // Is this the first layer of the print? In that case print the brim first. + bool is_first_layer, + // Is this the last layer of the waste tower? + bool is_last_layer) + { + m_z_pos = print_z; + m_layer_height = layer_height; + m_is_first_layer = is_first_layer; + m_print_brim = is_first_layer; + m_depth_traversed = 0.f; + m_current_shape = (! is_first_layer && m_current_shape == SHAPE_NORMAL) ? SHAPE_REVERSED : SHAPE_NORMAL; + if (is_first_layer) { + this->m_num_layer_changes = 0; + this->m_num_tool_changes = 0; + } + else + ++ m_num_layer_changes; + + // Calculate extrusion flow from desired line width, nozzle diameter, filament diameter and layer_height: + m_extrusion_flow = extrusion_flow(layer_height); + + // Advance m_layer_info iterator, making sure we got it right + while (!m_plan.empty() && m_layer_info->z < print_z - WT_EPSILON && m_layer_info+1 != m_plan.end()) + ++m_layer_info; + } + + // Return the wipe tower position. + const Vec2f& position() const { return m_wipe_tower_pos; } + // Return the wipe tower width. + float width() const { return m_wipe_tower_width; } + // The wipe tower is finished, there should be no more tool changes or wipe tower prints. + bool finished() const { return m_max_color_changes == 0; } + // Returns gcode to prime the nozzles at the front edge of the print bed. - virtual ToolChangeResult prime( + std::vector prime( // print_z of the first layer. float first_layer_height, // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. const std::vector &tools, // If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower. // If false, the last priming are will be large enough to wipe the last extruder sufficiently. - bool last_wipe_inside_wipe_tower) = 0; + bool last_wipe_inside_wipe_tower); - // Returns gcode for toolchange and the end position. - // if new_tool == -1, just unload the current filament over the wipe tower. - virtual ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer) = 0; + // Returns gcode for a toolchange and a final print head position. + // On the first layer, extrude a brim around the future wipe tower first. + ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer); - // Close the current wipe tower layer with a perimeter and possibly fill the unfilled space with a zig-zag. + // Fill the unfilled space with a sparse infill. // Call this method only if layer_finished() is false. - virtual ToolChangeResult finish_layer() = 0; + ToolChangeResult finish_layer(); - // Is the current layer finished? A layer is finished if either the wipe tower is finished, or - // the wipe tower has been completely covered by the tool change extrusions, - // or the rest of the tower has been filled by a sparse infill with the finish_layer() method. - virtual bool layer_finished() const = 0; + // Is the current layer finished? + bool layer_finished() const { + return ( (m_is_first_layer ? m_wipe_tower_depth - m_perimeter_width : m_layer_info->depth) - WT_EPSILON < m_depth_traversed); + } - // Returns used filament length per extruder: - virtual std::vector get_used_filament() const = 0; + std::vector get_used_filament() const { return m_used_filament_length; } + int get_number_of_toolchanges() const { return m_num_tool_changes; } - // Returns total number of toolchanges: - virtual int get_number_of_toolchanges() const = 0; + struct FilamentParameters { + std::string material = "PLA"; + int temperature = 0; + int first_layer_temperature = 0; + float loading_speed = 0.f; + float loading_speed_start = 0.f; + float unloading_speed = 0.f; + float unloading_speed_start = 0.f; + float delay = 0.f ; + int cooling_moves = 0; + float cooling_initial_speed = 0.f; + float cooling_final_speed = 0.f; + float ramming_line_width_multiplicator = 1.f; + float ramming_step_multiplicator = 1.f; + float max_e_speed = std::numeric_limits::max(); + std::vector ramming_speed; + float nozzle_diameter; + float filament_area; + }; + +private: + WipeTower(); + + enum wipe_shape // A fill-in direction + { + SHAPE_NORMAL = 1, + SHAPE_REVERSED = -1 + }; + + const bool m_peters_wipe_tower = false; // sparse wipe tower inspired by Peter's post processor - not finished yet + const float Width_To_Nozzle_Ratio = 1.25f; // desired line width (oval) in multiples of nozzle diameter - may not be actually neccessary to adjust + const float WT_EPSILON = 1e-3f; + const float filament_area() const { + return m_filpar[0].filament_area; // all extruders are assumed to have the same filament diameter at this point + } + + + bool m_semm = true; // Are we using a single extruder multimaterial printer? + Vec2f m_wipe_tower_pos; // Left front corner of the wipe tower in mm. + float m_wipe_tower_width; // Width of the wipe tower. + float m_wipe_tower_depth = 0.f; // Depth of the wipe tower + float m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis) + float m_internal_rotation = 0.f; + float m_y_shift = 0.f; // y shift passed to writer + float m_z_pos = 0.f; // Current Z position. + float m_layer_height = 0.f; // Current layer height. + size_t m_max_color_changes = 0; // Maximum number of color changes per layer. + bool m_is_first_layer = false;// Is this the 1st layer of the print? If so, print the brim around the waste tower. + int m_old_temperature = -1; // To keep track of what was the last temp that we set (so we don't issue the command when not neccessary) + + // G-code generator parameters. + float m_cooling_tube_retraction = 0.f; + float m_cooling_tube_length = 0.f; + float m_parking_pos_retraction = 0.f; + float m_extra_loading_move = 0.f; + float m_bridging = 0.f; + bool m_set_extruder_trimpot = false; + bool m_adhesion = true; + GCodeFlavor m_gcode_flavor; + + // Bed properties + enum { + RectangularBed, + CircularBed + } m_bed_shape; + float m_bed_width; // width of the bed bounding box + + float m_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill. + float m_extrusion_flow = 0.038f; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter. + + // Extruder specific parameters. + std::vector m_filpar; + + + // State of the wipe tower generator. + unsigned int m_num_layer_changes = 0; // Layer change counter for the output statistics. + unsigned int m_num_tool_changes = 0; // Tool change change counter for the output statistics. + ///unsigned int m_idx_tool_change_in_layer = 0; // Layer change counter in this layer. Counting up to m_max_color_changes. + bool m_print_brim = true; + // A fill-in direction (positive Y, negative Y) alternates with each layer. + wipe_shape m_current_shape = SHAPE_NORMAL; + unsigned int m_current_tool = 0; + const std::vector> wipe_volumes; + + float m_depth_traversed = 0.f; // Current y position at the wipe tower. + bool m_left_to_right = true; + float m_extra_spacing = 1.f; + + // Calculates extrusion flow needed to produce required line width for given layer height + float extrusion_flow(float layer_height = -1.f) const // negative layer_height - return current m_extrusion_flow + { + if ( layer_height < 0 ) + return m_extrusion_flow; + return layer_height * ( m_perimeter_width - layer_height * (1.f-float(M_PI)/4.f)) / filament_area(); + } + + // Calculates length of extrusion line to extrude given volume + float volume_to_length(float volume, float line_width, float layer_height) const { + return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f)))); + } + + // Calculates depth for all layers and propagates them downwards + void plan_tower(); + + // Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental + void make_wipe_tower_square(); + + // Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe + void save_on_last_wipe(); + + + struct box_coordinates + { + box_coordinates(float left, float bottom, float width, float height) : + ld(left , bottom ), + lu(left , bottom + height), + rd(left + width, bottom ), + ru(left + width, bottom + height) {} + box_coordinates(const Vec2f &pos, float width, float height) : box_coordinates(pos(0), pos(1), width, height) {} + void translate(const Vec2f &shift) { + ld += shift; lu += shift; + rd += shift; ru += shift; + } + void translate(const float dx, const float dy) { translate(Vec2f(dx, dy)); } + void expand(const float offset) { + ld += Vec2f(- offset, - offset); + lu += Vec2f(- offset, offset); + rd += Vec2f( offset, - offset); + ru += Vec2f( offset, offset); + } + void expand(const float offset_x, const float offset_y) { + ld += Vec2f(- offset_x, - offset_y); + lu += Vec2f(- offset_x, offset_y); + rd += Vec2f( offset_x, - offset_y); + ru += Vec2f( offset_x, offset_y); + } + Vec2f ld; // left down + Vec2f lu; // left upper + Vec2f rd; // right lower + Vec2f ru; // right upper + }; + + + // to store information about tool changes for a given layer + struct WipeTowerInfo{ + struct ToolChange { + unsigned int old_tool; + unsigned int new_tool; + float required_depth; + float ramming_depth; + float first_wipe_line; + float wipe_volume; + ToolChange(unsigned int old, unsigned int newtool, float depth=0.f, float ramming_depth=0.f, float fwl=0.f, float wv=0.f) + : old_tool{old}, new_tool{newtool}, required_depth{depth}, ramming_depth{ramming_depth}, first_wipe_line{fwl}, wipe_volume{wv} {} + }; + float z; // z position of the layer + float height; // layer height + float depth; // depth of the layer based on all layers above + float extra_spacing; + float toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; } + + std::vector tool_changes; + + WipeTowerInfo(float z_par, float layer_height_par) + : z{z_par}, height{layer_height_par}, depth{0}, extra_spacing{1.f} {} + }; + + std::vector m_plan; // Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...)) + std::vector::iterator m_layer_info = m_plan.end(); + + // Stores information about used filament length per extruder: + std::vector m_used_filament_length; + + + // Returns gcode for wipe tower brim + // sideOnly -- set to false -- experimental, draw brim on sides of wipe tower + // offset -- set to 0 -- experimental, offset to replace brim in front / rear of wipe tower + ToolChangeResult toolchange_Brim(bool sideOnly = false, float y_offset = 0.f); + + void toolchange_Unload( + WipeTowerWriter &writer, + const box_coordinates &cleaning_box, + const std::string& current_material, + const int new_temperature); + + void toolchange_Change( + WipeTowerWriter &writer, + const unsigned int new_tool, + const std::string& new_material); + + void toolchange_Load( + WipeTowerWriter &writer, + const box_coordinates &cleaning_box); + + void toolchange_Wipe( + WipeTowerWriter &writer, + const box_coordinates &cleaning_box, + float wipe_volume); }; + + + }; // namespace Slic3r -#endif /* slic3r_WipeTower_hpp_ */ +#endif // WipeTowerPrusaMM_hpp_ diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.hpp b/src/libslic3r/GCode/WipeTowerPrusaMM.hpp deleted file mode 100644 index f8adf4c5f7..0000000000 --- a/src/libslic3r/GCode/WipeTowerPrusaMM.hpp +++ /dev/null @@ -1,388 +0,0 @@ -#ifndef WipeTowerPrusaMM_hpp_ -#define WipeTowerPrusaMM_hpp_ - -#include -#include -#include -#include -#include - -#include "WipeTower.hpp" -#include "PrintConfig.hpp" - - -namespace Slic3r -{ - -namespace PrusaMultiMaterial { - class Writer; -}; - - - -class WipeTowerPrusaMM : public WipeTower -{ -public: - enum material_type - { - INVALID = -1, - PLA = 0, // E:210C B:55C - ABS = 1, // E:255C B:100C - PET = 2, // E:240C B:90C - HIPS = 3, // E:220C B:100C - FLEX = 4, // E:245C B:80C - SCAFF = 5, // E:215C B:55C - EDGE = 6, // E:240C B:80C - NGEN = 7, // E:230C B:80C - PVA = 8, // E:210C B:80C - PC = 9 - }; - - // Parse material name into material_type. - static material_type parse_material(const char *name); - static std::string to_string(material_type material); - - // x -- x coordinates of wipe tower in mm ( left bottom corner ) - // y -- y coordinates of wipe tower in mm ( left bottom corner ) - // width -- width of wipe tower in mm ( default 60 mm - leave as it is ) - // wipe_area -- space available for one toolchange in mm - WipeTowerPrusaMM(float x, float y, float width, float rotation_angle, float cooling_tube_retraction, - float cooling_tube_length, float parking_pos_retraction, float extra_loading_move, - float bridging, bool set_extruder_trimpot, GCodeFlavor flavor, - const std::vector>& wiping_matrix, unsigned int initial_tool) : - m_wipe_tower_pos(x, y), - m_wipe_tower_width(width), - m_wipe_tower_rotation_angle(rotation_angle), - m_y_shift(0.f), - m_z_pos(0.f), - m_is_first_layer(false), - m_cooling_tube_retraction(cooling_tube_retraction), - m_cooling_tube_length(cooling_tube_length), - m_parking_pos_retraction(parking_pos_retraction), - m_extra_loading_move(extra_loading_move), - m_bridging(bridging), - m_set_extruder_trimpot(set_extruder_trimpot), - m_gcode_flavor(flavor), - m_current_tool(initial_tool), - wipe_volumes(wiping_matrix) - {} - - virtual ~WipeTowerPrusaMM() {} - - - // Set the extruder properties. - void set_extruder(size_t idx, material_type material, int temp, int first_layer_temp, float loading_speed, float loading_speed_start, - float unloading_speed, float unloading_speed_start, float delay, int cooling_moves, - float cooling_initial_speed, float cooling_final_speed, std::string ramming_parameters, float nozzle_diameter) - { - //while (m_filpar.size() < idx+1) // makes sure the required element is in the vector - m_filpar.push_back(FilamentParameters()); - - m_filpar[idx].material = material; - if (material == FLEX || material == SCAFF || material == PVA) { - // MMU2 lowers the print speed using the speed override (M220) for printing of soluble PVA/BVOH and flex materials. - // Therefore it does not make sense to use the new M220 B and M220 R (backup / restore). - m_retain_speed_override = false; - } - m_filpar[idx].temperature = temp; - m_filpar[idx].first_layer_temperature = first_layer_temp; - m_filpar[idx].loading_speed = loading_speed; - m_filpar[idx].loading_speed_start = loading_speed_start; - m_filpar[idx].unloading_speed = unloading_speed; - m_filpar[idx].unloading_speed_start = unloading_speed_start; - m_filpar[idx].delay = delay; - m_filpar[idx].cooling_moves = cooling_moves; - m_filpar[idx].cooling_initial_speed = cooling_initial_speed; - m_filpar[idx].cooling_final_speed = cooling_final_speed; - m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM - - m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter - - std::stringstream stream{ramming_parameters}; - float speed = 0.f; - stream >> m_filpar[idx].ramming_line_width_multiplicator >> m_filpar[idx].ramming_step_multiplicator; - m_filpar[idx].ramming_line_width_multiplicator /= 100; - m_filpar[idx].ramming_step_multiplicator /= 100; - while (stream >> speed) - m_filpar[idx].ramming_speed.push_back(speed); - - m_used_filament_length.resize(std::max(m_used_filament_length.size(), idx + 1)); // makes sure that the vector is big enough so we don't have to check later - } - - - // Appends into internal structure m_plan containing info about the future wipe tower - // to be used before building begins. The entries must be added ordered in z. - void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, bool brim, float wipe_volume = 0.f); - - // Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result" - void generate(std::vector> &result); - - float get_depth() const { return m_wipe_tower_depth; } - - - - // Switch to a next layer. - virtual void set_layer( - // Print height of this layer. - float print_z, - // Layer height, used to calculate extrusion the rate. - float layer_height, - // Maximum number of tool changes on this layer or the layers below. - size_t max_tool_changes, - // Is this the first layer of the print? In that case print the brim first. - bool is_first_layer, - // Is this the last layer of the waste tower? - bool is_last_layer) - { - m_z_pos = print_z; - m_layer_height = layer_height; - m_is_first_layer = is_first_layer; - m_print_brim = is_first_layer; - m_depth_traversed = 0.f; - m_current_shape = (! is_first_layer && m_current_shape == SHAPE_NORMAL) ? SHAPE_REVERSED : SHAPE_NORMAL; - if (is_first_layer) { - this->m_num_layer_changes = 0; - this->m_num_tool_changes = 0; - } - else - ++ m_num_layer_changes; - - // Calculate extrusion flow from desired line width, nozzle diameter, filament diameter and layer_height: - m_extrusion_flow = extrusion_flow(layer_height); - - // Advance m_layer_info iterator, making sure we got it right - while (!m_plan.empty() && m_layer_info->z < print_z - WT_EPSILON && m_layer_info+1 != m_plan.end()) - ++m_layer_info; - } - - // Return the wipe tower position. - virtual const xy& position() const { return m_wipe_tower_pos; } - // Return the wipe tower width. - virtual float width() const { return m_wipe_tower_width; } - // The wipe tower is finished, there should be no more tool changes or wipe tower prints. - virtual bool finished() const { return m_max_color_changes == 0; } - - // Returns gcode to prime the nozzles at the front edge of the print bed. - virtual ToolChangeResult prime( - // print_z of the first layer. - float first_layer_height, - // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. - const std::vector &tools, - // If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower. - // If false, the last priming are will be large enough to wipe the last extruder sufficiently. - bool last_wipe_inside_wipe_tower); - - // Returns gcode for a toolchange and a final print head position. - // On the first layer, extrude a brim around the future wipe tower first. - virtual ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer); - - // Fill the unfilled space with a sparse infill. - // Call this method only if layer_finished() is false. - virtual ToolChangeResult finish_layer(); - - // Is the current layer finished? - virtual bool layer_finished() const { - return ( (m_is_first_layer ? m_wipe_tower_depth - m_perimeter_width : m_layer_info->depth) - WT_EPSILON < m_depth_traversed); - } - - virtual std::vector get_used_filament() const override { return m_used_filament_length; } - virtual int get_number_of_toolchanges() const override { return m_num_tool_changes; } - - -private: - WipeTowerPrusaMM(); - - enum wipe_shape // A fill-in direction - { - SHAPE_NORMAL = 1, - SHAPE_REVERSED = -1 - }; - - - const bool m_peters_wipe_tower = false; // sparse wipe tower inspired by Peter's post processor - not finished yet - const float Filament_Area = float(M_PI * 1.75f * 1.75f / 4.f); // filament area in mm^2 - const float Width_To_Nozzle_Ratio = 1.25f; // desired line width (oval) in multiples of nozzle diameter - may not be actually neccessary to adjust - const float WT_EPSILON = 1e-3f; - - - xy m_wipe_tower_pos; // Left front corner of the wipe tower in mm. - float m_wipe_tower_width; // Width of the wipe tower. - float m_wipe_tower_depth = 0.f; // Depth of the wipe tower - float m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis) - float m_internal_rotation = 0.f; - float m_y_shift = 0.f; // y shift passed to writer - float m_z_pos = 0.f; // Current Z position. - float m_layer_height = 0.f; // Current layer height. - size_t m_max_color_changes = 0; // Maximum number of color changes per layer. - bool m_is_first_layer = false;// Is this the 1st layer of the print? If so, print the brim around the waste tower. - int m_old_temperature = -1; // To keep track of what was the last temp that we set (so we don't issue the command when not neccessary) - - // G-code generator parameters. - float m_cooling_tube_retraction = 0.f; - float m_cooling_tube_length = 0.f; - float m_parking_pos_retraction = 0.f; - float m_extra_loading_move = 0.f; - float m_bridging = 0.f; - bool m_set_extruder_trimpot = false; - bool m_retain_speed_override = true; - bool m_adhesion = true; - GCodeFlavor m_gcode_flavor; - - float m_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill. - float m_extrusion_flow = 0.038f; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter. - - - struct FilamentParameters { - material_type material = PLA; - int temperature = 0; - int first_layer_temperature = 0; - float loading_speed = 0.f; - float loading_speed_start = 0.f; - float unloading_speed = 0.f; - float unloading_speed_start = 0.f; - float delay = 0.f ; - int cooling_moves = 0; - float cooling_initial_speed = 0.f; - float cooling_final_speed = 0.f; - float ramming_line_width_multiplicator = 0.f; - float ramming_step_multiplicator = 0.f; - std::vector ramming_speed; - float nozzle_diameter; - }; - - // Extruder specific parameters. - std::vector m_filpar; - - - // State of the wipe tower generator. - unsigned int m_num_layer_changes = 0; // Layer change counter for the output statistics. - unsigned int m_num_tool_changes = 0; // Tool change change counter for the output statistics. - ///unsigned int m_idx_tool_change_in_layer = 0; // Layer change counter in this layer. Counting up to m_max_color_changes. - bool m_print_brim = true; - // A fill-in direction (positive Y, negative Y) alternates with each layer. - wipe_shape m_current_shape = SHAPE_NORMAL; - unsigned int m_current_tool = 0; - const std::vector> wipe_volumes; - - float m_depth_traversed = 0.f; // Current y position at the wipe tower. - bool m_left_to_right = true; - float m_extra_spacing = 1.f; - - // Calculates extrusion flow needed to produce required line width for given layer height - float extrusion_flow(float layer_height = -1.f) const // negative layer_height - return current m_extrusion_flow - { - if ( layer_height < 0 ) - return m_extrusion_flow; - return layer_height * ( m_perimeter_width - layer_height * (1.f-float(M_PI)/4.f)) / Filament_Area; - } - - // Calculates length of extrusion line to extrude given volume - float volume_to_length(float volume, float line_width, float layer_height) const { - return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f)))); - } - - // Calculates depth for all layers and propagates them downwards - void plan_tower(); - - // Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental - void make_wipe_tower_square(); - - // Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe - void save_on_last_wipe(); - - - struct box_coordinates - { - box_coordinates(float left, float bottom, float width, float height) : - ld(left , bottom ), - lu(left , bottom + height), - rd(left + width, bottom ), - ru(left + width, bottom + height) {} - box_coordinates(const xy &pos, float width, float height) : box_coordinates(pos.x, pos.y, width, height) {} - void translate(const xy &shift) { - ld += shift; lu += shift; - rd += shift; ru += shift; - } - void translate(const float dx, const float dy) { translate(xy(dx, dy)); } - void expand(const float offset) { - ld += xy(- offset, - offset); - lu += xy(- offset, offset); - rd += xy( offset, - offset); - ru += xy( offset, offset); - } - void expand(const float offset_x, const float offset_y) { - ld += xy(- offset_x, - offset_y); - lu += xy(- offset_x, offset_y); - rd += xy( offset_x, - offset_y); - ru += xy( offset_x, offset_y); - } - xy ld; // left down - xy lu; // left upper - xy rd; // right lower - xy ru; // right upper - }; - - - // to store information about tool changes for a given layer - struct WipeTowerInfo{ - struct ToolChange { - unsigned int old_tool; - unsigned int new_tool; - float required_depth; - float ramming_depth; - float first_wipe_line; - float wipe_volume; - ToolChange(unsigned int old, unsigned int newtool, float depth=0.f, float ramming_depth=0.f, float fwl=0.f, float wv=0.f) - : old_tool{old}, new_tool{newtool}, required_depth{depth}, ramming_depth{ramming_depth}, first_wipe_line{fwl}, wipe_volume{wv} {} - }; - float z; // z position of the layer - float height; // layer height - float depth; // depth of the layer based on all layers above - float extra_spacing; - float toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; } - - std::vector tool_changes; - - WipeTowerInfo(float z_par, float layer_height_par) - : z{z_par}, height{layer_height_par}, depth{0}, extra_spacing{1.f} {} - }; - - std::vector m_plan; // Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...)) - std::vector::iterator m_layer_info = m_plan.end(); - - // Stores information about used filament length per extruder: - std::vector m_used_filament_length; - - - // Returns gcode for wipe tower brim - // sideOnly -- set to false -- experimental, draw brim on sides of wipe tower - // offset -- set to 0 -- experimental, offset to replace brim in front / rear of wipe tower - ToolChangeResult toolchange_Brim(bool sideOnly = false, float y_offset = 0.f); - - void toolchange_Unload( - PrusaMultiMaterial::Writer &writer, - const box_coordinates &cleaning_box, - const material_type current_material, - const int new_temperature); - - void toolchange_Change( - PrusaMultiMaterial::Writer &writer, - const unsigned int new_tool, - material_type new_material); - - void toolchange_Load( - PrusaMultiMaterial::Writer &writer, - const box_coordinates &cleaning_box); - - void toolchange_Wipe( - PrusaMultiMaterial::Writer &writer, - const box_coordinates &cleaning_box, - float wipe_volume); -}; - - - - -}; // namespace Slic3r - -#endif /* WipeTowerPrusaMM_hpp_ */ diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp index 60d7a4cdf4..00b409344e 100644 --- a/src/libslic3r/GCodeTimeEstimator.cpp +++ b/src/libslic3r/GCodeTimeEstimator.cpp @@ -125,8 +125,8 @@ namespace Slic3r { trapezoid.distance = distance; trapezoid.feedrate = feedrate; - float accelerate_distance = estimate_acceleration_distance(feedrate.entry, feedrate.cruise, acceleration); - float decelerate_distance = estimate_acceleration_distance(feedrate.cruise, feedrate.exit, -acceleration); + float accelerate_distance = std::max(0.0f, estimate_acceleration_distance(feedrate.entry, feedrate.cruise, acceleration)); + float decelerate_distance = std::max(0.0f, estimate_acceleration_distance(feedrate.cruise, feedrate.exit, -acceleration)); float cruise_distance = distance - accelerate_distance - decelerate_distance; // Not enough space to reach the nominal feedrate. @@ -168,13 +168,17 @@ namespace Slic3r { } #endif // ENABLE_MOVE_STATS - const std::string GCodeTimeEstimator::Normal_First_M73_Output_Placeholder_Tag = "; NORMAL_FIRST_M73_OUTPUT_PLACEHOLDER"; - const std::string GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag = "; SILENT_FIRST_M73_OUTPUT_PLACEHOLDER"; - const std::string GCodeTimeEstimator::Normal_Last_M73_Output_Placeholder_Tag = "; NORMAL_LAST_M73_OUTPUT_PLACEHOLDER"; - const std::string GCodeTimeEstimator::Silent_Last_M73_Output_Placeholder_Tag = "; SILENT_LAST_M73_OUTPUT_PLACEHOLDER"; + const std::string GCodeTimeEstimator::Normal_First_M73_Output_Placeholder_Tag = "; _TE_NORMAL_FIRST_M73_OUTPUT_PLACEHOLDER"; + const std::string GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag = "; _TE_SILENT_FIRST_M73_OUTPUT_PLACEHOLDER"; + const std::string GCodeTimeEstimator::Normal_Last_M73_Output_Placeholder_Tag = "; _TE_NORMAL_LAST_M73_OUTPUT_PLACEHOLDER"; + const std::string GCodeTimeEstimator::Silent_Last_M73_Output_Placeholder_Tag = "; _TE_SILENT_LAST_M73_OUTPUT_PLACEHOLDER"; + + // temporary human readable form to use until not removed from gcode by the new post-process method + const std::string GCodeTimeEstimator::Color_Change_Tag = "PRINT_COLOR_CHANGE"; +// const std::string GCodeTimeEstimator::Color_Change_Tag = "_TE_COLOR_CHANGE"; GCodeTimeEstimator::GCodeTimeEstimator(EMode mode) - : _mode(mode) + : m_mode(mode) { reset(); set_default(); @@ -183,7 +187,7 @@ namespace Slic3r { void GCodeTimeEstimator::add_gcode_line(const std::string& gcode_line) { PROFILE_FUNC(); - _parser.parse_line(gcode_line, + m_parser.parse_line(gcode_line, [this](GCodeReader &reader, const GCodeReader::GCodeLine &line) { this->_process_gcode_line(reader, line); }); } @@ -196,7 +200,7 @@ namespace Slic3r { { this->_process_gcode_line(reader, line); }; for (; *ptr != 0;) { gline.reset(); - ptr = _parser.parse_line(ptr, gline, action); + ptr = m_parser.parse_line(ptr, gline, action); } } @@ -206,10 +210,13 @@ namespace Slic3r { if (start_from_beginning) { _reset_time(); - _last_st_synchronized_block_id = -1; + m_last_st_synchronized_block_id = -1; } _calculate_time(); + if (m_needs_color_times && (m_color_time_cache != 0.0f)) + m_color_times.push_back(m_color_time_cache); + #if ENABLE_MOVE_STATS _log_moves_stats(); #endif // ENABLE_MOVE_STATS @@ -219,12 +226,15 @@ namespace Slic3r { { reset(); - _parser.parse_buffer(gcode, + m_parser.parse_buffer(gcode, [this](GCodeReader &reader, const GCodeReader::GCodeLine &line) { this->_process_gcode_line(reader, line); }); _calculate_time(); + if (m_needs_color_times && (m_color_time_cache != 0.0f)) + m_color_times.push_back(m_color_time_cache); + #if ENABLE_MOVE_STATS _log_moves_stats(); #endif // ENABLE_MOVE_STATS @@ -234,9 +244,12 @@ namespace Slic3r { { reset(); - _parser.parse_file(file, boost::bind(&GCodeTimeEstimator::_process_gcode_line, this, _1, _2)); + m_parser.parse_file(file, boost::bind(&GCodeTimeEstimator::_process_gcode_line, this, _1, _2)); _calculate_time(); + if (m_needs_color_times && (m_color_time_cache != 0.0f)) + m_color_times.push_back(m_color_time_cache); + #if ENABLE_MOVE_STATS _log_moves_stats(); #endif // ENABLE_MOVE_STATS @@ -249,9 +262,12 @@ namespace Slic3r { auto action = [this](GCodeReader &reader, const GCodeReader::GCodeLine &line) { this->_process_gcode_line(reader, line); }; for (const std::string& line : gcode_lines) - _parser.parse_line(line, action); + m_parser.parse_line(line, action); _calculate_time(); + if (m_needs_color_times && (m_color_time_cache != 0.0f)) + m_color_times.push_back(m_color_time_cache); + #if ENABLE_MOVE_STATS _log_moves_stats(); #endif // ENABLE_MOVE_STATS @@ -270,7 +286,7 @@ namespace Slic3r { throw std::runtime_error(std::string("Remaining times export failed.\nCannot open file for writing.\n")); std::string time_mask; - switch (_mode) + switch (m_mode) { default: case Normal: @@ -291,7 +307,7 @@ namespace Slic3r { // buffer line to export only when greater than 64K to reduce writing calls std::string export_line; char time_line[64]; - G1LineIdToBlockIdMap::const_iterator it_line_id = _g1_line_ids.begin(); + G1LineIdToBlockIdMap::const_iterator it_line_id = m_g1_line_ids.begin(); while (std::getline(in, gcode_line)) { if (!in.good()) @@ -301,15 +317,15 @@ namespace Slic3r { } // replaces placeholders for initial line M73 with the real lines - if (((_mode == Normal) && (gcode_line == Normal_First_M73_Output_Placeholder_Tag)) || - ((_mode == Silent) && (gcode_line == Silent_First_M73_Output_Placeholder_Tag))) + if (((m_mode == Normal) && (gcode_line == Normal_First_M73_Output_Placeholder_Tag)) || + ((m_mode == Silent) && (gcode_line == Silent_First_M73_Output_Placeholder_Tag))) { - sprintf(time_line, time_mask.c_str(), "0", _get_time_minutes(_time).c_str()); + sprintf(time_line, time_mask.c_str(), "0", _get_time_minutes(m_time).c_str()); gcode_line = time_line; } // replaces placeholders for final line M73 with the real lines - else if (((_mode == Normal) && (gcode_line == Normal_Last_M73_Output_Placeholder_Tag)) || - ((_mode == Silent) && (gcode_line == Silent_Last_M73_Output_Placeholder_Tag))) + else if (((m_mode == Normal) && (gcode_line == Normal_Last_M73_Output_Placeholder_Tag)) || + ((m_mode == Silent) && (gcode_line == Silent_Last_M73_Output_Placeholder_Tag))) { sprintf(time_line, time_mask.c_str(), "100", "0"); gcode_line = time_line; @@ -319,27 +335,27 @@ namespace Slic3r { // add remaining time lines where needed - _parser.parse_line(gcode_line, + m_parser.parse_line(gcode_line, [this, &it_line_id, &g1_lines_count, &last_recorded_time, &time_line, &gcode_line, time_mask, interval](GCodeReader& reader, const GCodeReader::GCodeLine& line) { if (line.cmd_is("G1")) { ++g1_lines_count; - assert(it_line_id == _g1_line_ids.end() || it_line_id->first >= g1_lines_count); + assert(it_line_id == m_g1_line_ids.end() || it_line_id->first >= g1_lines_count); const Block *block = nullptr; - if (it_line_id != _g1_line_ids.end() && it_line_id->first == g1_lines_count) { - if (line.has_e() && it_line_id->second < (unsigned int)_blocks.size()) - block = &_blocks[it_line_id->second]; + if (it_line_id != m_g1_line_ids.end() && it_line_id->first == g1_lines_count) { + if (line.has_e() && it_line_id->second < (unsigned int)m_blocks.size()) + block = &m_blocks[it_line_id->second]; ++it_line_id; } if (block != nullptr && block->elapsed_time != -1.0f) { - float block_remaining_time = _time - block->elapsed_time; + float block_remaining_time = m_time - block->elapsed_time; if (std::abs(last_recorded_time - block_remaining_time) > interval) { - sprintf(time_line, time_mask.c_str(), std::to_string((int)(100.0f * block->elapsed_time / _time)).c_str(), _get_time_minutes(block_remaining_time).c_str()); + sprintf(time_line, time_mask.c_str(), std::to_string((int)(100.0f * block->elapsed_time / m_time)).c_str(), _get_time_minutes(block_remaining_time).c_str()); gcode_line += time_line; last_recorded_time = block_remaining_time; @@ -378,7 +394,7 @@ namespace Slic3r { fclose(out); in.close(); - if (rename_file(path_tmp, filename) != 0) + if (rename_file(path_tmp, filename)) throw std::runtime_error(std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + filename + '\n' + "Is " + path_tmp + " locked?" + '\n'); @@ -387,240 +403,240 @@ namespace Slic3r { void GCodeTimeEstimator::set_axis_position(EAxis axis, float position) { - _state.axis[axis].position = position; + m_state.axis[axis].position = position; } void GCodeTimeEstimator::set_axis_max_feedrate(EAxis axis, float feedrate_mm_sec) { - _state.axis[axis].max_feedrate = feedrate_mm_sec; + m_state.axis[axis].max_feedrate = feedrate_mm_sec; } void GCodeTimeEstimator::set_axis_max_acceleration(EAxis axis, float acceleration) { - _state.axis[axis].max_acceleration = acceleration; + m_state.axis[axis].max_acceleration = acceleration; } void GCodeTimeEstimator::set_axis_max_jerk(EAxis axis, float jerk) { - _state.axis[axis].max_jerk = jerk; + m_state.axis[axis].max_jerk = jerk; } float GCodeTimeEstimator::get_axis_position(EAxis axis) const { - return _state.axis[axis].position; + return m_state.axis[axis].position; } float GCodeTimeEstimator::get_axis_max_feedrate(EAxis axis) const { - return _state.axis[axis].max_feedrate; + return m_state.axis[axis].max_feedrate; } float GCodeTimeEstimator::get_axis_max_acceleration(EAxis axis) const { - return _state.axis[axis].max_acceleration; + return m_state.axis[axis].max_acceleration; } float GCodeTimeEstimator::get_axis_max_jerk(EAxis axis) const { - return _state.axis[axis].max_jerk; + return m_state.axis[axis].max_jerk; } void GCodeTimeEstimator::set_feedrate(float feedrate_mm_sec) { - _state.feedrate = feedrate_mm_sec; + m_state.feedrate = feedrate_mm_sec; } float GCodeTimeEstimator::get_feedrate() const { - return _state.feedrate; + return m_state.feedrate; } void GCodeTimeEstimator::set_acceleration(float acceleration_mm_sec2) { - _state.acceleration = (_state.max_acceleration == 0) ? + m_state.acceleration = (m_state.max_acceleration == 0) ? acceleration_mm_sec2 : // Clamp the acceleration with the maximum. - std::min(_state.max_acceleration, acceleration_mm_sec2); + std::min(m_state.max_acceleration, acceleration_mm_sec2); } float GCodeTimeEstimator::get_acceleration() const { - return _state.acceleration; + return m_state.acceleration; } void GCodeTimeEstimator::set_max_acceleration(float acceleration_mm_sec2) { - _state.max_acceleration = acceleration_mm_sec2; + m_state.max_acceleration = acceleration_mm_sec2; if (acceleration_mm_sec2 > 0) - _state.acceleration = acceleration_mm_sec2; + m_state.acceleration = acceleration_mm_sec2; } float GCodeTimeEstimator::get_max_acceleration() const { - return _state.max_acceleration; + return m_state.max_acceleration; } void GCodeTimeEstimator::set_retract_acceleration(float acceleration_mm_sec2) { - _state.retract_acceleration = acceleration_mm_sec2; + m_state.retract_acceleration = acceleration_mm_sec2; } float GCodeTimeEstimator::get_retract_acceleration() const { - return _state.retract_acceleration; + return m_state.retract_acceleration; } void GCodeTimeEstimator::set_minimum_feedrate(float feedrate_mm_sec) { - _state.minimum_feedrate = feedrate_mm_sec; + m_state.minimum_feedrate = feedrate_mm_sec; } float GCodeTimeEstimator::get_minimum_feedrate() const { - return _state.minimum_feedrate; + return m_state.minimum_feedrate; } void GCodeTimeEstimator::set_minimum_travel_feedrate(float feedrate_mm_sec) { - _state.minimum_travel_feedrate = feedrate_mm_sec; + m_state.minimum_travel_feedrate = feedrate_mm_sec; } float GCodeTimeEstimator::get_minimum_travel_feedrate() const { - return _state.minimum_travel_feedrate; + return m_state.minimum_travel_feedrate; } void GCodeTimeEstimator::set_filament_load_times(const std::vector &filament_load_times) { - _state.filament_load_times.clear(); + m_state.filament_load_times.clear(); for (double t : filament_load_times) - _state.filament_load_times.push_back((float)t); + m_state.filament_load_times.push_back((float)t); } void GCodeTimeEstimator::set_filament_unload_times(const std::vector &filament_unload_times) { - _state.filament_unload_times.clear(); + m_state.filament_unload_times.clear(); for (double t : filament_unload_times) - _state.filament_unload_times.push_back((float)t); + m_state.filament_unload_times.push_back((float)t); } float GCodeTimeEstimator::get_filament_load_time(unsigned int id_extruder) { return - (_state.filament_load_times.empty() || id_extruder == _state.extruder_id_unloaded) ? + (m_state.filament_load_times.empty() || id_extruder == m_state.extruder_id_unloaded) ? 0 : - (_state.filament_load_times.size() <= id_extruder) ? - _state.filament_load_times.front() : - _state.filament_load_times[id_extruder]; + (m_state.filament_load_times.size() <= id_extruder) ? + m_state.filament_load_times.front() : + m_state.filament_load_times[id_extruder]; } float GCodeTimeEstimator::get_filament_unload_time(unsigned int id_extruder) { return - (_state.filament_unload_times.empty() || id_extruder == _state.extruder_id_unloaded) ? + (m_state.filament_unload_times.empty() || id_extruder == m_state.extruder_id_unloaded) ? 0 : - (_state.filament_unload_times.size() <= id_extruder) ? - _state.filament_unload_times.front() : - _state.filament_unload_times[id_extruder]; + (m_state.filament_unload_times.size() <= id_extruder) ? + m_state.filament_unload_times.front() : + m_state.filament_unload_times[id_extruder]; } void GCodeTimeEstimator::set_extrude_factor_override_percentage(float percentage) { - _state.extrude_factor_override_percentage = percentage; + m_state.extrude_factor_override_percentage = percentage; } float GCodeTimeEstimator::get_extrude_factor_override_percentage() const { - return _state.extrude_factor_override_percentage; + return m_state.extrude_factor_override_percentage; } void GCodeTimeEstimator::set_dialect(GCodeFlavor dialect) { - _state.dialect = dialect; + m_state.dialect = dialect; } GCodeFlavor GCodeTimeEstimator::get_dialect() const { PROFILE_FUNC(); - return _state.dialect; + return m_state.dialect; } void GCodeTimeEstimator::set_units(GCodeTimeEstimator::EUnits units) { - _state.units = units; + m_state.units = units; } GCodeTimeEstimator::EUnits GCodeTimeEstimator::get_units() const { - return _state.units; + return m_state.units; } void GCodeTimeEstimator::set_global_positioning_type(GCodeTimeEstimator::EPositioningType type) { - _state.global_positioning_type = type; + m_state.global_positioning_type = type; } GCodeTimeEstimator::EPositioningType GCodeTimeEstimator::get_global_positioning_type() const { - return _state.global_positioning_type; + return m_state.global_positioning_type; } void GCodeTimeEstimator::set_e_local_positioning_type(GCodeTimeEstimator::EPositioningType type) { - _state.e_local_positioning_type = type; + m_state.e_local_positioning_type = type; } GCodeTimeEstimator::EPositioningType GCodeTimeEstimator::get_e_local_positioning_type() const { - return _state.e_local_positioning_type; + return m_state.e_local_positioning_type; } int GCodeTimeEstimator::get_g1_line_id() const { - return _state.g1_line_id; + return m_state.g1_line_id; } void GCodeTimeEstimator::increment_g1_line_id() { - ++_state.g1_line_id; + ++m_state.g1_line_id; } void GCodeTimeEstimator::reset_g1_line_id() { - _state.g1_line_id = 0; + m_state.g1_line_id = 0; } void GCodeTimeEstimator::set_extruder_id(unsigned int id) { - _state.extruder_id = id; + m_state.extruder_id = id; } unsigned int GCodeTimeEstimator::get_extruder_id() const { - return _state.extruder_id; + return m_state.extruder_id; } void GCodeTimeEstimator::reset_extruder_id() { // Set the initial extruder ID to unknown. For the multi-material setup it means // that all the filaments are parked in the MMU and no filament is loaded yet. - _state.extruder_id = _state.extruder_id_unloaded; + m_state.extruder_id = m_state.extruder_id_unloaded; } void GCodeTimeEstimator::add_additional_time(float timeSec) { PROFILE_FUNC(); - _state.additional_time += timeSec; + m_state.additional_time += timeSec; } void GCodeTimeEstimator::set_additional_time(float timeSec) { - _state.additional_time = timeSec; + m_state.additional_time = timeSec; } float GCodeTimeEstimator::get_additional_time() const { - return _state.additional_time; + return m_state.additional_time; } void GCodeTimeEstimator::set_default() @@ -648,8 +664,8 @@ namespace Slic3r { set_axis_max_jerk(axis, DEFAULT_AXIS_MAX_JERK[a]); } - _state.filament_load_times.clear(); - _state.filament_unload_times.clear(); + m_state.filament_load_times.clear(); + m_state.filament_unload_times.clear(); } void GCodeTimeEstimator::reset() @@ -664,7 +680,7 @@ namespace Slic3r { float GCodeTimeEstimator::get_time() const { - return _time; + return m_time; } std::string GCodeTimeEstimator::get_time_dhms() const @@ -677,19 +693,61 @@ namespace Slic3r { return _get_time_minutes(get_time()); } + std::vector GCodeTimeEstimator::get_color_times() const + { + return m_color_times; + } + + std::vector GCodeTimeEstimator::get_color_times_dhms(bool include_remaining) const + { + std::vector ret; + float total_time = 0.0f; + for (float t : m_color_times) + { + std::string time = _get_time_dhms(t); + if (include_remaining) + { + time += " ("; + time += _get_time_dhms(m_time - total_time); + time += ")"; + } + total_time += t; + ret.push_back(time); + } + return ret; + } + + std::vector GCodeTimeEstimator::get_color_times_minutes(bool include_remaining) const + { + std::vector ret; + float total_time = 0.0f; + for (float t : m_color_times) + { + std::string time = _get_time_minutes(t); + if (include_remaining) + { + time += " ("; + time += _get_time_minutes(m_time - total_time); + time += ")"; + } + total_time += t; + } + return ret; + } + // Return an estimate of the memory consumed by the time estimator. size_t GCodeTimeEstimator::memory_used() const { size_t out = sizeof(*this); - out += SLIC3R_STDVEC_MEMSIZE(this->_blocks, Block); - out += SLIC3R_STDVEC_MEMSIZE(this->_g1_line_ids, G1LineIdToBlockId); + out += SLIC3R_STDVEC_MEMSIZE(this->m_blocks, Block); + out += SLIC3R_STDVEC_MEMSIZE(this->m_g1_line_ids, G1LineIdToBlockId); return out; } void GCodeTimeEstimator::_reset() { - _curr.reset(); - _prev.reset(); + m_curr.reset(); + m_prev.reset(); set_axis_position(X, 0.0f); set_axis_position(Y, 0.0f); @@ -701,19 +759,23 @@ namespace Slic3r { reset_extruder_id(); reset_g1_line_id(); - _g1_line_ids.clear(); + m_g1_line_ids.clear(); - _last_st_synchronized_block_id = -1; + m_last_st_synchronized_block_id = -1; + + m_needs_color_times = false; + m_color_times.clear(); + m_color_time_cache = 0.0f; } void GCodeTimeEstimator::_reset_time() { - _time = 0.0f; + m_time = 0.0f; } void GCodeTimeEstimator::_reset_blocks() { - _blocks.clear(); + m_blocks.clear(); } void GCodeTimeEstimator::_calculate_time() @@ -723,35 +785,32 @@ namespace Slic3r { _reverse_pass(); _recalculate_trapezoids(); - _time += get_additional_time(); + m_time += get_additional_time(); + m_color_time_cache += get_additional_time(); - for (int i = _last_st_synchronized_block_id + 1; i < (int)_blocks.size(); ++i) + for (int i = m_last_st_synchronized_block_id + 1; i < (int)m_blocks.size(); ++i) { - Block& block = _blocks[i]; - -#if ENABLE_MOVE_STATS + Block& block = m_blocks[i]; float block_time = 0.0f; block_time += block.acceleration_time(); block_time += block.cruise_time(); block_time += block.deceleration_time(); - _time += block_time; - block.elapsed_time = _time; + m_time += block_time; + block.elapsed_time = m_time; +#if ENABLE_MOVE_STATS MovesStatsMap::iterator it = _moves_stats.find(block.move_type); if (it == _moves_stats.end()) it = _moves_stats.insert(MovesStatsMap::value_type(block.move_type, MoveStats())).first; it->second.count += 1; it->second.time += block_time; -#else - _time += block.acceleration_time(); - _time += block.cruise_time(); - _time += block.deceleration_time(); - block.elapsed_time = _time; #endif // ENABLE_MOVE_STATS + + m_color_time_cache += block_time; } - _last_st_synchronized_block_id = (int)_blocks.size() - 1; + m_last_st_synchronized_block_id = (int)m_blocks.size() - 1; // The additional time has been consumed (added to the total time), reset it to zero. set_additional_time(0.); } @@ -759,6 +818,11 @@ namespace Slic3r { void GCodeTimeEstimator::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLine& line) { PROFILE_FUNC(); + + // processes 'special' comments contained in line + if (_process_tags(line)) + return; + std::string cmd = line.cmd(); if (cmd.length() > 1) { @@ -934,7 +998,7 @@ namespace Slic3r { return; // calculates block feedrate - _curr.feedrate = std::max(get_feedrate(), block.is_travel_move() ? get_minimum_travel_feedrate() : get_minimum_feedrate()); + m_curr.feedrate = std::max(get_feedrate(), block.is_travel_move() ? get_minimum_travel_feedrate() : get_minimum_feedrate()); float distance = block.move_length(); float invDistance = 1.0f / distance; @@ -942,23 +1006,23 @@ namespace Slic3r { float min_feedrate_factor = 1.0f; for (unsigned char a = X; a < Num_Axis; ++a) { - _curr.axis_feedrate[a] = _curr.feedrate * block.delta_pos[a] * invDistance; + m_curr.axis_feedrate[a] = m_curr.feedrate * block.delta_pos[a] * invDistance; if (a == E) - _curr.axis_feedrate[a] *= get_extrude_factor_override_percentage(); + m_curr.axis_feedrate[a] *= get_extrude_factor_override_percentage(); - _curr.abs_axis_feedrate[a] = std::abs(_curr.axis_feedrate[a]); - if (_curr.abs_axis_feedrate[a] > 0.0f) - min_feedrate_factor = std::min(min_feedrate_factor, get_axis_max_feedrate((EAxis)a) / _curr.abs_axis_feedrate[a]); + m_curr.abs_axis_feedrate[a] = std::abs(m_curr.axis_feedrate[a]); + if (m_curr.abs_axis_feedrate[a] > 0.0f) + min_feedrate_factor = std::min(min_feedrate_factor, get_axis_max_feedrate((EAxis)a) / m_curr.abs_axis_feedrate[a]); } - block.feedrate.cruise = min_feedrate_factor * _curr.feedrate; + block.feedrate.cruise = min_feedrate_factor * m_curr.feedrate; if (min_feedrate_factor < 1.0f) { for (unsigned char a = X; a < Num_Axis; ++a) { - _curr.axis_feedrate[a] *= min_feedrate_factor; - _curr.abs_axis_feedrate[a] *= min_feedrate_factor; + m_curr.axis_feedrate[a] *= min_feedrate_factor; + m_curr.abs_axis_feedrate[a] *= min_feedrate_factor; } } @@ -975,25 +1039,25 @@ namespace Slic3r { block.acceleration = acceleration; // calculates block exit feedrate - _curr.safe_feedrate = block.feedrate.cruise; + m_curr.safe_feedrate = block.feedrate.cruise; for (unsigned char a = X; a < Num_Axis; ++a) { float axis_max_jerk = get_axis_max_jerk((EAxis)a); - if (_curr.abs_axis_feedrate[a] > axis_max_jerk) - _curr.safe_feedrate = std::min(_curr.safe_feedrate, axis_max_jerk); + if (m_curr.abs_axis_feedrate[a] > axis_max_jerk) + m_curr.safe_feedrate = std::min(m_curr.safe_feedrate, axis_max_jerk); } - block.feedrate.exit = _curr.safe_feedrate; + block.feedrate.exit = m_curr.safe_feedrate; // calculates block entry feedrate - float vmax_junction = _curr.safe_feedrate; - if (!_blocks.empty() && (_prev.feedrate > PREVIOUS_FEEDRATE_THRESHOLD)) + float vmax_junction = m_curr.safe_feedrate; + if (!m_blocks.empty() && (m_prev.feedrate > PREVIOUS_FEEDRATE_THRESHOLD)) { - bool prev_speed_larger = _prev.feedrate > block.feedrate.cruise; - float smaller_speed_factor = prev_speed_larger ? (block.feedrate.cruise / _prev.feedrate) : (_prev.feedrate / block.feedrate.cruise); + bool prev_speed_larger = m_prev.feedrate > block.feedrate.cruise; + float smaller_speed_factor = prev_speed_larger ? (block.feedrate.cruise / m_prev.feedrate) : (m_prev.feedrate / block.feedrate.cruise); // Pick the smaller of the nominal speeds. Higher speed shall not be achieved at the junction during coasting. - vmax_junction = prev_speed_larger ? block.feedrate.cruise : _prev.feedrate; + vmax_junction = prev_speed_larger ? block.feedrate.cruise : m_prev.feedrate; float v_factor = 1.0f; bool limited = false; @@ -1001,8 +1065,8 @@ namespace Slic3r { for (unsigned char a = X; a < Num_Axis; ++a) { // Limit an axis. We have to differentiate coasting from the reversal of an axis movement, or a full stop. - float v_exit = _prev.axis_feedrate[a]; - float v_entry = _curr.axis_feedrate[a]; + float v_exit = m_prev.axis_feedrate[a]; + float v_entry = m_curr.axis_feedrate[a]; if (prev_speed_larger) v_exit *= smaller_speed_factor; @@ -1044,23 +1108,23 @@ namespace Slic3r { float vmax_junction_threshold = vmax_junction * 0.99f; // Not coasting. The machine will stop and start the movements anyway, better to start the segment from start. - if ((_prev.safe_feedrate > vmax_junction_threshold) && (_curr.safe_feedrate > vmax_junction_threshold)) - vmax_junction = _curr.safe_feedrate; + if ((m_prev.safe_feedrate > vmax_junction_threshold) && (m_curr.safe_feedrate > vmax_junction_threshold)) + vmax_junction = m_curr.safe_feedrate; } - float v_allowable = Block::max_allowable_speed(-acceleration, _curr.safe_feedrate, distance); + float v_allowable = Block::max_allowable_speed(-acceleration, m_curr.safe_feedrate, distance); block.feedrate.entry = std::min(vmax_junction, v_allowable); block.max_entry_speed = vmax_junction; block.flags.nominal_length = (block.feedrate.cruise <= v_allowable); block.flags.recalculate = true; - block.safe_feedrate = _curr.safe_feedrate; + block.safe_feedrate = m_curr.safe_feedrate; // calculates block trapezoid block.calculate_trapezoid(); // updates previous - _prev = _curr; + m_prev = m_curr; // updates axis positions for (unsigned char a = X; a < Num_Axis; ++a) @@ -1091,8 +1155,8 @@ namespace Slic3r { #endif // ENABLE_MOVE_STATS // adds block to blocks list - _blocks.emplace_back(block); - _g1_line_ids.emplace_back(G1LineIdToBlockIdMap::value_type(get_g1_line_id(), (unsigned int)_blocks.size() - 1)); + m_blocks.emplace_back(block); + m_g1_line_ids.emplace_back(G1LineIdToBlockIdMap::value_type(get_g1_line_id(), (unsigned int)m_blocks.size() - 1)); } void GCodeTimeEstimator::_processG4(const GCodeReader::GCodeLine& line) @@ -1367,6 +1431,33 @@ namespace Slic3r { } } + bool GCodeTimeEstimator::_process_tags(const GCodeReader::GCodeLine& line) + { + std::string comment = line.comment(); + + // color change tag + size_t pos = comment.find(Color_Change_Tag); + if (pos != comment.npos) + { + _process_color_change_tag(); + return true; + } + + return false; + } + + void GCodeTimeEstimator::_process_color_change_tag() + { + PROFILE_FUNC(); + m_needs_color_times = true; + _calculate_time(); + if (m_color_time_cache != 0.0f) + { + m_color_times.push_back(m_color_time_cache); + m_color_time_cache = 0.0f; + } + } + void GCodeTimeEstimator::_simulate_st_synchronize() { PROFILE_FUNC(); @@ -1376,11 +1467,11 @@ namespace Slic3r { void GCodeTimeEstimator::_forward_pass() { PROFILE_FUNC(); - if (_blocks.size() > 1) + if (m_blocks.size() > 1) { - for (int i = _last_st_synchronized_block_id + 1; i < (int)_blocks.size() - 1; ++i) + for (int i = m_last_st_synchronized_block_id + 1; i < (int)m_blocks.size() - 1; ++i) { - _planner_forward_pass_kernel(_blocks[i], _blocks[i + 1]); + _planner_forward_pass_kernel(m_blocks[i], m_blocks[i + 1]); } } } @@ -1388,11 +1479,11 @@ namespace Slic3r { void GCodeTimeEstimator::_reverse_pass() { PROFILE_FUNC(); - if (_blocks.size() > 1) + if (m_blocks.size() > 1) { - for (int i = (int)_blocks.size() - 1; i >= _last_st_synchronized_block_id + 2; --i) + for (int i = (int)m_blocks.size() - 1; i >= m_last_st_synchronized_block_id + 2; --i) { - _planner_reverse_pass_kernel(_blocks[i - 1], _blocks[i]); + _planner_reverse_pass_kernel(m_blocks[i - 1], m_blocks[i]); } } } @@ -1444,9 +1535,9 @@ namespace Slic3r { Block* curr = nullptr; Block* next = nullptr; - for (int i = _last_st_synchronized_block_id + 1; i < (int)_blocks.size(); ++i) + for (int i = m_last_st_synchronized_block_id + 1; i < (int)m_blocks.size(); ++i) { - Block& b = _blocks[i]; + Block& b = m_blocks[i]; curr = next; next = &b; @@ -1517,7 +1608,7 @@ namespace Slic3r { { std::cout << MOVE_TYPE_STR[move.first]; std::cout << ": count " << move.second.count << " (" << 100.0f * (float)move.second.count / moves_count << "%)"; - std::cout << " - time: " << move.second.time << "s (" << 100.0f * move.second.time / _time << "%)"; + std::cout << " - time: " << move.second.time << "s (" << 100.0f * move.second.time / m_time << "%)"; std::cout << std::endl; } std::cout << std::endl; diff --git a/src/libslic3r/GCodeTimeEstimator.hpp b/src/libslic3r/GCodeTimeEstimator.hpp index 1fbc1c14bf..dd5d14b57a 100644 --- a/src/libslic3r/GCodeTimeEstimator.hpp +++ b/src/libslic3r/GCodeTimeEstimator.hpp @@ -22,6 +22,8 @@ namespace Slic3r { static const std::string Normal_Last_M73_Output_Placeholder_Tag; static const std::string Silent_Last_M73_Output_Placeholder_Tag; + static const std::string Color_Change_Tag; + enum EMode : unsigned char { Normal, @@ -215,17 +217,22 @@ namespace Slic3r { typedef std::vector G1LineIdToBlockIdMap; private: - EMode _mode; - GCodeReader _parser; - State _state; - Feedrates _curr; - Feedrates _prev; - BlocksList _blocks; + EMode m_mode; + GCodeReader m_parser; + State m_state; + Feedrates m_curr; + Feedrates m_prev; + BlocksList m_blocks; // Map between g1 line id and blocks id, used to speed up export of remaining times - G1LineIdToBlockIdMap _g1_line_ids; + G1LineIdToBlockIdMap m_g1_line_ids; // Index of the last block already st_synchronized - int _last_st_synchronized_block_id; - float _time; // s + int m_last_st_synchronized_block_id; + float m_time; // s + + // data to calculate color print times + bool m_needs_color_times; + std::vector m_color_times; + float m_color_time_cache; #if ENABLE_MOVE_STATS MovesStatsMap _moves_stats; @@ -341,6 +348,17 @@ namespace Slic3r { // Returns the estimated time, in minutes (integer) std::string get_time_minutes() const; + // Returns the estimated time, in seconds, for each color + std::vector get_color_times() const; + + // Returns the estimated time, in format DDd HHh MMm SSs, for each color + // If include_remaining==true the strings will be formatted as: "time for color (remaining time at color start)" + std::vector get_color_times_dhms(bool include_remaining) const; + + // Returns the estimated time, in minutes (integer), for each color + // If include_remaining==true the strings will be formatted as: "time for color (remaining time at color start)" + std::vector get_color_times_minutes(bool include_remaining) const; + // Return an estimate of the memory consumed by the time estimator. size_t memory_used() const; @@ -415,6 +433,13 @@ namespace Slic3r { // Processes T line (Select Tool) void _processT(const GCodeReader::GCodeLine& line); + // Processes the tags + // Returns true if any tag has been processed + bool _process_tags(const GCodeReader::GCodeLine& line); + + // Processes color change tag + void _process_color_change_tag(); + // Simulates firmware st_synchronize() call void _simulate_st_synchronize(); diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index ccc629723e..cc8a86a96c 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -912,7 +912,7 @@ MedialAxis::build(ThickPolylines* polylines) } */ - typedef const VD::vertex_type vert_t; + //typedef const VD::vertex_type vert_t; typedef const VD::edge_type edge_t; // collect valid edges (i.e. prune those not belonging to MAT) @@ -1409,6 +1409,63 @@ Transformation Transformation::operator * (const Transformation& other) const return Transformation(get_matrix() * other.get_matrix()); } +Transformation Transformation::volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox) +{ + Transformation out; + + if (instance_transformation.is_scaling_uniform()) { + // No need to run the non-linear least squares fitting for uniform scaling. + // Just set the inverse. + out.set_from_transform(instance_transformation.get_matrix(true).inverse()); + } + else if (is_rotation_ninety_degrees(instance_transformation.get_rotation())) + { + // Anisotropic scaling, rotation by multiples of ninety degrees. + Eigen::Matrix3d instance_rotation_trafo = + (Eigen::AngleAxisd(instance_transformation.get_rotation().z(), Vec3d::UnitZ()) * + Eigen::AngleAxisd(instance_transformation.get_rotation().y(), Vec3d::UnitY()) * + Eigen::AngleAxisd(instance_transformation.get_rotation().x(), Vec3d::UnitX())).toRotationMatrix(); + Eigen::Matrix3d volume_rotation_trafo = + (Eigen::AngleAxisd(-instance_transformation.get_rotation().x(), Vec3d::UnitX()) * + Eigen::AngleAxisd(-instance_transformation.get_rotation().y(), Vec3d::UnitY()) * + Eigen::AngleAxisd(-instance_transformation.get_rotation().z(), Vec3d::UnitZ())).toRotationMatrix(); + + // 8 corners of the bounding box. + auto pts = Eigen::MatrixXd(8, 3); + pts(0, 0) = bbox.min.x(); pts(0, 1) = bbox.min.y(); pts(0, 2) = bbox.min.z(); + pts(1, 0) = bbox.min.x(); pts(1, 1) = bbox.min.y(); pts(1, 2) = bbox.max.z(); + pts(2, 0) = bbox.min.x(); pts(2, 1) = bbox.max.y(); pts(2, 2) = bbox.min.z(); + pts(3, 0) = bbox.min.x(); pts(3, 1) = bbox.max.y(); pts(3, 2) = bbox.max.z(); + pts(4, 0) = bbox.max.x(); pts(4, 1) = bbox.min.y(); pts(4, 2) = bbox.min.z(); + pts(5, 0) = bbox.max.x(); pts(5, 1) = bbox.min.y(); pts(5, 2) = bbox.max.z(); + pts(6, 0) = bbox.max.x(); pts(6, 1) = bbox.max.y(); pts(6, 2) = bbox.min.z(); + pts(7, 0) = bbox.max.x(); pts(7, 1) = bbox.max.y(); pts(7, 2) = bbox.max.z(); + + // Corners of the bounding box transformed into the modifier mesh coordinate space, with inverse rotation applied to the modifier. + auto qs = pts * + (instance_rotation_trafo * + Eigen::Scaling(instance_transformation.get_scaling_factor().cwiseProduct(instance_transformation.get_mirror())) * + volume_rotation_trafo).inverse().transpose(); + // Fill in scaling based on least squares fitting of the bounding box corners. + Vec3d scale; + for (int i = 0; i < 3; ++i) + scale(i) = pts.col(i).dot(qs.col(i)) / pts.col(i).dot(pts.col(i)); + + out.set_rotation(Geometry::extract_euler_angles(volume_rotation_trafo)); + out.set_scaling_factor(Vec3d(std::abs(scale(0)), std::abs(scale(1)), std::abs(scale(2)))); + out.set_mirror(Vec3d(scale(0) > 0 ? 1. : -1, scale(1) > 0 ? 1. : -1, scale(2) > 0 ? 1. : -1)); + } + else + { + // General anisotropic scaling, general rotation. + // Keep the modifier mesh in the instance coordinate system, so the modifier mesh will not be aligned with the world. + // Scale it to get the required size. + out.set_scaling_factor(instance_transformation.get_scaling_factor().cwiseInverse()); + } + + return out; +} + Eigen::Quaterniond rotation_xyz_diff(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) { return diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 033c24a846..eec2673227 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -7,6 +7,9 @@ #include "Polygon.hpp" #include "Polyline.hpp" +// Serialization through the Cereal library +#include + #include "boost/polygon/voronoi.hpp" using boost::polygon::voronoi_builder; using boost::polygon::voronoi_diagram; @@ -108,6 +111,29 @@ inline bool segment_segment_intersection(const Vec2d &p1, const Vec2d &v1, const return true; } + +inline int segments_could_intersect( + const Slic3r::Point &ip1, const Slic3r::Point &ip2, + const Slic3r::Point &jp1, const Slic3r::Point &jp2) +{ + Vec2i64 iv = (ip2 - ip1).cast(); + Vec2i64 vij1 = (jp1 - ip1).cast(); + Vec2i64 vij2 = (jp2 - ip1).cast(); + int64_t tij1 = cross2(iv, vij1); + int64_t tij2 = cross2(iv, vij2); + int sij1 = (tij1 > 0) ? 1 : ((tij1 < 0) ? -1 : 0); // signum + int sij2 = (tij2 > 0) ? 1 : ((tij2 < 0) ? -1 : 0); + return sij1 * sij2; +} + +inline bool segments_intersect( + const Slic3r::Point &ip1, const Slic3r::Point &ip2, + const Slic3r::Point &jp1, const Slic3r::Point &jp2) +{ + return segments_could_intersect(ip1, ip2, jp1, jp2) <= 0 && + segments_could_intersect(jp1, jp2, ip1, ip2) <= 0; +} + Pointf3s convex_hull(Pointf3s points); Polygon convex_hull(Points points); Polygon convex_hull(const Polygons &polygons); @@ -258,6 +284,22 @@ public: const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const; Transformation operator * (const Transformation& other) const; + + // Find volume transformation, so that the chained (instance_trafo * volume_trafo) will be as close to identity + // as possible in least squares norm in regard to the 8 corners of bbox. + // Bounding box is expected to be centered around zero in all axes. + static Transformation volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox); + +private: + friend class cereal::access; + template void serialize(Archive & ar) { ar(m_offset, m_rotation, m_scaling_factor, m_mirror); } + explicit Transformation(int) : m_dirty(true) {} + template static void load_and_construct(Archive &ar, cereal::construct &construct) + { + // Calling a private constructor with special "int" parameter to indicate that no construction is necessary. + construct(1); + ar(construct.ptr()->m_offset, construct.ptr()->m_rotation, construct.ptr()->m_scaling_factor, construct.ptr()->m_mirror); + } }; // Rotation when going from the first coordinate system with rotation rot_xyz_from applied diff --git a/src/libslic3r/Int128.hpp b/src/libslic3r/Int128.hpp index 56dc5f461d..8dc9e012dd 100644 --- a/src/libslic3r/Int128.hpp +++ b/src/libslic3r/Int128.hpp @@ -37,6 +37,8 @@ * * *******************************************************************************/ +#ifndef SLIC3R_INT128_HPP +#define SLIC3R_INT128_HPP // #define SLIC3R_DEBUG // Make assert active if SLIC3R_DEBUG @@ -48,6 +50,8 @@ #endif #include +#include +#include #if ! defined(_MSC_VER) && defined(__SIZEOF_INT128__) #define HAS_INTRINSIC_128_TYPE @@ -293,3 +297,5 @@ public: return sign_determinant_2x2(p1, q1, p2, q2) * invert; } }; + +#endif // SLIC3R_INT128_HPP diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index c1d92c6bbd..a8160867af 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -128,7 +128,7 @@ void Layer::make_perimeters() && config.external_perimeter_speed == other_config.external_perimeter_speed && config.gap_fill_speed == other_config.gap_fill_speed && config.overhangs == other_config.overhangs - && config.serialize("perimeter_extrusion_width").compare(other_config.serialize("perimeter_extrusion_width")) == 0 + && config.opt_serialize("perimeter_extrusion_width") == other_config.opt_serialize("perimeter_extrusion_width") && config.thin_walls == other_config.thin_walls && config.external_perimeters_first == other_config.external_perimeters_first) { layerms.push_back(other_layerm); diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 19fbb3bb6e..caf8dc20f6 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -201,7 +201,7 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer) size_t n_groups = 0; for (size_t i = 0; i < bridges.size(); ++ i) { // A grup id for this bridge. - size_t group_id = (bridge_group[i] == -1) ? (n_groups ++) : bridge_group[i]; + size_t group_id = (bridge_group[i] == size_t(-1)) ? (n_groups ++) : bridge_group[i]; bridge_group[i] = group_id; // For all possibly overlaping bridges: for (size_t j = i + 1; j < bridges.size(); ++ j) { @@ -210,7 +210,7 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer) if (intersection(bridges_grown[i], bridges_grown[j], false).empty()) continue; // The two bridge regions intersect. Give them the same group id. - if (bridge_group[j] != -1) { + if (bridge_group[j] != size_t(-1)) { // The j'th bridge has been merged with some other bridge before. size_t group_id_new = bridge_group[j]; for (size_t k = 0; k < j; ++ k) diff --git a/src/libslic3r/Line.cpp b/src/libslic3r/Line.cpp index 02f1cb7c2b..baa04795ad 100644 --- a/src/libslic3r/Line.cpp +++ b/src/libslic3r/Line.cpp @@ -22,7 +22,6 @@ Linef3 transform(const Linef3& line, const Transform3d& t) bool Line::intersection_infinite(const Line &other, Point* point) const { Vec2d a1 = this->a.cast(); - Vec2d a2 = other.a.cast(); Vec2d v12 = (other.a - this->a).cast(); Vec2d v1 = (this->b - this->a).cast(); Vec2d v2 = (other.b - other.a).cast(); diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index 7e91ace328..212752883c 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -5,167 +5,219 @@ #include // for std::lock_guard #include // for std::function #include // for std::forward +#include +#include +#include + +#include "libslic3r.h" +#include "Point.hpp" namespace Slic3r { /// Handy little spin mutex for the cached meshes. /// Implements the "Lockable" concept -class SpinMutex { - std::atomic_flag m_flg; +class SpinMutex +{ + std::atomic_flag m_flg; static const /*constexpr*/ auto MO_ACQ = std::memory_order_acquire; static const /*constexpr*/ auto MO_REL = std::memory_order_release; + public: inline SpinMutex() { m_flg.clear(MO_REL); } - inline void lock() { while(m_flg.test_and_set(MO_ACQ)); } + inline void lock() { while (m_flg.test_and_set(MO_ACQ)) ; } inline bool try_lock() { return !m_flg.test_and_set(MO_ACQ); } inline void unlock() { m_flg.clear(MO_REL); } }; /// A wrapper class around arbitrary object that needs thread safe caching. -template class CachedObject { +template class CachedObject +{ public: // Method type which refreshes the object when it has been invalidated - using Setter = std::function; + using Setter = std::function; + private: - T m_obj; // the object itself - bool m_valid; // invalidation flag - SpinMutex m_lck; // to make the caching thread safe + T m_obj; // the object itself + bool m_valid; // invalidation flag + SpinMutex m_lck; // to make the caching thread safe + + // the setter will be called just before the object's const value is + // about to be retrieved. + std::function m_setter; - // the setter will be called just before the object's const value is about - // to be retrieved. - std::function m_setter; public: - // Forwarded constructor - template inline CachedObject(Setter fn, Args&&...args): - m_obj(std::forward(args)...), m_valid(false), m_setter(fn) {} + template + inline CachedObject(Setter fn, Args &&... args) + : m_obj(std::forward(args)...), m_valid(false), m_setter(fn) + {} - // invalidate the value of the object. The object will be refreshed at the - // next retrieval (Setter will be called). The data that is used in - // the setter function should be guarded as well during modification so the - // modification has to take place in fn. - inline void invalidate(std::function fn) { - std::lock_guard lck(m_lck); fn(); m_valid = false; + // invalidate the value of the object. The object will be refreshed at + // the next retrieval (Setter will be called). The data that is used in + // the setter function should be guarded as well during modification so + // the modification has to take place in fn. + inline void invalidate(std::function fn) + { + std::lock_guard lck(m_lck); + fn(); + m_valid = false; } // Get the const object properly updated. - inline const T& get() { + inline const T &get() + { std::lock_guard lck(m_lck); - if(!m_valid) { m_setter(m_obj); m_valid = true; } + if (!m_valid) { + m_setter(m_obj); + m_valid = true; + } return m_obj; } }; -/// An std compatible random access iterator which uses indices to the source -/// vector thus resistant to invalidation caused by relocations. It also "knows" -/// its container. No comparison is neccesary to the container "end()" iterator. -/// The template can be instantiated with a different value type than that of -/// the container's but the types must be compatible. E.g. a base class of the -/// contained objects is compatible. +/// An std compatible random access iterator which uses indices to the +/// source vector thus resistant to invalidation caused by relocations. It +/// also "knows" its container. No comparison is neccesary to the container +/// "end()" iterator. The template can be instantiated with a different +/// value type than that of the container's but the types must be +/// compatible. E.g. a base class of the contained objects is compatible. /// /// For a constant iterator, one can instantiate this template with a value /// type preceded with 'const'. -template -class IndexBasedIterator { +class IndexBasedIterator +{ static const size_t NONE = size_t(-1); std::reference_wrapper m_index_ref; - size_t m_idx = NONE; -public: + size_t m_idx = NONE; - using value_type = Value; - using pointer = Value *; - using reference = Value &; - using difference_type = long; +public: + using value_type = Value; + using pointer = Value *; + using reference = Value &; + using difference_type = long; using iterator_category = std::random_access_iterator_tag; - inline explicit - IndexBasedIterator(Vector& index, size_t idx): - m_index_ref(index), m_idx(idx) {} + inline explicit IndexBasedIterator(Vector &index, size_t idx) + : m_index_ref(index), m_idx(idx) + {} // Post increment - inline IndexBasedIterator operator++(int) { - IndexBasedIterator cpy(*this); ++m_idx; return cpy; + inline IndexBasedIterator operator++(int) + { + IndexBasedIterator cpy(*this); + ++m_idx; + return cpy; } - inline IndexBasedIterator operator--(int) { - IndexBasedIterator cpy(*this); --m_idx; return cpy; + inline IndexBasedIterator operator--(int) + { + IndexBasedIterator cpy(*this); + --m_idx; + return cpy; } - inline IndexBasedIterator& operator++() { - ++m_idx; return *this; + inline IndexBasedIterator &operator++() + { + ++m_idx; + return *this; } - inline IndexBasedIterator& operator--() { - --m_idx; return *this; + inline IndexBasedIterator &operator--() + { + --m_idx; + return *this; } - inline IndexBasedIterator& operator+=(difference_type l) { - m_idx += size_t(l); return *this; + inline IndexBasedIterator &operator+=(difference_type l) + { + m_idx += size_t(l); + return *this; } - inline IndexBasedIterator operator+(difference_type l) { - auto cpy = *this; cpy += l; return cpy; + inline IndexBasedIterator operator+(difference_type l) + { + auto cpy = *this; + cpy += l; + return cpy; } - inline IndexBasedIterator& operator-=(difference_type l) { - m_idx -= size_t(l); return *this; + inline IndexBasedIterator &operator-=(difference_type l) + { + m_idx -= size_t(l); + return *this; } - inline IndexBasedIterator operator-(difference_type l) { - auto cpy = *this; cpy -= l; return cpy; + inline IndexBasedIterator operator-(difference_type l) + { + auto cpy = *this; + cpy -= l; + return cpy; } operator difference_type() { return difference_type(m_idx); } /// Tesing the end of the container... this is not possible with std /// iterators. - inline bool is_end() const { return m_idx >= m_index_ref.get().size();} + inline bool is_end() const + { + return m_idx >= m_index_ref.get().size(); + } - inline Value & operator*() const { + inline Value &operator*() const + { assert(m_idx < m_index_ref.get().size()); return m_index_ref.get().operator[](m_idx); } - inline Value * operator->() const { + inline Value *operator->() const + { assert(m_idx < m_index_ref.get().size()); return &m_index_ref.get().operator[](m_idx); } /// If both iterators point past the container, they are equal... - inline bool operator ==(const IndexBasedIterator& other) { + inline bool operator==(const IndexBasedIterator &other) + { size_t e = m_index_ref.get().size(); return m_idx == other.m_idx || (m_idx >= e && other.m_idx >= e); } - inline bool operator !=(const IndexBasedIterator& other) { + inline bool operator!=(const IndexBasedIterator &other) + { return !(*this == other); } - inline bool operator <=(const IndexBasedIterator& other) { + inline bool operator<=(const IndexBasedIterator &other) + { return (m_idx < other.m_idx) || (*this == other); } - inline bool operator <(const IndexBasedIterator& other) { + inline bool operator<(const IndexBasedIterator &other) + { return m_idx < other.m_idx && (*this != other); } - inline bool operator >=(const IndexBasedIterator& other) { + inline bool operator>=(const IndexBasedIterator &other) + { return m_idx > other.m_idx || *this == other; } - inline bool operator >(const IndexBasedIterator& other) { + inline bool operator>(const IndexBasedIterator &other) + { return m_idx > other.m_idx && *this != other; } }; /// A very simple range concept implementation with iterator-like objects. -template class Range { +template class Range +{ It from, to; -public: +public: // The class is ready for range based for loops. It begin() const { return from; } It end() const { return to; } @@ -174,14 +226,174 @@ public: using Type = It; Range() = default; - Range(It &&b, It &&e): - from(std::forward(b)), to(std::forward(e)) {} + Range(It &&b, It &&e) + : from(std::forward(b)), to(std::forward(e)) + {} // Some useful container-like methods... inline size_t size() const { return end() - begin(); } - inline bool empty() const { return size() == 0; } + inline bool empty() const { return size() == 0; } }; +template bool all_of(const C &container) +{ + return std::all_of(container.begin(), + container.end(), + [](const typename C::value_type &v) { + return static_cast(v); + }); } +template struct remove_cvref +{ + using type = + typename std::remove_cv::type>::type; +}; + +template using remove_cvref_t = typename remove_cvref::type; + +template class C, class T> +class Container : public C> +{ +public: + explicit Container(size_t count, T &&initval) + : C>(count, initval) + {} +}; + +template using DefaultContainer = std::vector; + +/// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html +template class C = DefaultContainer> +inline C> linspace(const T &start, const T &stop, const I &n) +{ + Container vals(n, T()); + + T stride = (stop - start) / n; + size_t i = 0; + std::generate(vals.begin(), vals.end(), [&i, start, stride] { + return start + i++ * stride; + }); + + return vals; +} + +/// A set of equidistant values starting from 'start' (inclusive), ending +/// in the closest multiple of 'stride' less than or equal to 'end' and +/// leaving 'stride' space between each value. +/// Very similar to Matlab [start:stride:end] notation. +template class C = DefaultContainer> +inline C> grid(const T &start, const T &stop, const T &stride) +{ + Container vals(size_t(std::ceil((stop - start) / stride)), T()); + + int i = 0; + std::generate(vals.begin(), vals.end(), [&i, start, stride] { + return start + i++ * stride; + }); + + return vals; +} + + +// A shorter C++14 style form of the enable_if metafunction +template +using enable_if_t = typename std::enable_if::type; + +// ///////////////////////////////////////////////////////////////////////////// +// Type safe conversions to and from scaled and unscaled coordinates +// ///////////////////////////////////////////////////////////////////////////// + +// A meta-predicate which is true for integers wider than or equal to coord_t +template struct is_scaled_coord +{ + static const SLIC3R_CONSTEXPR bool value = + std::is_integral::value && + std::numeric_limits::digits >= + std::numeric_limits::digits; +}; + +// Meta predicates for floating, 'scaled coord' and generic arithmetic types +template +using FloatingOnly = enable_if_t::value, O>; + +template +using ScaledCoordOnly = enable_if_t::value, O>; + +template +using IntegerOnly = enable_if_t::value, O>; + +template +using ArithmeticOnly = enable_if_t::value, O>; + +// Semantics are the following: +// Upscaling (scaled()): only from floating point types (or Vec) to either +// floating point or integer 'scaled coord' coordinates. +// Downscaling (unscaled()): from arithmetic (or Vec) to floating point only + +// Conversion definition from unscaled to floating point scaled +template> +inline constexpr FloatingOnly scaled(const Tin &v) noexcept +{ + return Tout(v / Tin(SCALING_FACTOR)); +} + +// Conversion definition from unscaled to integer 'scaled coord'. +// TODO: is the rounding necessary? Here it is commented out to show that +// it can be different for integers but it does not have to be. Using +// std::round means loosing noexcept and constexpr modifiers +template> +inline constexpr ScaledCoordOnly scaled(const Tin &v) noexcept +{ + //return static_cast(std::round(v / SCALING_FACTOR)); + return Tout(v / Tin(SCALING_FACTOR)); +} + +// Conversion for Eigen vectors (N dimensional points) +template, + int...EigenArgs> +inline Eigen::Matrix, N, EigenArgs...> +scaled(const Eigen::Matrix &v) +{ + return (v / SCALING_FACTOR).template cast(); +} + +// Conversion from arithmetic scaled type to floating point unscaled +template, + class = FloatingOnly> +inline constexpr Tout unscaled(const Tin &v) noexcept +{ + return Tout(v * Tout(SCALING_FACTOR)); +} + +// Unscaling for Eigen vectors. Input base type can be arithmetic, output base +// type can only be floating point. +template, + class = FloatingOnly, + int...EigenArgs> +inline constexpr Eigen::Matrix +unscaled(const Eigen::Matrix &v) noexcept +{ + return v.template cast() * SCALING_FACTOR; +} + +template inline std::vector reserve_vector(size_t capacity) +{ + std::vector ret; + ret.reserve(capacity); + return ret; +} + +} // namespace Slic3r + #endif // MTUTILS_HPP diff --git a/src/libslic3r/MinAreaBoundingBox.cpp b/src/libslic3r/MinAreaBoundingBox.cpp new file mode 100644 index 0000000000..15c04517d0 --- /dev/null +++ b/src/libslic3r/MinAreaBoundingBox.cpp @@ -0,0 +1,151 @@ +#include "MinAreaBoundingBox.hpp" + +#include + +#if defined(_MSC_VER) && defined(__clang__) +#define BOOST_NO_CXX17_HDR_STRING_VIEW +#endif + +#include + +#include + +#if !defined(HAS_INTRINSIC_128_TYPE) || defined(__APPLE__) +#include +#endif + +#include +#include + +namespace libnest2d { + +template<> struct PointType { using Type = Slic3r::Point; }; +template<> struct CoordType { using Type = coord_t; }; +template<> struct ShapeTag { using Type = PolygonTag; }; +template<> struct ShapeTag { using Type = PolygonTag; }; +template<> struct ShapeTag { using Type = PathTag; }; +template<> struct ShapeTag { using Type = PointTag; }; +template<> struct ContourType { using Type = Slic3r::Points; }; +template<> struct ContourType { using Type = Slic3r::Points; }; + +namespace pointlike { + +template<> inline coord_t x(const Slic3r::Point& p) { return p.x(); } +template<> inline coord_t y(const Slic3r::Point& p) { return p.y(); } +template<> inline coord_t& x(Slic3r::Point& p) { return p.x(); } +template<> inline coord_t& y(Slic3r::Point& p) { return p.y(); } + +} // pointlike + +namespace shapelike { +template<> inline Slic3r::Points& contour(Slic3r::ExPolygon& sh) { return sh.contour.points; } +template<> inline const Slic3r::Points& contour(const Slic3r::ExPolygon& sh) { return sh.contour.points; } +template<> inline Slic3r::Points& contour(Slic3r::Polygon& sh) { return sh.points; } +template<> inline const Slic3r::Points& contour(const Slic3r::Polygon& sh) { return sh.points; } + +template<> Slic3r::Points::iterator begin(Slic3r::Points& pts, const PathTag&) { return pts.begin();} +template<> Slic3r::Points::const_iterator cbegin(const Slic3r::Points& pts, const PathTag&) { return pts.cbegin(); } +template<> Slic3r::Points::iterator end(Slic3r::Points& pts, const PathTag&) { return pts.end();} +template<> Slic3r::Points::const_iterator cend(const Slic3r::Points& pts, const PathTag&) { return pts.cend(); } + +template<> inline Slic3r::ExPolygon create(Slic3r::Points&& contour) +{ + Slic3r::ExPolygon expoly; expoly.contour.points.swap(contour); + return expoly; +} + +template<> inline Slic3r::Polygon create(Slic3r::Points&& contour) +{ + Slic3r::Polygon poly; poly.points.swap(contour); + return poly; +} + +} // shapelike +} // libnest2d + +namespace Slic3r { + +// Used as compute type. +using Unit = int64_t; + +#if !defined(HAS_INTRINSIC_128_TYPE) || defined(__APPLE__) +using Rational = boost::rational; +#else +using Rational = boost::rational<__int128>; +#endif + +MinAreaBoundigBox::MinAreaBoundigBox(const Polygon &p, PolygonLevel pc) +{ + const Polygon &chull = pc == pcConvex ? p : + libnest2d::sl::convexHull(p); + + libnest2d::RotatedBox box = + libnest2d::minAreaBoundingBox(chull); + + m_right = libnest2d::cast(box.right_extent()); + m_bottom = libnest2d::cast(box.bottom_extent()); + m_axis = box.axis(); +} + +MinAreaBoundigBox::MinAreaBoundigBox(const ExPolygon &p, PolygonLevel pc) +{ + const ExPolygon &chull = pc == pcConvex ? p : + libnest2d::sl::convexHull(p); + + libnest2d::RotatedBox box = + libnest2d::minAreaBoundingBox(chull); + + m_right = libnest2d::cast(box.right_extent()); + m_bottom = libnest2d::cast(box.bottom_extent()); + m_axis = box.axis(); +} + +MinAreaBoundigBox::MinAreaBoundigBox(const Points &pts, PolygonLevel pc) +{ + const Points &chull = pc == pcConvex ? pts : + libnest2d::sl::convexHull(pts); + + libnest2d::RotatedBox box = + libnest2d::minAreaBoundingBox(chull); + + m_right = libnest2d::cast(box.right_extent()); + m_bottom = libnest2d::cast(box.bottom_extent()); + m_axis = box.axis(); +} + +double MinAreaBoundigBox::angle_to_X() const +{ + double ret = std::atan2(m_axis.y(), m_axis.x()); + auto s = std::signbit(ret); + if (s) ret += 2 * PI; + return -ret; +} + +long double MinAreaBoundigBox::width() const +{ + return std::abs(m_bottom) / + std::sqrt(libnest2d::pl::magnsq(m_axis)); +} + +long double MinAreaBoundigBox::height() const +{ + return std::abs(m_right) / + std::sqrt(libnest2d::pl::magnsq(m_axis)); +} + +long double MinAreaBoundigBox::area() const +{ + long double asq = libnest2d::pl::magnsq(m_axis); + return m_bottom * m_right / asq; +} + +void remove_collinear_points(Polygon &p) +{ + p = libnest2d::removeCollinearPoints(p, Unit(0)); +} + +void remove_collinear_points(ExPolygon &p) +{ + p = libnest2d::removeCollinearPoints(p, Unit(0)); +} +} // namespace Slic3r diff --git a/src/libslic3r/MinAreaBoundingBox.hpp b/src/libslic3r/MinAreaBoundingBox.hpp new file mode 100644 index 0000000000..30d0e9799d --- /dev/null +++ b/src/libslic3r/MinAreaBoundingBox.hpp @@ -0,0 +1,59 @@ +#ifndef MINAREABOUNDINGBOX_HPP +#define MINAREABOUNDINGBOX_HPP + +#include "libslic3r/Point.hpp" + +namespace Slic3r { + +class Polygon; +class ExPolygon; + +void remove_collinear_points(Polygon& p); +void remove_collinear_points(ExPolygon& p); + +/// A class that holds a rotated bounding box. If instantiated with a polygon +/// type it will hold the minimum area bounding box for the given polygon. +/// If the input polygon is convex, the complexity is linear to the number of +/// points. Otherwise a convex hull of O(n*log(n)) has to be performed. +class MinAreaBoundigBox { + Point m_axis; + long double m_bottom = 0.0l, m_right = 0.0l; +public: + + // Polygons can be convex or simple (convex or concave with possible holes) + enum PolygonLevel { + pcConvex, pcSimple + }; + + // Constructors with various types of geometry data used in Slic3r. + // If the convexity is known apriory, pcConvex can be used to skip + // convex hull calculation. It is very important that the input polygons + // do NOT have any collinear points (except for the first and the last + // vertex being the same -- meaning a closed polygon for boost) + // To make sure this constraint is satisfied, you can call + // remove_collinear_points on the input polygon before handing over here) + explicit MinAreaBoundigBox(const Polygon&, PolygonLevel = pcSimple); + explicit MinAreaBoundigBox(const ExPolygon&, PolygonLevel = pcSimple); + explicit MinAreaBoundigBox(const Points&, PolygonLevel = pcSimple); + + // Returns the angle in radians needed for the box to be aligned with the + // X axis. Rotate the polygon by this angle and it will be aligned. + double angle_to_X() const; + + // The box width + long double width() const; + + // The box height + long double height() const; + + // The box area + long double area() const; + + // The axis of the rotated box. If the angle_to_X is not sufficiently + // precise, use this unnormalized direction vector. + const Point& axis() const { return m_axis; } +}; + +} + +#endif // MINAREABOUNDINGBOX_HPP diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 3b1bd5df22..5121a1fe21 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1,5 +1,6 @@ #include "Model.hpp" #include "Geometry.hpp" +#include "MTUtils.hpp" #include "Format/AMF.hpp" #include "Format/OBJ.hpp" @@ -20,23 +21,6 @@ namespace Slic3r { -unsigned int Model::s_auto_extruder_id = 1; - -size_t ModelBase::s_last_id = 0; - -// Unique object / instance ID for the wipe tower. -ModelID wipe_tower_object_id() -{ - static ModelBase mine; - return mine.id(); -} - -ModelID wipe_tower_instance_id() -{ - static ModelBase mine; - return mine.id(); -} - Model& Model::assign_copy(const Model &rhs) { this->copy_id(rhs); @@ -87,7 +71,20 @@ void Model::assign_new_unique_ids_recursive() model_object->assign_new_unique_ids_recursive(); } -Model Model::read_from_file(const std::string &input_file, DynamicPrintConfig *config, bool add_default_instances) +void Model::update_links_bottom_up_recursive() +{ + for (std::pair &kvp : this->materials) + kvp.second->set_model(this); + for (ModelObject *model_object : this->objects) { + model_object->set_model(this); + for (ModelInstance *model_instance : model_object->instances) + model_instance->set_model_object(model_object); + for (ModelVolume *model_volume : model_object->volumes) + model_volume->set_model_object(model_object); + } +} + +Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances, bool check_version) { Model model; @@ -100,11 +97,10 @@ Model Model::read_from_file(const std::string &input_file, DynamicPrintConfig *c result = load_stl(input_file.c_str(), &model); else if (boost::algorithm::iends_with(input_file, ".obj")) result = load_obj(input_file.c_str(), &model); - else if (!boost::algorithm::iends_with(input_file, ".zip.amf") && (boost::algorithm::iends_with(input_file, ".amf") || - boost::algorithm::iends_with(input_file, ".amf.xml"))) - result = load_amf(input_file.c_str(), config, &model); + else if (boost::algorithm::iends_with(input_file, ".amf") || boost::algorithm::iends_with(input_file, ".amf.xml")) + result = load_amf(input_file.c_str(), config, &model, check_version); else if (boost::algorithm::iends_with(input_file, ".3mf")) - result = load_3mf(input_file.c_str(), config, &model); + result = load_3mf(input_file.c_str(), config, &model, false); else if (boost::algorithm::iends_with(input_file, ".prusa")) result = load_prus(input_file.c_str(), &model); else @@ -125,15 +121,15 @@ Model Model::read_from_file(const std::string &input_file, DynamicPrintConfig *c return model; } -Model Model::read_from_archive(const std::string &input_file, DynamicPrintConfig *config, bool add_default_instances) +Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances, bool check_version) { Model model; bool result = false; if (boost::algorithm::iends_with(input_file, ".3mf")) - result = load_3mf(input_file.c_str(), config, &model); + result = load_3mf(input_file.c_str(), config, &model, check_version); else if (boost::algorithm::iends_with(input_file, ".zip.amf")) - result = load_amf(input_file.c_str(), config, &model); + result = load_amf(input_file.c_str(), config, &model, check_version); else throw std::runtime_error("Unknown file format. Input file must have .3mf or .zip.amf extension."); @@ -160,12 +156,6 @@ Model Model::read_from_archive(const std::string &input_file, DynamicPrintConfig return model; } -void Model::repair() -{ - for (ModelObject *o : this->objects) - o->repair(); -} - ModelObject* Model::add_object() { this->objects.emplace_back(new ModelObject(this)); @@ -227,7 +217,7 @@ bool Model::delete_object(ModelObject* object) return false; } -bool Model::delete_object(ModelID id) +bool Model::delete_object(ObjectID id) { if (id.id != 0) { size_t idx = 0; @@ -377,34 +367,44 @@ static bool _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb /* arrange objects preserving their instance count but altering their instance positions */ bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) -{ - // get the (transformed) size of each instance so that we take - // into account their different transformations when packing - Pointfs instance_sizes; - Pointfs instance_centers; - for (const ModelObject *o : this->objects) - for (size_t i = 0; i < o->instances.size(); ++ i) { - // an accurate snug bounding box around the transformed mesh. - BoundingBoxf3 bbox(o->instance_bounding_box(i, true)); - instance_sizes.emplace_back(to_2d(bbox.size())); - instance_centers.emplace_back(to_2d(bbox.center())); +{ + size_t count = 0; + for (auto obj : objects) count += obj->instances.size(); + + arrangement::ArrangePolygons input; + ModelInstancePtrs instances; + input.reserve(count); + instances.reserve(count); + for (ModelObject *mo : objects) + for (ModelInstance *minst : mo->instances) { + input.emplace_back(minst->get_arrange_polygon()); + instances.emplace_back(minst); } - - Pointfs positions; - if (! _arrange(instance_sizes, dist, bb, positions)) - return false; - - size_t idx = 0; - for (ModelObject *o : this->objects) { - for (ModelInstance *i : o->instances) { - Vec2d offset_xy = positions[idx] - instance_centers[idx]; - i->set_offset(Vec3d(offset_xy(0), offset_xy(1), i->get_offset(Z))); - ++idx; - } - o->invalidate_bounding_box(); + + arrangement::BedShapeHint bedhint; + coord_t bedwidth = 0; + + if (bb) { + bedwidth = scaled(bb->size().x()); + bedhint = arrangement::BedShapeHint( + BoundingBox(scaled(bb->min), scaled(bb->max))); } - return true; + arrangement::arrange(input, scaled(dist), bedhint); + + bool ret = true; + coord_t stride = bedwidth + bedwidth / 5; + + for(size_t i = 0; i < input.size(); ++i) { + if (input[i].bed_idx != 0) ret = false; + if (input[i].bed_idx >= 0) { + input[i].translation += Vec2crd{input[i].bed_idx * stride, 0}; + instances[i]->apply_arrange_result(input[i].translation, + input[i].rotation); + } + } + + return ret; } // Duplicate the entire model preserving instance relative positions. @@ -472,7 +472,7 @@ bool Model::looks_like_multipart_object() const if (obj->volumes.size() > 1 || obj->config.keys().size() > 1) return false; for (const ModelVolume *vol : obj->volumes) { - double zmin_this = vol->mesh.bounding_box().min(2); + double zmin_this = vol->mesh().bounding_box().min(2); if (zmin == std::numeric_limits::max()) zmin = zmin_this; else if (std::abs(zmin - zmin_this) > EPSILON) @@ -483,9 +483,20 @@ bool Model::looks_like_multipart_object() const return false; } +// Generate next extruder ID string, in the range of (1, max_extruders). +static inline std::string auto_extruder_id(unsigned int max_extruders, unsigned int &cntr) +{ + char str_extruder[64]; + sprintf(str_extruder, "%ud", cntr + 1); + if (++ cntr == max_extruders) + cntr = 0; + return str_extruder; +} + void Model::convert_multipart_object(unsigned int max_extruders) { - if (this->objects.empty()) + assert(this->objects.size() >= 2); + if (this->objects.size() < 2) return; ModelObject* object = new ModelObject(this); @@ -493,23 +504,34 @@ void Model::convert_multipart_object(unsigned int max_extruders) object->name = this->objects.front()->name; //FIXME copy the config etc? - reset_auto_extruder_id(); - - for (const ModelObject* o : this->objects) - for (const ModelVolume* v : o->volumes) - { - ModelVolume* new_v = object->add_volume(*v); - if (new_v != nullptr) - { - new_v->name = o->name; - new_v->config.set_deserialize("extruder", get_auto_extruder_id_as_string(max_extruders)); - new_v->translate(-o->origin_translation); + unsigned int extruder_counter = 0; + for (const ModelObject* o : this->objects) + for (const ModelVolume* v : o->volumes) { + // If there are more than one object, put all volumes together + // Each object may contain any number of volumes and instances + // The volumes transformations are relative to the object containing them... + Geometry::Transformation trafo_volume = v->get_transformation(); + // Revert the centering operation. + trafo_volume.set_offset(trafo_volume.get_offset() - o->origin_translation); + int counter = 1; + auto copy_volume = [o, max_extruders, &counter, &extruder_counter](ModelVolume *new_v) { + assert(new_v != nullptr); + new_v->name = o->name + "_" + std::to_string(counter++); + new_v->config.set_deserialize("extruder", auto_extruder_id(max_extruders, extruder_counter)); + return new_v; + }; + if (o->instances.empty()) { + copy_volume(object->add_volume(*v))->set_transformation(trafo_volume); + } else { + for (const ModelInstance* i : o->instances) + // ...so, transform everything to a common reference system (world) + copy_volume(object->add_volume(*v))->set_transformation(i->get_transformation() * trafo_volume); } } - for (const ModelInstance* i : this->objects.front()->instances) - object->add_instance(*i); - + object->add_instance(); + object->instances[0]->set_offset(object->raw_mesh_bounding_box().center()); + this->clear_objects(); this->objects.push_back(object); } @@ -533,32 +555,6 @@ void Model::adjust_min_z() } } -unsigned int Model::get_auto_extruder_id(unsigned int max_extruders) -{ - unsigned int id = s_auto_extruder_id; - if (id > max_extruders) { - // The current counter is invalid, likely due to switching the printer profiles - // to a profile with a lower number of extruders. - reset_auto_extruder_id(); - id = s_auto_extruder_id; - } else if (++ s_auto_extruder_id > max_extruders) { - reset_auto_extruder_id(); - } - return id; -} - -std::string Model::get_auto_extruder_id_as_string(unsigned int max_extruders) -{ - char str_extruder[64]; - sprintf(str_extruder, "%ud", get_auto_extruder_id(max_extruders)); - return str_extruder; -} - -void Model::reset_auto_extruder_id() -{ - s_auto_extruder_id = 1; -} - // Propose a filename including path derived from the ModelObject's input path. // If object's name is filled in, use the object name, otherwise use the input name. std::string Model::propose_export_file_name_and_path() const @@ -592,15 +588,20 @@ ModelObject::~ModelObject() // maintains the m_model pointer ModelObject& ModelObject::assign_copy(const ModelObject &rhs) { - this->copy_id(rhs); + assert(this->id().invalid() || this->id() == rhs.id()); + assert(this->config.id().invalid() || this->config.id() == rhs.config.id()); + this->copy_id(rhs); this->name = rhs.name; this->input_file = rhs.input_file; + // Copies the config's ID this->config = rhs.config; + assert(this->config.id() == rhs.config.id()); this->sla_support_points = rhs.sla_support_points; this->sla_points_status = rhs.sla_points_status; - this->layer_height_ranges = rhs.layer_height_ranges; + this->layer_config_ranges = rhs.layer_config_ranges; // #ys_FIXME_experiment this->layer_height_profile = rhs.layer_height_profile; + this->printable = rhs.printable; this->origin_translation = rhs.origin_translation; m_bounding_box = rhs.m_bounding_box; m_bounding_box_valid = rhs.m_bounding_box_valid; @@ -628,14 +629,17 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs) // maintains the m_model pointer ModelObject& ModelObject::assign_copy(ModelObject &&rhs) { + assert(this->id().invalid()); this->copy_id(rhs); this->name = std::move(rhs.name); this->input_file = std::move(rhs.input_file); + // Moves the config's ID this->config = std::move(rhs.config); + assert(this->config.id() == rhs.config.id()); this->sla_support_points = std::move(rhs.sla_support_points); this->sla_points_status = std::move(rhs.sla_points_status); - this->layer_height_ranges = std::move(rhs.layer_height_ranges); + this->layer_config_ranges = std::move(rhs.layer_config_ranges); // #ys_FIXME_experiment this->layer_height_profile = std::move(rhs.layer_height_profile); this->origin_translation = std::move(rhs.origin_translation); m_bounding_box = std::move(rhs.m_bounding_box); @@ -679,7 +683,7 @@ ModelVolume* ModelObject::add_volume(const TriangleMesh &mesh) { ModelVolume* v = new ModelVolume(this, mesh); this->volumes.push_back(v); - v->center_geometry(); + v->center_geometry_after_creation(); this->invalidate_bounding_box(); return v; } @@ -688,7 +692,7 @@ ModelVolume* ModelObject::add_volume(TriangleMesh &&mesh) { ModelVolume* v = new ModelVolume(this, std::move(mesh)); this->volumes.push_back(v); - v->center_geometry(); + v->center_geometry_after_creation(); this->invalidate_bounding_box(); return v; } @@ -697,8 +701,9 @@ ModelVolume* ModelObject::add_volume(const ModelVolume &other) { ModelVolume* v = new ModelVolume(this, other); this->volumes.push_back(v); - v->center_geometry(); - this->invalidate_bounding_box(); + // The volume should already be centered at this point of time when copying shared pointers of the triangle mesh and convex hull. +// v->center_geometry_after_creation(); +// this->invalidate_bounding_box(); return v; } @@ -706,7 +711,7 @@ ModelVolume* ModelObject::add_volume(const ModelVolume &other, TriangleMesh &&me { ModelVolume* v = new ModelVolume(this, other, std::move(mesh)); this->volumes.push_back(v); - v->center_geometry(); + v->center_geometry_after_creation(); this->invalidate_bounding_box(); return v; } @@ -827,7 +832,7 @@ TriangleMesh ModelObject::raw_mesh() const for (const ModelVolume *v : this->volumes) if (v->is_model_part()) { - TriangleMesh vol_mesh(v->mesh); + TriangleMesh vol_mesh(v->mesh()); vol_mesh.transform(v->get_matrix()); mesh.merge(vol_mesh); } @@ -840,7 +845,7 @@ TriangleMesh ModelObject::full_raw_mesh() const TriangleMesh mesh; for (const ModelVolume *v : this->volumes) { - TriangleMesh vol_mesh(v->mesh); + TriangleMesh vol_mesh(v->mesh()); vol_mesh.transform(v->get_matrix()); mesh.merge(vol_mesh); } @@ -854,7 +859,7 @@ const BoundingBoxf3& ModelObject::raw_mesh_bounding_box() const m_raw_mesh_bounding_box.reset(); for (const ModelVolume *v : this->volumes) if (v->is_model_part()) - m_raw_mesh_bounding_box.merge(v->mesh.transformed_bounding_box(v->get_matrix())); + m_raw_mesh_bounding_box.merge(v->mesh().transformed_bounding_box(v->get_matrix())); } return m_raw_mesh_bounding_box; } @@ -863,7 +868,7 @@ BoundingBoxf3 ModelObject::full_raw_mesh_bounding_box() const { BoundingBoxf3 bb; for (const ModelVolume *v : this->volumes) - bb.merge(v->mesh.transformed_bounding_box(v->get_matrix())); + bb.merge(v->mesh().transformed_bounding_box(v->get_matrix())); return bb; } @@ -881,7 +886,7 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const for (const ModelVolume *v : this->volumes) { if (v->is_model_part()) - m_raw_bounding_box.merge(v->mesh.transformed_bounding_box(inst_matrix * v->get_matrix())); + m_raw_bounding_box.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix())); } } return m_raw_bounding_box; @@ -895,7 +900,7 @@ BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx, bool dont_ for (ModelVolume *v : this->volumes) { if (v->is_model_part()) - bb.merge(v->mesh.transformed_bounding_box(inst_matrix * v->get_matrix())); + bb.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix())); } return bb; } @@ -908,21 +913,20 @@ Polygon ModelObject::convex_hull_2d(const Transform3d &trafo_instance) const Points pts; for (const ModelVolume *v : this->volumes) if (v->is_model_part()) { - const stl_file &stl = v->mesh.stl; Transform3d trafo = trafo_instance * v->get_matrix(); - if (stl.v_shared == nullptr) { + const indexed_triangle_set &its = v->mesh().its; + if (its.vertices.empty()) { // Using the STL faces. - for (unsigned int i = 0; i < stl.stats.number_of_facets; ++ i) { - const stl_facet &facet = stl.facet_start[i]; + const stl_file& stl = v->mesh().stl; + for (const stl_facet &facet : stl.facet_start) for (size_t j = 0; j < 3; ++ j) { Vec3d p = trafo * facet.vertex[j].cast(); pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); } - } } else { // Using the shared vertices should be a bit quicker than using the STL faces. - for (int i = 0; i < stl.stats.shared_vertices; ++ i) { - Vec3d p = trafo * stl.v_shared[i].cast(); + for (size_t i = 0; i < its.vertices.size(); ++ i) { + Vec3d p = trafo * its.vertices[i].cast(); pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); } } @@ -1039,11 +1043,12 @@ void ModelObject::mirror(Axis axis) this->invalidate_bounding_box(); } -void ModelObject::scale_mesh(const Vec3d &versor) +// This method could only be called before the meshes of this ModelVolumes are not shared! +void ModelObject::scale_mesh_after_creation(const Vec3d &versor) { for (ModelVolume *v : this->volumes) { - v->scale_geometry(versor); + v->scale_geometry_after_creation(versor); v->set_offset(versor.cwiseProduct(v->get_offset())); } this->invalidate_bounding_box(); @@ -1062,14 +1067,14 @@ size_t ModelObject::facets_count() const size_t num = 0; for (const ModelVolume *v : this->volumes) if (v->is_model_part()) - num += v->mesh.stl.stats.number_of_facets; + num += v->mesh().stl.stats.number_of_facets; return num; } bool ModelObject::needed_repair() const { for (const ModelVolume *v : this->volumes) - if (v->is_model_part() && v->mesh.needed_repair()) + if (v->is_model_part() && v->mesh().needed_repair()) return true; return false; } @@ -1087,7 +1092,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b if (keep_upper) { upper->set_model(nullptr); upper->sla_support_points.clear(); - upper->sla_points_status = sla::PointsStatus::None; + upper->sla_points_status = sla::PointsStatus::NoPoints; upper->clear_volumes(); upper->input_file = ""; } @@ -1095,7 +1100,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b if (keep_lower) { lower->set_model(nullptr); lower->sla_support_points.clear(); - lower->sla_points_status = sla::PointsStatus::None; + lower->sla_points_status = sla::PointsStatus::NoPoints; lower->clear_volumes(); lower->input_file = ""; } @@ -1135,11 +1140,12 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b // Transform the mesh by the combined transformation matrix. // Flip the triangles in case the composite transformation is left handed. - volume->mesh.transform(instance_matrix * volume_matrix, true); + TriangleMesh mesh(volume->mesh()); + mesh.transform(instance_matrix * volume_matrix, true); + volume->reset_mesh(); // Perform cut - volume->mesh.require_shared_vertices(); // TriangleMeshSlicer needs this - TriangleMeshSlicer tms(&volume->mesh); + TriangleMeshSlicer tms(&mesh); tms.cut(float(z), &upper_mesh, &lower_mesh); // Reset volume transformation except for offset @@ -1158,14 +1164,20 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b if (keep_upper && upper_mesh.facets_count() > 0) { ModelVolume* vol = upper->add_volume(upper_mesh); - vol->name = volume->name; - vol->config = volume->config; + vol->name = volume->name; + // Don't copy the config's ID. + static_cast(vol->config) = static_cast(volume->config); + assert(vol->config.id().valid()); + assert(vol->config.id() != volume->config.id()); vol->set_material(volume->material_id(), *volume->material()); } if (keep_lower && lower_mesh.facets_count() > 0) { ModelVolume* vol = lower->add_volume(lower_mesh); - vol->name = volume->name; - vol->config = volume->config; + vol->name = volume->name; + // Don't copy the config's ID. + static_cast(vol->config) = static_cast(volume->config); + assert(vol->config.id().valid()); + assert(vol->config.id() != volume->config.id()); vol->set_material(volume->material_id(), *volume->material()); // Compute the lower part instances' bounding boxes to figure out where to place @@ -1233,14 +1245,17 @@ void ModelObject::split(ModelObjectPtrs* new_objects) } ModelVolume* volume = this->volumes.front(); - TriangleMeshPtrs meshptrs = volume->mesh.split(); + TriangleMeshPtrs meshptrs = volume->mesh().split(); for (TriangleMesh *mesh : meshptrs) { mesh->repair(); // XXX: this seems to be the only real usage of m_model, maybe refactor this so that it's not needed? ModelObject* new_object = m_model->add_object(); new_object->name = this->name; - new_object->config = this->config; + // Don't copy the config's ID. + static_cast(new_object->config) = static_cast(this->config); + assert(new_object->config.id().valid()); + assert(new_object->config.id() != this->config.id()); new_object->instances.reserve(this->instances.size()); for (const ModelInstance *model_instance : this->instances) new_object->add_instance(*model_instance); @@ -1260,12 +1275,6 @@ void ModelObject::split(ModelObjectPtrs* new_objects) return; } -void ModelObject::repair() -{ - for (ModelVolume *v : this->volumes) - v->mesh.repair(); -} - // Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees, // then the scaling in world coordinate system is not representable by the Geometry::Transformation structure. // This situation is solved by baking in the instance transformation into the mesh vertices. @@ -1295,8 +1304,8 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx) // Adjust the meshes. // Transformation to be applied to the meshes. - Eigen::Matrix3d mesh_trafo_3x3 = reference_trafo.get_matrix(true, false, uniform_scaling, ! has_mirrorring).matrix().block<3, 3>(0, 0); - Transform3d volume_offset_correction = this->instances[instance_idx]->get_transformation().get_matrix().inverse() * reference_trafo.get_matrix(); + Eigen::Matrix3d mesh_trafo_3x3 = reference_trafo.get_matrix(true, false, uniform_scaling, ! has_mirrorring).matrix().block<3, 3>(0, 0); + Transform3d volume_offset_correction = this->instances[instance_idx]->get_transformation().get_matrix().inverse() * reference_trafo.get_matrix(); for (ModelVolume *model_volume : this->volumes) { const Geometry::Transformation volume_trafo = model_volume->get_transformation(); bool volume_left_handed = volume_trafo.is_left_handed(); @@ -1306,7 +1315,8 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx) double volume_new_scaling_factor = volume_uniform_scaling ? volume_trafo.get_scaling_factor().x() : 1.; // Transform the mesh. Matrix3d volume_trafo_3x3 = volume_trafo.get_matrix(true, false, volume_uniform_scaling, !volume_has_mirrorring).matrix().block<3, 3>(0, 0); - model_volume->transform_mesh(mesh_trafo_3x3 * volume_trafo_3x3, left_handed != volume_left_handed); + // Following method creates a new shared_ptr + model_volume->transform_this_mesh(mesh_trafo_3x3 * volume_trafo_3x3, left_handed != volume_left_handed); // Reset the rotation, scaling and mirroring. model_volume->set_rotation(Vec3d(0., 0., 0.)); model_volume->set_scaling_factor(Vec3d(volume_new_scaling_factor, volume_new_scaling_factor, volume_new_scaling_factor)); @@ -1347,13 +1357,9 @@ double ModelObject::get_instance_min_z(size_t instance_idx) const Transform3d mv = mi * v->get_matrix(); const TriangleMesh& hull = v->get_convex_hull(); - for (uint32_t f = 0; f < hull.stl.stats.number_of_facets; ++f) - { - const stl_facet* facet = hull.stl.facet_start + f; - min_z = std::min(min_z, Vec3d::UnitZ().dot(mv * facet->vertex[0].cast())); - min_z = std::min(min_z, Vec3d::UnitZ().dot(mv * facet->vertex[1].cast())); - min_z = std::min(min_z, Vec3d::UnitZ().dot(mv * facet->vertex[2].cast())); - } + for (const stl_facet &facet : hull.stl.facet_start) + for (int i = 0; i < 3; ++ i) + min_z = std::min(min_z, (mv * facet.vertex[i].cast()).z()); } return min_z + inst->get_offset(Z); @@ -1452,7 +1458,7 @@ std::string ModelObject::get_export_filename() const stl_stats ModelObject::get_object_stl_stats() const { if (this->volumes.size() == 1) - return this->volumes[0]->mesh.stl.stats; + return this->volumes[0]->mesh().stl.stats; stl_stats full_stats; memset(&full_stats, 0, sizeof(stl_stats)); @@ -1463,7 +1469,7 @@ stl_stats ModelObject::get_object_stl_stats() const if (volume->id() == this->volumes[0]->id()) continue; - const stl_stats& stats = volume->mesh.stl.stats; + const stl_stats& stats = volume->mesh().stl.stats; // initialize full_stats (for repaired errors) full_stats.degenerate_facets += stats.degenerate_facets; @@ -1531,30 +1537,32 @@ bool ModelVolume::is_splittable() const { // the call mesh.is_splittable() is expensive, so cache the value to calculate it only once if (m_is_splittable == -1) - m_is_splittable = (int)mesh.is_splittable(); + m_is_splittable = (int)this->mesh().is_splittable(); return m_is_splittable == 1; } -void ModelVolume::center_geometry() +void ModelVolume::center_geometry_after_creation() { - Vec3d shift = mesh.bounding_box().center(); + Vec3d shift = this->mesh().bounding_box().center(); if (!shift.isApprox(Vec3d::Zero())) { - mesh.translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); - m_convex_hull.translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); + if (m_mesh) + const_cast(m_mesh.get())->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); + if (m_convex_hull) + const_cast(m_convex_hull.get())->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); translate(shift); } } void ModelVolume::calculate_convex_hull() { - m_convex_hull = mesh.convex_hull_3d(); + m_convex_hull = std::make_shared(this->mesh().convex_hull_3d()); } int ModelVolume::get_mesh_errors_count() const { - const stl_stats& stats = this->mesh.stl.stats; + const stl_stats& stats = this->mesh().stl.stats; return stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + stats.facets_added + stats.facets_reversed + stats.backwards_edges; @@ -1562,7 +1570,7 @@ int ModelVolume::get_mesh_errors_count() const const TriangleMesh& ModelVolume::get_convex_hull() const { - return m_convex_hull; + return *m_convex_hull.get(); } ModelVolumeType ModelVolume::type_from_string(const std::string &s) @@ -1602,7 +1610,7 @@ std::string ModelVolume::type_to_string(const ModelVolumeType t) // This is useful to assign different materials to different volumes of an object. size_t ModelVolume::split(unsigned int max_extruders) { - TriangleMeshPtrs meshptrs = this->mesh.split(); + TriangleMeshPtrs meshptrs = this->mesh().split(); if (meshptrs.size() <= 1) { delete meshptrs.front(); return 1; @@ -1612,14 +1620,14 @@ size_t ModelVolume::split(unsigned int max_extruders) size_t ivolume = std::find(this->object->volumes.begin(), this->object->volumes.end(), this) - this->object->volumes.begin(); std::string name = this->name; - Model::reset_auto_extruder_id(); + unsigned int extruder_counter = 0; Vec3d offset = this->get_offset(); for (TriangleMesh *mesh : meshptrs) { mesh->repair(); if (idx == 0) { - this->mesh = std::move(*mesh); + this->set_mesh(std::move(*mesh)); this->calculate_convex_hull(); // Assign a new unique ID, so that a new GLVolume will be generated. this->set_new_unique_id(); @@ -1628,10 +1636,10 @@ size_t ModelVolume::split(unsigned int max_extruders) this->object->volumes.insert(this->object->volumes.begin() + (++ivolume), new ModelVolume(object, *this, std::move(*mesh))); this->object->volumes[ivolume]->set_offset(Vec3d::Zero()); - this->object->volumes[ivolume]->center_geometry(); + this->object->volumes[ivolume]->center_geometry_after_creation(); this->object->volumes[ivolume]->translate(offset); this->object->volumes[ivolume]->name = name + "_" + std::to_string(idx + 1); - this->object->volumes[ivolume]->config.set_deserialize("extruder", Model::get_auto_extruder_id_as_string(max_extruders)); + this->object->volumes[ivolume]->config.set_deserialize("extruder", auto_extruder_id(max_extruders, extruder_counter)); delete mesh; ++ idx; } @@ -1694,24 +1702,33 @@ void ModelVolume::mirror(Axis axis) set_mirror(mirror); } -void ModelVolume::scale_geometry(const Vec3d& versor) +// This method could only be called before the meshes of this ModelVolumes are not shared! +void ModelVolume::scale_geometry_after_creation(const Vec3d& versor) { - mesh.scale(versor); - m_convex_hull.scale(versor); + const_cast(m_mesh.get())->scale(versor); + const_cast(m_convex_hull.get())->scale(versor); } -void ModelVolume::transform_mesh(const Transform3d &mesh_trafo, bool fix_left_handed) +void ModelVolume::transform_this_mesh(const Transform3d &mesh_trafo, bool fix_left_handed) { - this->mesh.transform(mesh_trafo, fix_left_handed); - this->m_convex_hull.transform(mesh_trafo, fix_left_handed); + TriangleMesh mesh = this->mesh(); + mesh.transform(mesh_trafo, fix_left_handed); + this->set_mesh(std::move(mesh)); + TriangleMesh convex_hull = this->get_convex_hull(); + convex_hull.transform(mesh_trafo, fix_left_handed); + this->m_convex_hull = std::make_shared(std::move(convex_hull)); // Let the rest of the application know that the geometry changed, so the meshes have to be reloaded. this->set_new_unique_id(); } -void ModelVolume::transform_mesh(const Matrix3d &matrix, bool fix_left_handed) +void ModelVolume::transform_this_mesh(const Matrix3d &matrix, bool fix_left_handed) { - this->mesh.transform(matrix, fix_left_handed); - this->m_convex_hull.transform(matrix, fix_left_handed); + TriangleMesh mesh = this->mesh(); + mesh.transform(matrix, fix_left_handed); + this->set_mesh(std::move(mesh)); + TriangleMesh convex_hull = this->get_convex_hull(); + convex_hull.transform(matrix, fix_left_handed); + this->m_convex_hull = std::make_shared(std::move(convex_hull)); // Let the rest of the application know that the geometry changed, so the meshes have to be reloaded. this->set_new_unique_id(); } @@ -1766,6 +1783,36 @@ void ModelInstance::transform_polygon(Polygon* polygon) const polygon->scale(get_scaling_factor(X), get_scaling_factor(Y)); // scale around polygon origin } +arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const +{ + static const double SIMPLIFY_TOLERANCE_MM = 0.1; + + Vec3d rotation = get_rotation(); + rotation.z() = 0.; + Transform3d trafo_instance = + Geometry::assemble_transform(Vec3d::Zero(), rotation, + get_scaling_factor(), get_mirror()); + + Polygon p = get_object()->convex_hull_2d(trafo_instance); + + assert(!p.points.empty()); + + // this may happen for malformed models, see: + // https://github.com/prusa3d/PrusaSlicer/issues/2209 + if (!p.points.empty()) { + Polygons pp{p}; + pp = p.simplify(scaled(SIMPLIFY_TOLERANCE_MM)); + if (!pp.empty()) p = pp.front(); + } + + arrangement::ArrangePolygon ret; + ret.poly.contour = std::move(p); + ret.translation = Vec2crd{scaled(get_offset(X)), scaled(get_offset(Y))}; + ret.rotation = get_rotation(Z); + + return ret; +} + // Test whether the two models contain the same number of ModelObjects with the same set of IDs // ordered in the same order. In that case it is not necessary to kill the background processing. bool model_object_list_equal(const Model &model_old, const Model &model_new) @@ -1811,7 +1858,7 @@ bool model_volume_list_changed(const ModelObject &model_object_old, const ModelO if (!mv_old.get_matrix().isApprox(mv_new.get_matrix())) return true; - ++i_old; + ++ i_old; ++ i_new; } for (; i_old < model_object_old.volumes.size(); ++ i_old) { @@ -1829,25 +1876,55 @@ bool model_volume_list_changed(const ModelObject &model_object_old, const ModelO return false; } +extern bool model_has_multi_part_objects(const Model &model) +{ + for (const ModelObject *model_object : model.objects) + if (model_object->volumes.size() != 1 || ! model_object->volumes.front()->is_model_part()) + return true; + return false; +} + +extern bool model_has_advanced_features(const Model &model) +{ + auto config_is_advanced = [](const DynamicPrintConfig &config) { + return ! (config.empty() || (config.size() == 1 && config.cbegin()->first == "extruder")); + }; + for (const ModelObject *model_object : model.objects) { + // Is there more than one instance or advanced config data? + if (model_object->instances.size() > 1 || config_is_advanced(model_object->config)) + return true; + // Is there any modifier or advanced config data? + for (const ModelVolume* model_volume : model_object->volumes) + if (! model_volume->is_model_part() || config_is_advanced(model_volume->config)) + return true; + } + return false; +} + #ifndef NDEBUG // Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique. void check_model_ids_validity(const Model &model) { - std::set ids; - auto check = [&ids](ModelID id) { - assert(id.id > 0); + std::set ids; + auto check = [&ids](ObjectID id) { + assert(id.valid()); assert(ids.find(id) == ids.end()); ids.insert(id); }; for (const ModelObject *model_object : model.objects) { check(model_object->id()); - for (const ModelVolume *model_volume : model_object->volumes) + check(model_object->config.id()); + for (const ModelVolume *model_volume : model_object->volumes) { check(model_volume->id()); + check(model_volume->config.id()); + } for (const ModelInstance *model_instance : model_object->instances) check(model_instance->id()); } - for (const auto mm : model.materials) + for (const auto mm : model.materials) { check(mm.second->id()); + check(mm.second->config.id()); + } } void check_model_ids_equal(const Model &model1, const Model &model2) @@ -1858,10 +1935,13 @@ void check_model_ids_equal(const Model &model1, const Model &model2) const ModelObject &model_object1 = *model1.objects[idx_model]; const ModelObject &model_object2 = * model2.objects[idx_model]; assert(model_object1.id() == model_object2.id()); + assert(model_object1.config.id() == model_object2.config.id()); assert(model_object1.volumes.size() == model_object2.volumes.size()); assert(model_object1.instances.size() == model_object2.instances.size()); - for (size_t i = 0; i < model_object1.volumes.size(); ++ i) + for (size_t i = 0; i < model_object1.volumes.size(); ++ i) { assert(model_object1.volumes[i]->id() == model_object2.volumes[i]->id()); + assert(model_object1.volumes[i]->config.id() == model_object2.volumes[i]->config.id()); + } for (size_t i = 0; i < model_object1.instances.size(); ++ i) assert(model_object1.instances[i]->id() == model_object2.instances[i]->id()); } @@ -1872,9 +1952,22 @@ void check_model_ids_equal(const Model &model1, const Model &model2) for (; it1 != model1.materials.end(); ++ it1, ++ it2) { assert(it1->first == it2->first); // compare keys assert(it1->second->id() == it2->second->id()); + assert(it1->second->config.id() == it2->second->config.id()); } } } #endif /* NDEBUG */ } + +#if 0 +CEREAL_REGISTER_TYPE(Slic3r::ModelObject) +CEREAL_REGISTER_TYPE(Slic3r::ModelVolume) +CEREAL_REGISTER_TYPE(Slic3r::ModelInstance) +CEREAL_REGISTER_TYPE(Slic3r::Model) + +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::ModelObject) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::ModelVolume) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::ModelInstance) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::Model) +#endif diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 41bf5bd4be..422467e088 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -2,17 +2,30 @@ #define slic3r_Model_hpp_ #include "libslic3r.h" -#include "PrintConfig.hpp" +#include "Geometry.hpp" #include "Layer.hpp" +#include "ObjectID.hpp" #include "Point.hpp" -#include "TriangleMesh.hpp" +#include "PrintConfig.hpp" #include "Slicing.hpp" +#include "SLA/SLACommon.hpp" +#include "TriangleMesh.hpp" +#include "Arrange.hpp" + #include +#include #include #include #include -#include "Geometry.hpp" -#include + +namespace cereal { + class BinaryInputArchive; + class BinaryOutputArchive; + template void load_optional(BinaryInputArchive &ar, std::shared_ptr &ptr); + template void save_optional(BinaryOutputArchive &ar, const std::shared_ptr &ptr); + template void load_by_value(BinaryInputArchive &ar, T &obj); + template void save_by_value(BinaryOutputArchive &ar, const T &obj); +} namespace Slic3r { @@ -21,9 +34,57 @@ class ModelInstance; class ModelMaterial; class ModelObject; class ModelVolume; +class ModelWipeTower; class Print; class SLAPrint; +namespace UndoRedo { + class StackImpl; +} + +class ModelConfig : public ObjectBase, public DynamicPrintConfig +{ +private: + friend class cereal::access; + friend class UndoRedo::StackImpl; + friend class ModelObject; + friend class ModelVolume; + friend class ModelMaterial; + + // Constructors to be only called by derived classes. + // Default constructor to assign a unique ID. + explicit ModelConfig() {} + // Constructor with ignored int parameter to assign an invalid ID, to be replaced + // by an existing ID copied from elsewhere. + explicit ModelConfig(int) : ObjectBase(-1) {} + // Copy constructor copies the ID. + explicit ModelConfig(const ModelConfig &cfg) : ObjectBase(-1), DynamicPrintConfig(cfg) { this->copy_id(cfg); } + // Move constructor copies the ID. + explicit ModelConfig(ModelConfig &&cfg) : ObjectBase(-1), DynamicPrintConfig(std::move(cfg)) { this->copy_id(cfg); } + + ModelConfig& operator=(const ModelConfig &rhs) = default; + ModelConfig& operator=(ModelConfig &&rhs) = default; + + template void serialize(Archive &ar) { + ar(cereal::base_class(this)); + } +}; + +namespace Internal { + template + class StaticSerializationWrapper + { + public: + StaticSerializationWrapper(T &wrap) : wrapped(wrap) {} + private: + friend class cereal::access; + friend class UndoRedo::StackImpl; + template void load(Archive &ar) { cereal::load_by_value(ar, wrapped); } + template void save(Archive &ar) const { cereal::save_by_value(ar, wrapped); } + T& wrapped; + }; +} + typedef std::string t_model_material_id; typedef std::string t_model_material_attribute; typedef std::map t_model_material_attributes; @@ -33,74 +94,13 @@ typedef std::vector ModelObjectPtrs; typedef std::vector ModelVolumePtrs; typedef std::vector ModelInstancePtrs; -// Unique identifier of a Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial. -// Used to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject) -// Valid IDs are strictly positive (non zero). -// It is declared as an object, as some compilers (notably msvcc) consider a typedef size_t equivalent to size_t -// for parameter overload. -struct ModelID -{ - ModelID(size_t id) : id(id) {} - - bool operator==(const ModelID &rhs) const { return this->id == rhs.id; } - bool operator!=(const ModelID &rhs) const { return this->id != rhs.id; } - bool operator< (const ModelID &rhs) const { return this->id < rhs.id; } - bool operator> (const ModelID &rhs) const { return this->id > rhs.id; } - bool operator<=(const ModelID &rhs) const { return this->id <= rhs.id; } - bool operator>=(const ModelID &rhs) const { return this->id >= rhs.id; } - - bool valid() const { return id != 0; } - - size_t id; -}; - -// Unique object / instance ID for the wipe tower. -extern ModelID wipe_tower_object_id(); -extern ModelID wipe_tower_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). -// Achtung! The s_last_id counter is not thread safe, so it is expected, that the ModelBase derived instances -// are only instantiated from the main thread. -class ModelBase -{ -public: - ModelID id() const { return m_id; } - -protected: - // Constructors to be only called by derived classes. - // Default constructor to assign a unique ID. - ModelBase() : m_id(generate_new_id()) {} - // Constructor with ignored int parameter to assign an invalid ID, to be replaced - // by an existing ID copied from elsewhere. - ModelBase(int) : m_id(ModelID(0)) {} - - // Use with caution! - void set_new_unique_id() { m_id = generate_new_id(); } - void set_invalid_id() { m_id = 0; } - // Use with caution! - void copy_id(const ModelBase &rhs) { m_id = rhs.id(); } - - // Override this method if a ModelBase derived class owns other ModelBase derived instances. - void assign_new_unique_ids_recursive() { this->set_new_unique_id(); } - -private: - ModelID m_id; - - static inline ModelID generate_new_id() { return ModelID(++ s_last_id); } - static size_t s_last_id; - - friend ModelID wipe_tower_object_id(); - friend ModelID wipe_tower_instance_id(); -}; - -#define MODELBASE_DERIVED_COPY_MOVE_CLONE(TYPE) \ +#define OBJECTBASE_DERIVED_COPY_MOVE_CLONE(TYPE) \ /* Copy a model, copy the IDs. The Print::apply() will call the TYPE::copy() method */ \ /* to make a private copy for background processing. */ \ - static TYPE* new_copy(const TYPE &rhs) { return new TYPE(rhs); } \ - static TYPE* new_copy(TYPE &&rhs) { return new TYPE(std::move(rhs)); } \ - static TYPE make_copy(const TYPE &rhs) { return TYPE(rhs); } \ - static TYPE make_copy(TYPE &&rhs) { return TYPE(std::move(rhs)); } \ + static TYPE* new_copy(const TYPE &rhs) { auto *ret = new TYPE(rhs); assert(ret->id() == rhs.id()); return ret; } \ + static TYPE* new_copy(TYPE &&rhs) { auto *ret = new TYPE(std::move(rhs)); assert(ret->id() == rhs.id()); return ret; } \ + static TYPE make_copy(const TYPE &rhs) { TYPE ret(rhs); assert(ret.id() == rhs.id()); return ret; } \ + static TYPE make_copy(TYPE &&rhs) { TYPE ret(std::move(rhs)); assert(ret.id() == rhs.id()); return ret; } \ TYPE& assign_copy(const TYPE &rhs); \ TYPE& assign_copy(TYPE &&rhs); \ /* Copy a TYPE, generate new IDs. The front end will use this call. */ \ @@ -108,52 +108,63 @@ private: /* Default constructor assigning an invalid ID. */ \ auto obj = new TYPE(-1); \ obj->assign_clone(rhs); \ + assert(obj->id().valid() && obj->id() != rhs.id()); \ return obj; \ } \ TYPE make_clone(const TYPE &rhs) { \ /* Default constructor assigning an invalid ID. */ \ TYPE obj(-1); \ obj.assign_clone(rhs); \ + assert(obj.id().valid() && obj.id() != rhs.id()); \ return obj; \ } \ TYPE& assign_clone(const TYPE &rhs) { \ this->assign_copy(rhs); \ + assert(this->id().valid() && this->id() == rhs.id()); \ this->assign_new_unique_ids_recursive(); \ + assert(this->id().valid() && this->id() != rhs.id()); \ return *this; \ } -#define MODELBASE_DERIVED_PRIVATE_COPY_MOVE(TYPE) \ -private: \ - /* Private constructor with an unused int parameter will create a TYPE instance with an invalid ID. */ \ - explicit TYPE(int) : ModelBase(-1) {}; \ - void assign_new_unique_ids_recursive(); - // Material, which may be shared across multiple ModelObjects of a single Model. -class ModelMaterial : public ModelBase +class ModelMaterial final : public ObjectBase { public: // Attributes are defined by the AMF file format, but they don't seem to be used by Slic3r for any purpose. t_model_material_attributes attributes; // Dynamic configuration storage for the object specific configuration values, overriding the global configuration. - DynamicPrintConfig config; + ModelConfig config; Model* get_model() const { return m_model; } void apply(const t_model_material_attributes &attributes) { this->attributes.insert(attributes.begin(), attributes.end()); } -protected: - friend class Model; - // Constructor, which assigns a new unique ID. - ModelMaterial(Model *model) : m_model(model) {} - // Copy constructor copies the ID and m_model! - ModelMaterial(const ModelMaterial &rhs) = default; - void set_model(Model *model) { m_model = model; } - private: // Parent, owning this material. Model *m_model; - - ModelMaterial() = delete; + + // To be accessed by the Model. + friend class Model; + // Constructor, which assigns a new unique ID to the material and to its config. + ModelMaterial(Model *model) : m_model(model) { assert(this->id().valid()); } + // Copy constructor copies the IDs of the ModelMaterial and its config, and m_model! + ModelMaterial(const ModelMaterial &rhs) = default; + void set_model(Model *model) { m_model = model; } + void set_new_unique_id() { ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); } + + // To be accessed by the serialization and Undo/Redo code. + friend class cereal::access; + friend class UndoRedo::StackImpl; + // Create an object for deserialization, don't allocate IDs for ModelMaterial and its config. + ModelMaterial() : ObjectBase(-1), config(-1), m_model(nullptr) { assert(this->id().invalid()); assert(this->config.id().invalid()); } + template void serialize(Archive &ar) { + assert(this->id().invalid()); assert(this->config.id().invalid()); + Internal::StaticSerializationWrapper config_wrapper(config); + ar(attributes, config_wrapper); + // assert(this->id().valid()); assert(this->config.id().valid()); + } + + // Disabled methods. ModelMaterial(ModelMaterial &&rhs) = delete; ModelMaterial& operator=(const ModelMaterial &rhs) = delete; ModelMaterial& operator=(ModelMaterial &&rhs) = delete; @@ -163,9 +174,8 @@ private: // and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials. // Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed, // different rotation and different uniform scaling. -class ModelObject : public ModelBase +class ModelObject final : public ObjectBase { - friend class Model; public: std::string name; std::string input_file; // XXX: consider fs::path @@ -176,12 +186,14 @@ public: // ModelVolumes are owned by this ModelObject. ModelVolumePtrs volumes; // Configuration parameters specific to a single ModelObject, overriding the global Slic3r settings. - DynamicPrintConfig config; - // Variation of a layer thickness for spans of Z coordinates. - t_layer_height_ranges layer_height_ranges; + ModelConfig config; + // Variation of a layer thickness for spans of Z coordinates + optional parameter overrides. + t_layer_config_ranges layer_config_ranges; // Profile of increasing z to a layer height, to be linearly interpolated when calculating the layers. // The pairs of are packed into a 1D array. std::vector layer_height_profile; + // Whether or not this object is printable + bool printable; // This vector holds position of selected support points for SLA. The data are // saved in mesh coordinates to allow using them for several instances. @@ -189,7 +201,7 @@ public: std::vector sla_support_points; // To keep track of where the points came from (used for synchronization between // the SLA gizmo and the backend). - sla::PointsStatus sla_points_status = sla::PointsStatus::None; + sla::PointsStatus sla_points_status = sla::PointsStatus::NoPoints; /* This vector accumulates the total translation applied to the object by the center_around_origin() method. Callers might want to apply the same translation @@ -261,14 +273,14 @@ public: void rotate(double angle, const Vec3d& axis); void mirror(Axis axis); - void scale_mesh(const Vec3d& versor); + // This method could only be called before the meshes of this ModelVolumes are not shared! + void scale_mesh_after_creation(const Vec3d& versor); size_t materials_count() const; size_t facets_count() const; bool needed_repair() const; ModelObjectPtrs cut(size_t instance, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false); // Note: z is in world coordinates void split(ModelObjectPtrs* new_objects); - void repair(); // Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees, // then the scaling in world coordinate system is not representable by the Geometry::Transformation structure. // This situation is solved by baking in the instance transformation into the mesh vertices. @@ -286,31 +298,53 @@ public: std::string get_export_filename() const; - // Get full stl statistics for all object's meshes + // Get full stl statistics for all object's meshes stl_stats get_object_stl_stats() const; - // Get count of errors in the mesh( or all object's meshes, if volume index isn't defined) + // Get count of errors in the mesh( or all object's meshes, if volume index isn't defined) int get_mesh_errors_count(const int vol_idx = -1) const; -protected: - friend class Print; - friend class SLAPrint; - // Called by Print::apply() to set the model pointer after making a copy. - void set_model(Model *model) { m_model = model; } - private: - ModelObject(Model *model) : m_model(model), origin_translation(Vec3d::Zero()), - m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) {} - ~ModelObject(); + friend class Model; + // This constructor assigns new ID to this ModelObject and its config. + explicit ModelObject(Model* model) : m_model(model), printable(true), origin_translation(Vec3d::Zero()), + m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) + { assert(this->id().valid()); } + explicit ModelObject(int) : ObjectBase(-1), config(-1), m_model(nullptr), printable(true), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) + { assert(this->id().invalid()); assert(this->config.id().invalid()); } + ~ModelObject(); + void assign_new_unique_ids_recursive() override; - /* To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" */ - /* (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). */ - ModelObject(const ModelObject &rhs) : ModelBase(-1), m_model(rhs.m_model) { this->assign_copy(rhs); } - explicit ModelObject(ModelObject &&rhs) : ModelBase(-1) { this->assign_copy(std::move(rhs)); } - ModelObject& operator=(const ModelObject &rhs) { this->assign_copy(rhs); m_model = rhs.m_model; return *this; } - ModelObject& operator=(ModelObject &&rhs) { this->assign_copy(std::move(rhs)); m_model = rhs.m_model; return *this; } + // To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" + // (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). + ModelObject(const ModelObject &rhs) : ObjectBase(-1), config(-1), m_model(rhs.m_model) { + assert(this->id().invalid()); assert(this->config.id().invalid()); assert(rhs.id() != rhs.config.id()); + this->assign_copy(rhs); + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); + assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id()); + } + explicit ModelObject(ModelObject &&rhs) : ObjectBase(-1), config(-1) { + assert(this->id().invalid()); assert(this->config.id().invalid()); assert(rhs.id() != rhs.config.id()); + this->assign_copy(std::move(rhs)); + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); + assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id()); + } + ModelObject& operator=(const ModelObject &rhs) { + this->assign_copy(rhs); + m_model = rhs.m_model; + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); + assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id()); + return *this; + } + ModelObject& operator=(ModelObject &&rhs) { + this->assign_copy(std::move(rhs)); + m_model = rhs.m_model; + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); + assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id()); + return *this; + } + void set_new_unique_id() { ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); } - MODELBASE_DERIVED_COPY_MOVE_CLONE(ModelObject) - MODELBASE_DERIVED_PRIVATE_COPY_MOVE(ModelObject) + OBJECTBASE_DERIVED_COPY_MOVE_CLONE(ModelObject) // Parent object, owning this ModelObject. Set to nullptr here, so the macros above will have it initialized. Model *m_model = nullptr; @@ -321,7 +355,26 @@ private: mutable BoundingBoxf3 m_raw_bounding_box; mutable bool m_raw_bounding_box_valid; mutable BoundingBoxf3 m_raw_mesh_bounding_box; - mutable bool m_raw_mesh_bounding_box_valid; + mutable bool m_raw_mesh_bounding_box_valid; + + // Called by Print::apply() to set the model pointer after making a copy. + friend class Print; + friend class SLAPrint; + void set_model(Model *model) { m_model = model; } + + // Undo / Redo through the cereal serialization library + friend class cereal::access; + friend class UndoRedo::StackImpl; + // Used for deserialization -> Don't allocate any IDs for the ModelObject or its config. + ModelObject() : ObjectBase(-1), config(-1), m_model(nullptr), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) { + assert(this->id().invalid()); assert(this->config.id().invalid()); + } + template void serialize(Archive &ar) { + ar(cereal::base_class(this)); + Internal::StaticSerializationWrapper config_wrapper(config); + ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_height_profile, sla_support_points, sla_points_status, printable, origin_translation, + m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid); + } }; // Declared outside of ModelVolume, so it could be forward declared. @@ -335,15 +388,20 @@ enum class ModelVolumeType : int { // An object STL, or a modifier volume, over which a different set of parameters shall be applied. // ModelVolume instances are owned by a ModelObject. -class ModelVolume : public ModelBase +class ModelVolume final : public ObjectBase { public: std::string name; // The triangular model. - TriangleMesh mesh; + const TriangleMesh& mesh() const { return *m_mesh.get(); } + void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared(mesh); } + void set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared(std::move(mesh)); } + void set_mesh(std::shared_ptr &mesh) { m_mesh = mesh; } + void set_mesh(std::unique_ptr &&mesh) { m_mesh = std::move(mesh); } + void reset_mesh() { m_mesh = std::make_shared(); } // Configuration parameters specific to an object model geometry or a modifier volume, // overriding the global Slic3r settings and the ModelObject settings. - DynamicPrintConfig config; + ModelConfig config; // A parent object owning this modifier volume. ModelObject* get_object() const { return this->object; }; @@ -377,13 +435,16 @@ public: void rotate(double angle, const Vec3d& axis); void mirror(Axis axis); - void scale_geometry(const Vec3d& versor); + // This method could only be called before the meshes of this ModelVolumes are not shared! + void scale_geometry_after_creation(const Vec3d& versor); - // translates the mesh and the convex hull so that the origin of their vertices is in the center of this volume's bounding box - void center_geometry(); + // Translates the mesh and the convex hull so that the origin of their vertices is in the center of this volume's bounding box. + // Attention! This method may only be called just after ModelVolume creation! It must not be called once the TriangleMesh of this ModelVolume is shared! + void center_geometry_after_creation(); void calculate_convex_hull(); const TriangleMesh& get_convex_hull() const; + std::shared_ptr get_convex_hull_shared_ptr() const { return m_convex_hull; } // Get count of errors in the mesh int get_mesh_errors_count() const; @@ -421,64 +482,105 @@ public: const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } - using ModelBase::set_new_unique_id; + void set_new_unique_id() { ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); } protected: friend class Print; friend class SLAPrint; + friend class Model; friend class ModelObject; + // Copies IDs of both the ModelVolume and its config. explicit ModelVolume(const ModelVolume &rhs) = default; void set_model_object(ModelObject *model_object) { object = model_object; } - void transform_mesh(const Transform3d& t, bool fix_left_handed); - void transform_mesh(const Matrix3d& m, bool fix_left_handed); + void assign_new_unique_ids_recursive() override { ObjectBase::set_new_unique_id(); config.set_new_unique_id(); } + void transform_this_mesh(const Transform3d& t, bool fix_left_handed); + void transform_this_mesh(const Matrix3d& m, bool fix_left_handed); private: // Parent object owning this ModelVolume. - ModelObject* object; + ModelObject* object; + // The triangular model. + std::shared_ptr m_mesh; // Is it an object to be printed, or a modifier volume? - ModelVolumeType m_type; - t_model_material_id m_material_id; + ModelVolumeType m_type; + t_model_material_id m_material_id; // The convex hull of this model's mesh. - TriangleMesh m_convex_hull; - Geometry::Transformation m_transformation; + std::shared_ptr m_convex_hull; + Geometry::Transformation m_transformation; // flag to optimize the checking if the volume is splittable // -1 -> is unknown value (before first cheking) // 0 -> is not splittable // 1 -> is splittable - mutable int m_is_splittable{ -1 }; + mutable int m_is_splittable{ -1 }; - ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), m_type(ModelVolumeType::MODEL_PART), object(object) + ModelVolume(ModelObject *object, const TriangleMesh &mesh) : m_mesh(new TriangleMesh(mesh)), m_type(ModelVolumeType::MODEL_PART), object(object) { + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); if (mesh.stl.stats.number_of_facets > 1) calculate_convex_hull(); } ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : - mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), m_type(ModelVolumeType::MODEL_PART), object(object) {} + m_mesh(new TriangleMesh(std::move(mesh))), m_convex_hull(new TriangleMesh(std::move(convex_hull))), m_type(ModelVolumeType::MODEL_PART), object(object) { + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); + } // Copying an existing volume, therefore this volume will get a copy of the ID assigned. ModelVolume(ModelObject *object, const ModelVolume &other) : - ModelBase(other), // copy the ID - name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) + ObjectBase(other), + name(other.name), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) { + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); + assert(this->id() == other.id() && this->config.id() == other.config.id()); this->set_material_id(other.material_id()); } // Providing a new mesh, therefore this volume will get a new unique ID assigned. ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) : - name(other.name), mesh(std::move(mesh)), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) + name(other.name), m_mesh(new TriangleMesh(std::move(mesh))), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) { + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); + assert(this->id() != other.id() && this->config.id() == other.config.id()); this->set_material_id(other.material_id()); + this->config.set_new_unique_id(); if (mesh.stl.stats.number_of_facets > 1) calculate_convex_hull(); + assert(this->config.id().valid()); assert(this->config.id() != other.config.id()); assert(this->id() != this->config.id()); } ModelVolume& operator=(ModelVolume &rhs) = delete; + + friend class cereal::access; + friend class UndoRedo::StackImpl; + // Used for deserialization, therefore no IDs are allocated. + ModelVolume() : ObjectBase(-1), config(-1), object(nullptr) { + assert(this->id().invalid()); assert(this->config.id().invalid()); + } + template void load(Archive &ar) { + bool has_convex_hull; + ar(name, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); + cereal::load_by_value(ar, config); + assert(m_mesh); + if (has_convex_hull) { + cereal::load_optional(ar, m_convex_hull); + if (! m_convex_hull && ! m_mesh->empty()) + // The convex hull was released from the Undo / Redo stack to conserve memory. Recalculate it. + this->calculate_convex_hull(); + } else + m_convex_hull.reset(); + } + template void save(Archive &ar) const { + bool has_convex_hull = m_convex_hull.get() != nullptr; + ar(name, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); + cereal::save_by_value(ar, config); + if (has_convex_hull) + cereal::save_optional(ar, m_convex_hull); + } }; // A single instance of a ModelObject. // Knows the affine transformation of an object. -class ModelInstance : public ModelBase +class ModelInstance final : public ObjectBase { public: enum EPrintVolumeState : unsigned char @@ -495,6 +597,8 @@ private: public: // flag showing the position of this instance with respect to the print volume (set by Print::validate() using ModelObject::check_instances_print_volume_state()) EPrintVolumeState print_volume_state; + // Whether or not this instance is printable + bool printable; ModelObject* get_object() const { return this->object; } @@ -503,7 +607,7 @@ public: const Vec3d& get_offset() const { return m_transformation.get_offset(); } double get_offset(Axis axis) const { return m_transformation.get_offset(axis); } - + void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); } void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); } @@ -522,7 +626,7 @@ public: const Vec3d& get_mirror() const { return m_transformation.get_mirror(); } double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); } bool is_left_handed() const { return m_transformation.is_left_handed(); } - + void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); } void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); } @@ -539,11 +643,24 @@ public: const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } - bool is_printable() const { return print_volume_state == PVS_Inside; } + bool is_printable() const { return object->printable && printable && (print_volume_state == PVS_Inside); } + + // Getting the input polygon for arrange + arrangement::ArrangePolygon get_arrange_polygon() const; + + // Apply the arrange result on the ModelInstance + void apply_arrange_result(const Vec2crd& offs, double rotation) + { + // write the transformation data into the model instance + set_rotation(Z, rotation); + set_offset(X, unscale(offs(X))); + set_offset(Y, unscale(offs(Y))); + } protected: friend class Print; friend class SLAPrint; + friend class Model; friend class ModelObject; explicit ModelInstance(const ModelInstance &rhs) = default; @@ -554,15 +671,51 @@ private: ModelObject* object; // Constructor, which assigns a new unique ID. - explicit ModelInstance(ModelObject *object) : object(object), print_volume_state(PVS_Inside) {} + explicit ModelInstance(ModelObject* object) : print_volume_state(PVS_Inside), printable(true), object(object) { assert(this->id().valid()); } // Constructor, which assigns a new unique ID. explicit ModelInstance(ModelObject *object, const ModelInstance &other) : - m_transformation(other.m_transformation), object(object), print_volume_state(PVS_Inside) {} + m_transformation(other.m_transformation), print_volume_state(PVS_Inside), printable(true), object(object) {assert(this->id().valid() && this->id() != other.id());} - ModelInstance() = delete; explicit ModelInstance(ModelInstance &&rhs) = delete; ModelInstance& operator=(const ModelInstance &rhs) = delete; ModelInstance& operator=(ModelInstance &&rhs) = delete; + + friend class cereal::access; + friend class UndoRedo::StackImpl; + // Used for deserialization, therefore no IDs are allocated. + ModelInstance() : ObjectBase(-1), object(nullptr) { assert(this->id().invalid()); } + template void serialize(Archive &ar) { + ar(m_transformation, print_volume_state, printable); + } +}; + +class ModelWipeTower final : public ObjectBase +{ +public: + Vec2d position; + double rotation; + +private: + friend class cereal::access; + friend class UndoRedo::StackImpl; + friend class Model; + + // Constructors to be only called by derived classes. + // Default constructor to assign a unique ID. + explicit ModelWipeTower() {} + // Constructor with ignored int parameter to assign an invalid ID, to be replaced + // by an existing ID copied from elsewhere. + explicit ModelWipeTower(int) : ObjectBase(-1) {} + // Copy constructor copies the ID. + explicit ModelWipeTower(const ModelWipeTower &cfg) = default; + + // Disabled methods. + ModelWipeTower(ModelWipeTower &&rhs) = delete; + ModelWipeTower& operator=(const ModelWipeTower &rhs) = delete; + ModelWipeTower& operator=(ModelWipeTower &&rhs) = delete; + + // For serialization / deserialization of ModelWipeTower composed into another class into the Undo / Redo stack as a separate object. + template void serialize(Archive &ar) { ar(position, rotation); } }; // The print bed content. @@ -570,36 +723,32 @@ private: // and with multiple modifier meshes. // A model groups multiple objects, each object having possibly multiple instances, // all objects may share mutliple materials. -class Model : public ModelBase +class Model final : public ObjectBase { - static unsigned int s_auto_extruder_id; - public: // Materials are owned by a model and referenced by objects through t_model_material_id. // Single material may be shared by multiple models. ModelMaterialMap materials; // Objects are owned by a model. Each model may have multiple instances, each instance having its own transformation (shift, scale, rotation). ModelObjectPtrs objects; + // Wipe tower object. + ModelWipeTower wipe_tower; // Default constructor assigns a new ID to the model. - Model() {} + Model() { assert(this->id().valid()); } ~Model() { this->clear_objects(); this->clear_materials(); } /* To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" */ /* (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). */ - Model(const Model &rhs) : ModelBase(-1) { this->assign_copy(rhs); } - explicit Model(Model &&rhs) : ModelBase(-1) { this->assign_copy(std::move(rhs)); } - Model& operator=(const Model &rhs) { this->assign_copy(rhs); return *this; } - Model& operator=(Model &&rhs) { this->assign_copy(std::move(rhs)); return *this; } + Model(const Model &rhs) : ObjectBase(-1) { assert(this->id().invalid()); this->assign_copy(rhs); assert(this->id().valid()); assert(this->id() == rhs.id()); } + explicit Model(Model &&rhs) : ObjectBase(-1) { assert(this->id().invalid()); this->assign_copy(std::move(rhs)); assert(this->id().valid()); assert(this->id() == rhs.id()); } + Model& operator=(const Model &rhs) { this->assign_copy(rhs); assert(this->id().valid()); assert(this->id() == rhs.id()); return *this; } + Model& operator=(Model &&rhs) { this->assign_copy(std::move(rhs)); assert(this->id().valid()); assert(this->id() == rhs.id()); return *this; } - MODELBASE_DERIVED_COPY_MOVE_CLONE(Model) + OBJECTBASE_DERIVED_COPY_MOVE_CLONE(Model) - static Model read_from_file(const std::string &input_file, DynamicPrintConfig *config = nullptr, bool add_default_instances = true); - static Model read_from_archive(const std::string &input_file, DynamicPrintConfig *config, bool add_default_instances = true); - - /// Repair the ModelObjects of the current Model. - /// This function calls repair function on each TriangleMesh of each model object volume - void repair(); + static Model read_from_file(const std::string& input_file, DynamicPrintConfig* config = nullptr, bool add_default_instances = true, bool check_version = false); + static Model read_from_archive(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances = true, bool check_version = false); // Add a new ModelObject to this Model, generate a new ID for this ModelObject. ModelObject* add_object(); @@ -607,7 +756,7 @@ public: ModelObject* add_object(const char *name, const char *path, TriangleMesh &&mesh); ModelObject* add_object(const ModelObject &other); void delete_object(size_t idx); - bool delete_object(ModelID id); + bool delete_object(ObjectID id); bool delete_object(ModelObject* object); void clear_objects(); @@ -625,40 +774,45 @@ public: BoundingBoxf3 bounding_box() const; // Set the print_volume_state of PrintObject::instances, // return total number of printable objects. - unsigned int update_print_volume_state(const BoundingBoxf3 &print_volume); + unsigned int update_print_volume_state(const BoundingBoxf3 &print_volume); // Returns true if any ModelObject was modified. - bool center_instances_around_point(const Vec2d &point); - void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); } - TriangleMesh mesh() const; - bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL); + bool center_instances_around_point(const Vec2d &point); + void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); } + TriangleMesh mesh() const; + bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL); // Croaks if the duplicated objects do not fit the print bed. - void duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL); - void duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL); - void duplicate_objects_grid(size_t x, size_t y, coordf_t dist); + void duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL); + void duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL); + void duplicate_objects_grid(size_t x, size_t y, coordf_t dist); - bool looks_like_multipart_object() const; - void convert_multipart_object(unsigned int max_extruders); + bool looks_like_multipart_object() const; + void convert_multipart_object(unsigned int max_extruders); // Ensures that the min z of the model is not negative - void adjust_min_z(); + void adjust_min_z(); - void print_info() const { for (const ModelObject *o : this->objects) o->print_info(); } - - static unsigned int get_auto_extruder_id(unsigned int max_extruders); - static std::string get_auto_extruder_id_as_string(unsigned int max_extruders); - static void reset_auto_extruder_id(); + void print_info() const { for (const ModelObject *o : this->objects) o->print_info(); } // Propose an output file name & path based on the first printable object's name and source input file's path. - std::string propose_export_file_name_and_path() const; + std::string propose_export_file_name_and_path() const; // Propose an output path, replace extension. The new_extension shall contain the initial dot. - std::string propose_export_file_name_and_path(const std::string &new_extension) const; + std::string propose_export_file_name_and_path(const std::string &new_extension) const; private: - MODELBASE_DERIVED_PRIVATE_COPY_MOVE(Model) + explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); }; + void assign_new_unique_ids_recursive(); + void update_links_bottom_up_recursive(); + + friend class cereal::access; + friend class UndoRedo::StackImpl; + template void serialize(Archive &ar) { + Internal::StaticSerializationWrapper wipe_tower_wrapper(wipe_tower); + ar(materials, objects, wipe_tower_wrapper); + } }; -#undef MODELBASE_DERIVED_COPY_MOVE_CLONE -#undef MODELBASE_DERIVED_PRIVATE_COPY_MOVE +#undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE +#undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE // Test whether the two models contain the same number of ModelObjects with the same set of IDs // ordered in the same order. In that case it is not necessary to kill the background processing. @@ -672,12 +826,24 @@ extern bool model_object_list_extended(const Model &model_old, const Model &mode // than the old ModelObject. extern bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolumeType type); +// If the model has multi-part objects, then it is currently not supported by the SLA mode. +// Either the model cannot be loaded, or a SLA printer has to be activated. +extern bool model_has_multi_part_objects(const Model &model); +// If the model has advanced features, then it cannot be processed in simple mode. +extern bool model_has_advanced_features(const Model &model); + #ifndef NDEBUG // Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique. void check_model_ids_validity(const Model &model); void check_model_ids_equal(const Model &model1, const Model &model2); #endif /* NDEBUG */ +} // namespace Slic3r + +namespace cereal +{ + template struct specialize {}; + template struct specialize {}; } -#endif +#endif /* slic3r_Model_hpp_ */ diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp deleted file mode 100644 index b088a1f176..0000000000 --- a/src/libslic3r/ModelArrange.cpp +++ /dev/null @@ -1,1025 +0,0 @@ -#include "ModelArrange.hpp" -#include "Model.hpp" -#include "Geometry.hpp" -#include "SVG.hpp" - -#include - -#include -#include - -#include - -namespace Slic3r { - -namespace arr { - -using namespace libnest2d; - -// Only for debugging. Prints the model object vertices on stdout. -std::string toString(const Model& model, bool holes = true) { - std::stringstream ss; - - ss << "{\n"; - - for(auto objptr : model.objects) { - if(!objptr) continue; - - auto rmesh = objptr->raw_mesh(); - - for(auto objinst : objptr->instances) { - if(!objinst) continue; - - Slic3r::TriangleMesh tmpmesh = rmesh; - // CHECK_ME -> Is the following correct ? - tmpmesh.scale(objinst->get_scaling_factor()); - objinst->transform_mesh(&tmpmesh); - ExPolygons expolys = tmpmesh.horizontal_projection(); - for(auto& expoly_complex : expolys) { - - auto tmp = expoly_complex.simplify(1.0/SCALING_FACTOR); - if(tmp.empty()) continue; - auto expoly = tmp.front(); - expoly.contour.make_clockwise(); - for(auto& h : expoly.holes) h.make_counter_clockwise(); - - ss << "\t{\n"; - ss << "\t\t{\n"; - - for(auto v : expoly.contour.points) ss << "\t\t\t{" - << v(0) << ", " - << v(1) << "},\n"; - { - auto v = expoly.contour.points.front(); - ss << "\t\t\t{" << v(0) << ", " << v(1) << "},\n"; - } - ss << "\t\t},\n"; - - // Holes: - ss << "\t\t{\n"; - if(holes) for(auto h : expoly.holes) { - ss << "\t\t\t{\n"; - for(auto v : h.points) ss << "\t\t\t\t{" - << v(0) << ", " - << v(1) << "},\n"; - { - auto v = h.points.front(); - ss << "\t\t\t\t{" << v(0) << ", " << v(1) << "},\n"; - } - ss << "\t\t\t},\n"; - } - ss << "\t\t},\n"; - - ss << "\t},\n"; - } - } - } - - ss << "}\n"; - - return ss.str(); -} - -// Debugging: Save model to svg file. -void toSVG(SVG& svg, const Model& model) { - for(auto objptr : model.objects) { - if(!objptr) continue; - - auto rmesh = objptr->raw_mesh(); - - for(auto objinst : objptr->instances) { - if(!objinst) continue; - - Slic3r::TriangleMesh tmpmesh = rmesh; - tmpmesh.scale(objinst->get_scaling_factor()); - objinst->transform_mesh(&tmpmesh); - ExPolygons expolys = tmpmesh.horizontal_projection(); - svg.draw(expolys); - } - } -} - -namespace bgi = boost::geometry::index; - -using SpatElement = std::pair; -using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; -using ItemGroup = std::vector>; -template -using TPacker = typename placers::_NofitPolyPlacer; - -const double BIG_ITEM_TRESHOLD = 0.02; - -Box boundingBox(const Box& pilebb, const Box& ibb ) { - auto& pminc = pilebb.minCorner(); - auto& pmaxc = pilebb.maxCorner(); - auto& iminc = ibb.minCorner(); - auto& imaxc = ibb.maxCorner(); - PointImpl minc, maxc; - - setX(minc, std::min(getX(pminc), getX(iminc))); - setY(minc, std::min(getY(pminc), getY(iminc))); - - setX(maxc, std::max(getX(pmaxc), getX(imaxc))); - setY(maxc, std::max(getY(pmaxc), getY(imaxc))); - return Box(minc, maxc); -} - -// This is "the" object function which is evaluated many times for each vertex -// (decimated with the accuracy parameter) of each object. Therefore it is -// upmost crucial for this function to be as efficient as it possibly can be but -// at the same time, it has to provide reasonable results. -std::tuple -objfunc(const PointImpl& bincenter, - const shapelike::Shapes& merged_pile, - const Box& pilebb, - const ItemGroup& items, - const Item &item, - double bin_area, - double norm, // A norming factor for physical dimensions - // a spatial index to quickly get neighbors of the candidate item - const SpatIndex& spatindex, - const SpatIndex& smalls_spatindex, - const ItemGroup& remaining - ) -{ - // We will treat big items (compared to the print bed) differently - auto isBig = [bin_area](double a) { - return a/bin_area > BIG_ITEM_TRESHOLD ; - }; - - // Candidate item bounding box - auto ibb = sl::boundingBox(item.transformedShape()); - - // Calculate the full bounding box of the pile with the candidate item - auto fullbb = boundingBox(pilebb, ibb); - - // The bounding box of the big items (they will accumulate in the center - // of the pile - Box bigbb; - if(spatindex.empty()) bigbb = fullbb; - else { - auto boostbb = spatindex.bounds(); - boost::geometry::convert(boostbb, bigbb); - } - - // Will hold the resulting score - double score = 0; - - if(isBig(item.area()) || spatindex.empty()) { - // This branch is for the bigger items.. - - auto minc = ibb.minCorner(); // bottom left corner - auto maxc = ibb.maxCorner(); // top right corner - - // top left and bottom right corners - auto top_left = PointImpl{getX(minc), getY(maxc)}; - auto bottom_right = PointImpl{getX(maxc), getY(minc)}; - - // Now the distance of the gravity center will be calculated to the - // five anchor points and the smallest will be chosen. - std::array dists; - auto cc = fullbb.center(); // The gravity center - dists[0] = pl::distance(minc, cc); - dists[1] = pl::distance(maxc, cc); - dists[2] = pl::distance(ibb.center(), cc); - dists[3] = pl::distance(top_left, cc); - dists[4] = pl::distance(bottom_right, cc); - - // The smalles distance from the arranged pile center: - auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; - auto bindist = pl::distance(ibb.center(), bincenter) / norm; - dist = 0.8*dist + 0.2*bindist; - - // Density is the pack density: how big is the arranged pile - double density = 0; - - if(remaining.empty()) { - - auto mp = merged_pile; - mp.emplace_back(item.transformedShape()); - auto chull = sl::convexHull(mp); - - placers::EdgeCache ec(chull); - - double circ = ec.circumference() / norm; - double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm; - score = 0.5*circ + 0.5*bcirc; - - } else { - // Prepare a variable for the alignment score. - // This will indicate: how well is the candidate item aligned with - // its neighbors. We will check the alignment with all neighbors and - // return the score for the best alignment. So it is enough for the - // candidate to be aligned with only one item. - auto alignment_score = 1.0; - - density = std::sqrt((fullbb.width() / norm )* - (fullbb.height() / norm)); - auto querybb = item.boundingBox(); - - // Query the spatial index for the neighbors - std::vector result; - result.reserve(spatindex.size()); - if(isBig(item.area())) { - spatindex.query(bgi::intersects(querybb), - std::back_inserter(result)); - } else { - smalls_spatindex.query(bgi::intersects(querybb), - std::back_inserter(result)); - } - - for(auto& e : result) { // now get the score for the best alignment - auto idx = e.second; - Item& p = items[idx]; - auto parea = p.area(); - if(std::abs(1.0 - parea/item.area()) < 1e-6) { - auto bb = boundingBox(p.boundingBox(), ibb); - auto bbarea = bb.area(); - auto ascore = 1.0 - (item.area() + parea)/bbarea; - - if(ascore < alignment_score) alignment_score = ascore; - } - } - - // The final mix of the score is the balance between the distance - // from the full pile center, the pack density and the - // alignment with the neighbors - if(result.empty()) - score = 0.5 * dist + 0.5 * density; - else - score = 0.40 * dist + 0.40 * density + 0.2 * alignment_score; - } - } else { - // Here there are the small items that should be placed around the - // already processed bigger items. - // No need to play around with the anchor points, the center will be - // just fine for small items - score = pl::distance(ibb.center(), bigbb.center()) / norm; - } - - return std::make_tuple(score, fullbb); -} - -// Fill in the placer algorithm configuration with values carefully chosen for -// Slic3r. -template -void fillConfig(PConf& pcfg) { - - // Align the arranged pile into the center of the bin - pcfg.alignment = PConf::Alignment::CENTER; - - // Start placing the items from the center of the print bed - pcfg.starting_point = PConf::Alignment::CENTER; - - // TODO cannot use rotations until multiple objects of same geometry can - // handle different rotations - // arranger.useMinimumBoundigBoxRotation(); - pcfg.rotations = { 0.0 }; - - // The accuracy of optimization. - // Goes from 0.0 to 1.0 and scales performance as well - pcfg.accuracy = 0.65f; - - pcfg.parallel = true; -} - -// Type trait for an arranger class for different bin types (box, circle, -// polygon, etc...) -template -class AutoArranger {}; - - -// A class encapsulating the libnest2d Nester class and extending it with other -// management and spatial index structures for acceleration. -template -class _ArrBase { -protected: - - // Useful type shortcuts... - using Placer = TPacker; - using Selector = FirstFitSelection; - using Packer = Nester; - using PConfig = typename Packer::PlacementConfig; - using Distance = TCoord; - using Pile = sl::Shapes; - - Packer m_pck; - PConfig m_pconf; // Placement configuration - double m_bin_area; - SpatIndex m_rtree; // spatial index for the normal (bigger) objects - SpatIndex m_smallsrtree; // spatial index for only the smaller items - double m_norm; // A coefficient to scale distances - Pile m_merged_pile; // The already merged pile (vector of items) - Box m_pilebb; // The bounding box of the merged pile. - ItemGroup m_remaining; // Remaining items (m_items at the beginning) - ItemGroup m_items; // The items to be packed -public: - - _ArrBase(const TBin& bin, Distance dist, - std::function progressind, - std::function stopcond): - m_pck(bin, dist), m_bin_area(sl::area(bin)), - m_norm(std::sqrt(sl::area(bin))) - { - fillConfig(m_pconf); - - // Set up a callback that is called just before arranging starts - // This functionality is provided by the Nester class (m_pack). - m_pconf.before_packing = - [this](const Pile& merged_pile, // merged pile - const ItemGroup& items, // packed items - const ItemGroup& remaining) // future items to be packed - { - m_items = items; - m_merged_pile = merged_pile; - m_remaining = remaining; - - m_pilebb = sl::boundingBox(merged_pile); - - m_rtree.clear(); - m_smallsrtree.clear(); - - // We will treat big items (compared to the print bed) differently - auto isBig = [this](double a) { - return a/m_bin_area > BIG_ITEM_TRESHOLD ; - }; - - for(unsigned idx = 0; idx < items.size(); ++idx) { - Item& itm = items[idx]; - if(isBig(itm.area())) m_rtree.insert({itm.boundingBox(), idx}); - m_smallsrtree.insert({itm.boundingBox(), idx}); - } - }; - - m_pck.progressIndicator(progressind); - m_pck.stopCondition(stopcond); - } - - template inline IndexedPackGroup operator()(Args&&...args) { - m_rtree.clear(); - return m_pck.executeIndexed(std::forward(args)...); - } - - inline void preload(const PackGroup& pg) { - m_pconf.alignment = PConfig::Alignment::DONT_ALIGN; - m_pconf.object_function = nullptr; // drop the special objectfunction - m_pck.preload(pg); - - // Build the rtree for queries to work - for(const ItemGroup& grp : pg) - for(unsigned idx = 0; idx < grp.size(); ++idx) { - Item& itm = grp[idx]; - m_rtree.insert({itm.boundingBox(), idx}); - } - - m_pck.configure(m_pconf); - } - - bool is_colliding(const Item& item) { - if(m_rtree.empty()) return false; - std::vector result; - m_rtree.query(bgi::intersects(item.boundingBox()), - std::back_inserter(result)); - return !result.empty(); - } -}; - -// Arranger specialization for a Box shaped bin. -template<> class AutoArranger: public _ArrBase { -public: - - AutoArranger(const Box& bin, Distance dist, - std::function progressind = [](unsigned){}, - std::function stopcond = [](){return false;}): - _ArrBase(bin, dist, progressind, stopcond) - { - - // Here we set up the actual object function that calls the common - // object function for all bin shapes than does an additional inside - // check for the arranged pile. - m_pconf.object_function = [this, bin] (const Item &item) { - - auto result = objfunc(bin.center(), - m_merged_pile, - m_pilebb, - m_items, - item, - m_bin_area, - m_norm, - m_rtree, - m_smallsrtree, - m_remaining); - - double score = std::get<0>(result); - auto& fullbb = std::get<1>(result); - - double miss = Placer::overfit(fullbb, bin); - miss = miss > 0? miss : 0; - score += miss*miss; - - return score; - }; - - m_pck.configure(m_pconf); - } -}; - -using lnCircle = libnest2d::_Circle; - -inline lnCircle to_lnCircle(const Circle& circ) { - return lnCircle({circ.center()(0), circ.center()(1)}, circ.radius()); -} - -// Arranger specialization for circle shaped bin. -template<> class AutoArranger: public _ArrBase { -public: - - AutoArranger(const lnCircle& bin, Distance dist, - std::function progressind = [](unsigned){}, - std::function stopcond = [](){return false;}): - _ArrBase(bin, dist, progressind, stopcond) { - - // As with the box, only the inside check is different. - m_pconf.object_function = [this, &bin] (const Item &item) { - - auto result = objfunc(bin.center(), - m_merged_pile, - m_pilebb, - m_items, - item, - m_bin_area, - m_norm, - m_rtree, - m_smallsrtree, - m_remaining); - - double score = std::get<0>(result); - - auto isBig = [this](const Item& itm) { - return itm.area()/m_bin_area > BIG_ITEM_TRESHOLD ; - }; - - if(isBig(item)) { - auto mp = m_merged_pile; - mp.push_back(item.transformedShape()); - auto chull = sl::convexHull(mp); - double miss = Placer::overfit(chull, bin); - if(miss < 0) miss = 0; - score += miss*miss; - } - - return score; - }; - - m_pck.configure(m_pconf); - } -}; - -// Arranger specialization for a generalized polygon. -// Warning: this is unfinished business. It may or may not work. -template<> class AutoArranger: public _ArrBase { -public: - AutoArranger(const PolygonImpl& bin, Distance dist, - std::function progressind = [](unsigned){}, - std::function stopcond = [](){return false;}): - _ArrBase(bin, dist, progressind, stopcond) - { - m_pconf.object_function = [this, &bin] (const Item &item) { - - auto binbb = sl::boundingBox(bin); - auto result = objfunc(binbb.center(), - m_merged_pile, - m_pilebb, - m_items, - item, - m_bin_area, - m_norm, - m_rtree, - m_smallsrtree, - m_remaining); - double score = std::get<0>(result); - - return score; - }; - - m_pck.configure(m_pconf); - } -}; - -// Specialization with no bin. In this case the arranger should just arrange -// all objects into a minimum sized pile but it is not limited by a bin. A -// consequence is that only one pile should be created. -template<> class AutoArranger: public _ArrBase { -public: - - AutoArranger(Distance dist, std::function progressind, - std::function stopcond): - _ArrBase(Box(0, 0), dist, progressind, stopcond) - { - this->m_pconf.object_function = [this] (const Item &item) { - - auto result = objfunc({0, 0}, - m_merged_pile, - m_pilebb, - m_items, - item, - 0, - m_norm, - m_rtree, - m_smallsrtree, - m_remaining); - return std::get<0>(result); - }; - - this->m_pck.configure(m_pconf); - } -}; - -// A container which stores a pointer to the 3D object and its projected -// 2D shape from top view. -using ShapeData2D = std::vector>; - -ShapeData2D projectModelFromTop(const Slic3r::Model &model, const WipeTowerInfo& wti) { - ShapeData2D ret; - - // Count all the items on the bin (all the object's instances) - auto s = std::accumulate(model.objects.begin(), model.objects.end(), - size_t(0), [](size_t s, ModelObject* o) - { - return s + o->instances.size(); - }); - - ret.reserve(s); - - for(ModelObject* objptr : model.objects) { - if (! objptr->instances.empty()) { - - // TODO export the exact 2D projection. Cannot do it as libnest2d - // does not support concave shapes (yet). - ClipperLib::Path clpath; - - // Object instances should carry the same scaling and - // x, y rotation that is why we use the first instance. - { - ModelInstance *finst = objptr->instances.front(); - Vec3d rotation = finst->get_rotation(); - rotation.z() = 0.; - Transform3d trafo_instance = Geometry::assemble_transform(Vec3d::Zero(), rotation, finst->get_scaling_factor(), finst->get_mirror()); - Polygon p = objptr->convex_hull_2d(trafo_instance); - assert(! p.points.empty()); - - // this may happen for malformed models, see: https://github.com/prusa3d/PrusaSlicer/issues/2209 - if (p.points.empty()) - continue; - - p.reverse(); - assert(!p.is_counter_clockwise()); - p.append(p.first_point()); - clpath = Slic3rMultiPoint_to_ClipperPath(p); - } - - Vec3d rotation0 = objptr->instances.front()->get_rotation(); - rotation0(2) = 0.; - for(ModelInstance* objinst : objptr->instances) { - ClipperLib::Polygon pn; - pn.Contour = clpath; - - // Efficient conversion to item. - Item item(std::move(pn)); - - // Invalid geometries would throw exceptions when arranging - if(item.vertexCount() > 3) { - item.rotation(float(Geometry::rotation_diff_z(rotation0, objinst->get_rotation()))), - item.translation({ - ClipperLib::cInt(objinst->get_offset(X)/SCALING_FACTOR), - ClipperLib::cInt(objinst->get_offset(Y)/SCALING_FACTOR) - }); - ret.emplace_back(objinst, item); - } - } - } - } - - // The wipe tower is a separate case (in case there is one), let's duplicate the code - if (wti.is_wipe_tower) { - Points pts; - pts.emplace_back(coord_t(scale_(0.)), coord_t(scale_(0.))); - pts.emplace_back(coord_t(scale_(wti.bb_size(0))), coord_t(scale_(0.))); - pts.emplace_back(coord_t(scale_(wti.bb_size(0))), coord_t(scale_(wti.bb_size(1)))); - pts.emplace_back(coord_t(scale_(-0.)), coord_t(scale_(wti.bb_size(1)))); - pts.emplace_back(coord_t(scale_(-0.)), coord_t(scale_(0.))); - Polygon p(std::move(pts)); - ClipperLib::Path clpath = Slic3rMultiPoint_to_ClipperPath(p); - ClipperLib::Polygon pn; - pn.Contour = clpath; - // Efficient conversion to item. - Item item(std::move(pn)); - item.rotation(wti.rotation), - item.translation({ - ClipperLib::cInt(wti.pos(0)/SCALING_FACTOR), - ClipperLib::cInt(wti.pos(1)/SCALING_FACTOR) - }); - ret.emplace_back(nullptr, item); - } - - return ret; -} - -// Apply the calculated translations and rotations (currently disabled) to the -// Model object instances. -void applyResult( - IndexedPackGroup::value_type& group, - Coord batch_offset, - ShapeData2D& shapemap, - WipeTowerInfo& wti) -{ - for(auto& r : group) { - auto idx = r.first; // get the original item index - Item& item = r.second; // get the item itself - - // Get the model instance from the shapemap using the index - ModelInstance *inst_ptr = shapemap[idx].first; - - // Get the transformation data from the item object and scale it - // appropriately - auto off = item.translation(); - Radians rot = item.rotation(); - - Vec3d foff(off.X*SCALING_FACTOR + batch_offset, - off.Y*SCALING_FACTOR, - inst_ptr ? inst_ptr->get_offset()(Z) : 0.); - - if (inst_ptr) { - // write the transformation data into the model instance - inst_ptr->set_rotation(Z, rot); - inst_ptr->set_offset(foff); - } - else { // this is the wipe tower - we will modify the struct with the info - // and leave it up to the called to actually move the wipe tower - wti.pos = Vec2d(foff(0), foff(1)); - wti.rotation = rot; - } - } -} - -// Get the type of bed geometry from a simple vector of points. -BedShapeHint bedShape(const Polyline &bed) { - BedShapeHint ret; - - auto x = [](const Point& p) { return p(0); }; - auto y = [](const Point& p) { return p(1); }; - - auto width = [x](const BoundingBox& box) { - return x(box.max) - x(box.min); - }; - - auto height = [y](const BoundingBox& box) { - return y(box.max) - y(box.min); - }; - - auto area = [&width, &height](const BoundingBox& box) { - double w = width(box); - double h = height(box); - return w*h; - }; - - auto poly_area = [](Polyline p) { - Polygon pp; pp.points.reserve(p.points.size() + 1); - pp.points = std::move(p.points); - pp.points.emplace_back(pp.points.front()); - return std::abs(pp.area()); - }; - - auto distance_to = [x, y](const Point& p1, const Point& p2) { - double dx = x(p2) - x(p1); - double dy = y(p2) - y(p1); - return std::sqrt(dx*dx + dy*dy); - }; - - auto bb = bed.bounding_box(); - - auto isCircle = [bb, distance_to](const Polyline& polygon) { - auto center = bb.center(); - std::vector vertex_distances; - double avg_dist = 0; - for (auto pt: polygon.points) - { - double distance = distance_to(center, pt); - vertex_distances.push_back(distance); - avg_dist += distance; - } - - avg_dist /= vertex_distances.size(); - - Circle ret(center, avg_dist); - for(auto el : vertex_distances) - { - if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) { - ret = Circle(); - break; - } - } - - return ret; - }; - - auto parea = poly_area(bed); - - if( (1.0 - parea/area(bb)) < 1e-3 ) { - ret.type = BedShapeType::BOX; - ret.shape.box = bb; - } - else if(auto c = isCircle(bed)) { - ret.type = BedShapeType::CIRCLE; - ret.shape.circ = c; - } else { - ret.type = BedShapeType::IRREGULAR; - ret.shape.polygon = bed; - } - - // Determine the bed shape by hand - return ret; -} - -// The final client function to arrange the Model. A progress indicator and -// a stop predicate can be also be passed to control the process. -bool arrange(Model &model, // The model with the geometries - WipeTowerInfo& wti, // Wipe tower info - coord_t min_obj_distance, // Has to be in scaled (clipper) measure - const Polyline &bed, // The bed geometry. - BedShapeHint bedhint, // Hint about the bed geometry type. - bool first_bin_only, // What to do is not all items fit. - - // Controlling callbacks. - std::function progressind, - std::function stopcondition) -{ - bool ret = true; - - // Get the 2D projected shapes with their 3D model instance pointers - auto shapemap = arr::projectModelFromTop(model, wti); - - // Copy the references for the shapes only as the arranger expects a - // sequence of objects convertible to Item or ClipperPolygon - std::vector> shapes; - shapes.reserve(shapemap.size()); - std::for_each(shapemap.begin(), shapemap.end(), - [&shapes] (ShapeData2D::value_type& it) - { - shapes.push_back(std::ref(it.second)); - }); - - IndexedPackGroup result; - - // If there is no hint about the shape, we will try to guess - if(bedhint.type == BedShapeType::WHO_KNOWS) bedhint = bedShape(bed); - - BoundingBox bbb(bed); - - auto& cfn = stopcondition; - - auto binbb = Box({ - static_cast(bbb.min(0)), - static_cast(bbb.min(1)) - }, - { - static_cast(bbb.max(0)), - static_cast(bbb.max(1)) - }); - - switch(bedhint.type) { - case BedShapeType::BOX: { - - // Create the arranger for the box shaped bed - AutoArranger arrange(binbb, min_obj_distance, progressind, cfn); - - // Arrange and return the items with their respective indices within the - // input sequence. - result = arrange(shapes.begin(), shapes.end()); - break; - } - case BedShapeType::CIRCLE: { - - auto c = bedhint.shape.circ; - auto cc = to_lnCircle(c); - - AutoArranger arrange(cc, min_obj_distance, progressind, cfn); - result = arrange(shapes.begin(), shapes.end()); - break; - } - case BedShapeType::IRREGULAR: - case BedShapeType::WHO_KNOWS: { - - using P = libnest2d::PolygonImpl; - - auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); - P irrbed = sl::create(std::move(ctour)); - - AutoArranger

arrange(irrbed, min_obj_distance, progressind, cfn); - - // Arrange and return the items with their respective indices within the - // input sequence. - result = arrange(shapes.begin(), shapes.end()); - break; - } - }; - - if(result.empty() || stopcondition()) return false; - - if(first_bin_only) { - applyResult(result.front(), 0, shapemap, wti); - } else { - - const auto STRIDE_PADDING = 1.2; - - Coord stride = static_cast(STRIDE_PADDING* - binbb.width()*SCALING_FACTOR); - Coord batch_offset = 0; - - for(auto& group : result) { - applyResult(group, batch_offset, shapemap, wti); - - // Only the first pack group can be placed onto the print bed. The - // other objects which could not fit will be placed next to the - // print bed - batch_offset += stride; - } - } - - for(auto objptr : model.objects) objptr->invalidate_bounding_box(); - - return ret && result.size() == 1; -} - -void find_new_position(const Model &model, - ModelInstancePtrs toadd, - coord_t min_obj_distance, - const Polyline &bed, - WipeTowerInfo& wti) -{ - // Get the 2D projected shapes with their 3D model instance pointers - auto shapemap = arr::projectModelFromTop(model, wti); - - // Copy the references for the shapes only as the arranger expects a - // sequence of objects convertible to Item or ClipperPolygon - PackGroup preshapes; preshapes.emplace_back(); - ItemGroup shapes; - preshapes.front().reserve(shapemap.size()); - - std::vector shapes_ptr; shapes_ptr.reserve(toadd.size()); - IndexedPackGroup result; - - // If there is no hint about the shape, we will try to guess - BedShapeHint bedhint = bedShape(bed); - - BoundingBox bbb(bed); - - auto binbb = Box({ - static_cast(bbb.min(0)), - static_cast(bbb.min(1)) - }, - { - static_cast(bbb.max(0)), - static_cast(bbb.max(1)) - }); - - for(auto it = shapemap.begin(); it != shapemap.end(); ++it) { - if(std::find(toadd.begin(), toadd.end(), it->first) == toadd.end()) { - if(it->second.isInside(binbb)) // just ignore items which are outside - preshapes.front().emplace_back(std::ref(it->second)); - } - else { - shapes_ptr.emplace_back(it->first); - shapes.emplace_back(std::ref(it->second)); - } - } - - auto try_first_to_center = [&shapes, &shapes_ptr, &binbb] - (std::function is_colliding, - std::function preload) - { - // Try to put the first item to the center, as the arranger will not - // do this for us. - auto shptrit = shapes_ptr.begin(); - for(auto shit = shapes.begin(); shit != shapes.end(); ++shit, ++shptrit) - { - // Try to place items to the center - Item& itm = *shit; - auto ibb = itm.boundingBox(); - auto d = binbb.center() - ibb.center(); - itm.translate(d); - if(!is_colliding(itm)) { - preload(itm); - - auto offset = itm.translation(); - Radians rot = itm.rotation(); - ModelInstance *minst = *shptrit; - Vec3d foffset(offset.X*SCALING_FACTOR, - offset.Y*SCALING_FACTOR, - minst->get_offset()(Z)); - - // write the transformation data into the model instance - minst->set_rotation(Z, rot); - minst->set_offset(foffset); - - shit = shapes.erase(shit); - shptrit = shapes_ptr.erase(shptrit); - break; - } - } - }; - - switch(bedhint.type) { - case BedShapeType::BOX: { - - // Create the arranger for the box shaped bed - AutoArranger arrange(binbb, min_obj_distance); - - if(!preshapes.front().empty()) { // If there is something on the plate - arrange.preload(preshapes); - try_first_to_center( - [&arrange](const Item& itm) {return arrange.is_colliding(itm);}, - [&arrange](Item& itm) { arrange.preload({{itm}}); } - ); - } - - // Arrange and return the items with their respective indices within the - // input sequence. - result = arrange(shapes.begin(), shapes.end()); - break; - } - case BedShapeType::CIRCLE: { - - auto c = bedhint.shape.circ; - auto cc = to_lnCircle(c); - - // Create the arranger for the box shaped bed - AutoArranger arrange(cc, min_obj_distance); - - if(!preshapes.front().empty()) { // If there is something on the plate - arrange.preload(preshapes); - try_first_to_center( - [&arrange](const Item& itm) {return arrange.is_colliding(itm);}, - [&arrange](Item& itm) { arrange.preload({{itm}}); } - ); - } - - // Arrange and return the items with their respective indices within the - // input sequence. - result = arrange(shapes.begin(), shapes.end()); - break; - } - case BedShapeType::IRREGULAR: - case BedShapeType::WHO_KNOWS: { - using P = libnest2d::PolygonImpl; - - auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); - P irrbed = sl::create(std::move(ctour)); - - AutoArranger

arrange(irrbed, min_obj_distance); - - if(!preshapes.front().empty()) { // If there is something on the plate - arrange.preload(preshapes); - try_first_to_center( - [&arrange](const Item& itm) {return arrange.is_colliding(itm);}, - [&arrange](Item& itm) { arrange.preload({{itm}}); } - ); - } - - // Arrange and return the items with their respective indices within the - // input sequence. - result = arrange(shapes.begin(), shapes.end()); - break; - } - }; - - // Now we go through the result which will contain the fixed and the moving - // polygons as well. We will have to search for our item. - - const auto STRIDE_PADDING = 1.2; - Coord stride = Coord(STRIDE_PADDING*binbb.width()*SCALING_FACTOR); - Coord batch_offset = 0; - - for(auto& group : result) { - for(auto& r : group) if(r.first < shapes.size()) { - Item& resultitem = r.second; - unsigned idx = r.first; - auto offset = resultitem.translation(); - Radians rot = resultitem.rotation(); - ModelInstance *minst = shapes_ptr[idx]; - Vec3d foffset(offset.X*SCALING_FACTOR + batch_offset, - offset.Y*SCALING_FACTOR, - minst->get_offset()(Z)); - - // write the transformation data into the model instance - minst->set_rotation(Z, rot); - minst->set_offset(foffset); - } - batch_offset += stride; - } -} - -} - - -} diff --git a/src/libslic3r/ModelArrange.hpp b/src/libslic3r/ModelArrange.hpp deleted file mode 100644 index b61443da07..0000000000 --- a/src/libslic3r/ModelArrange.hpp +++ /dev/null @@ -1,95 +0,0 @@ -#ifndef MODELARRANGE_HPP -#define MODELARRANGE_HPP - -#include "Model.hpp" - -namespace Slic3r { - -class Model; - -namespace arr { - -class Circle { - Point center_; - double radius_; -public: - - inline Circle(): center_(0, 0), radius_(std::nan("")) {} - inline Circle(const Point& c, double r): center_(c), radius_(r) {} - - inline double radius() const { return radius_; } - inline const Point& center() const { return center_; } - inline operator bool() { return !std::isnan(radius_); } -}; - -enum class BedShapeType { - BOX, - CIRCLE, - IRREGULAR, - WHO_KNOWS -}; - -struct BedShapeHint { - BedShapeType type; - /*union*/ struct { // I know but who cares... - Circle circ; - BoundingBox box; - Polyline polygon; - } shape; -}; - -BedShapeHint bedShape(const Polyline& bed); - -struct WipeTowerInfo { - bool is_wipe_tower = false; - Vec2d pos; - Vec2d bb_size; - double rotation; -}; - -/** - * \brief Arranges the model objects on the screen. - * - * The arrangement considers multiple bins (aka. print beds) for placing all - * the items provided in the model argument. If the items don't fit on one - * print bed, the remaining will be placed onto newly created print beds. - * The first_bin_only parameter, if set to true, disables this behavior and - * makes sure that only one print bed is filled and the remaining items will be - * untouched. When set to false, the items which could not fit onto the - * print bed will be placed next to the print bed so the user should see a - * pile of items on the print bed and some other piles outside the print - * area that can be dragged later onto the print bed as a group. - * - * \param model The model object with the 3D content. - * \param dist The minimum distance which is allowed for any pair of items - * on the print bed in any direction. - * \param bb The bounding box of the print bed. It corresponds to the 'bin' - * for bin packing. - * \param first_bin_only This parameter controls whether to place the - * remaining items which do not fit onto the print area next to the print - * bed or leave them untouched (let the user arrange them by hand or remove - * them). - * \param progressind Progress indicator callback called when an object gets - * packed. The unsigned argument is the number of items remaining to pack. - * \param stopcondition A predicate returning true if abort is needed. - */ -bool arrange(Model &model, - WipeTowerInfo& wipe_tower_info, - coord_t min_obj_distance, - const Slic3r::Polyline& bed, - BedShapeHint bedhint, - bool first_bin_only, - std::function progressind, - std::function stopcondition); - -/// This will find a suitable position for a new object instance and leave the -/// old items untouched. -void find_new_position(const Model& model, - ModelInstancePtrs instances_to_add, - coord_t min_obj_distance, - const Slic3r::Polyline& bed, - WipeTowerInfo& wti); - -} // arr -} // Slic3r -#endif // MODELARRANGE_HPP diff --git a/src/libslic3r/MotionPlanner.cpp b/src/libslic3r/MotionPlanner.cpp index 198c39f311..dd34438799 100644 --- a/src/libslic3r/MotionPlanner.cpp +++ b/src/libslic3r/MotionPlanner.cpp @@ -332,7 +332,7 @@ Polyline MotionPlannerGraph::shortest_path(size_t node_start, size_t node_end) c queue.pop(); map_node_to_queue_id[u] = size_t(-1); // Stop searching if we reached our destination. - if (u == node_end) + if (size_t(u) == node_end) break; // Visit each edge starting at node u. for (const Neighbor& neighbor : m_adjacency_list[u]) diff --git a/src/libslic3r/ObjectID.cpp b/src/libslic3r/ObjectID.cpp new file mode 100644 index 0000000000..b188d84c06 --- /dev/null +++ b/src/libslic3r/ObjectID.cpp @@ -0,0 +1,22 @@ +#include "ObjectID.hpp" + +namespace Slic3r { + +size_t ObjectBase::s_last_id = 0; + +// Unique object / instance ID for the wipe tower. +ObjectID wipe_tower_object_id() +{ + static ObjectBase mine; + return mine.id(); +} + +ObjectID wipe_tower_instance_id() +{ + static ObjectBase mine; + return mine.id(); +} + +} // namespace Slic3r + +// CEREAL_REGISTER_TYPE(Slic3r::ObjectBase) diff --git a/src/libslic3r/ObjectID.hpp b/src/libslic3r/ObjectID.hpp new file mode 100644 index 0000000000..484d1173ba --- /dev/null +++ b/src/libslic3r/ObjectID.hpp @@ -0,0 +1,93 @@ +#ifndef slic3r_ObjectID_hpp_ +#define slic3r_ObjectID_hpp_ + +#include + +namespace Slic3r { + +namespace UndoRedo { + class StackImpl; +}; + +// Unique identifier of a mutable object accross the application. +// Used to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject) +// (for Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial classes) +// and to serialize / deserialize an object onto the Undo / Redo stack. +// Valid IDs are strictly positive (non zero). +// It is declared as an object, as some compilers (notably msvcc) consider a typedef size_t equivalent to size_t +// for parameter overload. +class ObjectID +{ +public: + ObjectID(size_t id) : id(id) {} + // Default constructor constructs an invalid ObjectID. + ObjectID() : id(0) {} + + bool operator==(const ObjectID &rhs) const { return this->id == rhs.id; } + bool operator!=(const ObjectID &rhs) const { return this->id != rhs.id; } + bool operator< (const ObjectID &rhs) const { return this->id < rhs.id; } + bool operator> (const ObjectID &rhs) const { return this->id > rhs.id; } + bool operator<=(const ObjectID &rhs) const { return this->id <= rhs.id; } + bool operator>=(const ObjectID &rhs) const { return this->id >= rhs.id; } + + bool valid() const { return id != 0; } + bool invalid() const { return id == 0; } + + size_t id; + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(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). +// Achtung! The s_last_id counter is not thread safe, so it is expected, that the ObjectBase derived instances +// are only instantiated from the main thread. +class ObjectBase +{ +public: + ObjectID id() const { return m_id; } + +protected: + // Constructors to be only called by derived classes. + // Default constructor to assign a unique ID. + ObjectBase() : m_id(generate_new_id()) {} + // Constructor with ignored int parameter to assign an invalid ID, to be replaced + // by an existing ID copied from elsewhere. + ObjectBase(int) : m_id(ObjectID(0)) {} + // The class tree will have virtual tables and type information. + virtual ~ObjectBase() {} + + // Use with caution! + void set_new_unique_id() { m_id = generate_new_id(); } + void set_invalid_id() { m_id = 0; } + // Use with caution! + void copy_id(const ObjectBase &rhs) { m_id = rhs.id(); } + + // Override this method if a ObjectBase derived class owns other ObjectBase derived instances. + virtual void assign_new_unique_ids_recursive() { this->set_new_unique_id(); } + +private: + ObjectID m_id; + + static inline ObjectID generate_new_id() { return ObjectID(++ s_last_id); } + static size_t s_last_id; + + friend ObjectID wipe_tower_object_id(); + friend ObjectID wipe_tower_instance_id(); + + friend class cereal::access; + friend class Slic3r::UndoRedo::StackImpl; + template void serialize(Archive &ar) { ar(m_id); } + ObjectBase(const ObjectID id) : m_id(id) {} + template static void load_and_construct(Archive & ar, cereal::construct &construct) { ObjectID id; ar(id); construct(id); } +}; + +// Unique object / instance ID for the wipe tower. +extern ObjectID wipe_tower_object_id(); +extern ObjectID wipe_tower_instance_id(); + +} // namespace Slic3r + +#endif /* slic3r_ObjectID_hpp_ */ diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index de8aeeb2ab..4dac192adc 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -175,7 +175,7 @@ void PerimeterGenerator::process() const PerimeterGeneratorLoop &loop = contours_d[i]; // find the contour loop that contains it for (int t = d - 1; t >= 0; -- t) { - for (int j = 0; j < contours[t].size(); ++ j) { + for (size_t j = 0; j < contours[t].size(); ++ j) { PerimeterGeneratorLoop &candidate_parent = contours[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { candidate_parent.children.push_back(loop); @@ -397,7 +397,7 @@ static inline ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyli pp.push_back(line.b); width.push_back(line.b_width); - assert(pp.size() == segments + 1); + assert(pp.size() == segments + 1u); assert(width.size() == segments*2); } diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index ae66178aa3..530d849072 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -62,7 +62,7 @@ namespace Slic3r { -PlaceholderParser::PlaceholderParser() +PlaceholderParser::PlaceholderParser(const DynamicConfig *external_config) : m_external_config(external_config) { this->set("version", std::string(SLIC3R_VERSION)); this->apply_env_variables(); @@ -94,14 +94,6 @@ void PlaceholderParser::update_timestamp(DynamicConfig &config) config.set_key_value("second", new ConfigOptionInt(timeinfo->tm_sec)); } -// Ignore this key by the placeholder parser. -static inline bool placeholder_parser_ignore(const ConfigDef *def, const std::string &opt_key) -{ - const ConfigOptionDef *opt_def = def->get(opt_key); - assert(opt_def != nullptr); - return (opt_def->multiline && boost::ends_with(opt_key, "_gcode")) || opt_key == "post_process"; -} - static inline bool opts_equal(const DynamicConfig &config_old, const DynamicConfig &config_new, const std::string &opt_key) { const ConfigOption *opt_old = config_old.option(opt_key); @@ -119,7 +111,7 @@ std::vector PlaceholderParser::config_diff(const DynamicPrintConfig const ConfigDef *def = rhs.def(); std::vector diff_keys; for (const t_config_option_key &opt_key : rhs.keys()) - if (! placeholder_parser_ignore(def, opt_key) && ! opts_equal(m_config, rhs, opt_key)) + if (! opts_equal(m_config, rhs, opt_key)) diff_keys.emplace_back(opt_key); return diff_keys; } @@ -135,8 +127,6 @@ bool PlaceholderParser::apply_config(const DynamicPrintConfig &rhs) const ConfigDef *def = rhs.def(); bool modified = false; for (const t_config_option_key &opt_key : rhs.keys()) { - if (placeholder_parser_ignore(def, opt_key)) - continue; if (! opts_equal(m_config, rhs, opt_key)) { // Store a copy of the config option. // Convert FloatOrPercent values to floats first. @@ -155,7 +145,6 @@ bool PlaceholderParser::apply_config(const DynamicPrintConfig &rhs) void PlaceholderParser::apply_only(const DynamicPrintConfig &rhs, const std::vector &keys) { for (const t_config_option_key &opt_key : keys) { - assert(! placeholder_parser_ignore(rhs.def(), opt_key)); // Store a copy of the config option. // Convert FloatOrPercent values to floats first. //FIXME there are some ratio_over chains, which end with empty ratio_with. @@ -167,6 +156,11 @@ void PlaceholderParser::apply_only(const DynamicPrintConfig &rhs, const std::vec } } +void PlaceholderParser::apply_config(DynamicPrintConfig &&rhs) +{ + m_config += std::move(rhs); +} + void PlaceholderParser::apply_env_variables() { for (char** env = environ; *env; ++ env) { @@ -181,6 +175,11 @@ void PlaceholderParser::apply_env_variables() } namespace spirit = boost::spirit; +// Using an encoding, which accepts unsigned chars. +// Don't use boost::spirit::ascii, as it crashes internally due to indexing with negative char values for UTF8 characters into some 7bit character classification tables. +//namespace spirit_encoding = boost::spirit::ascii; +//FIXME iso8859_1 is just a workaround for the problem above. Replace it with UTF8 support! +namespace spirit_encoding = boost::spirit::iso8859_1; namespace qi = boost::spirit::qi; namespace px = boost::phoenix; @@ -521,7 +520,6 @@ namespace client static void regex_op(expr &lhs, boost::iterator_range &rhs, char op) { const std::string *subject = nullptr; - const std::string *mask = nullptr; if (lhs.type == TYPE_STRING) { // One type is string, the other could be converted to string. subject = &lhs.s(); @@ -563,7 +561,6 @@ namespace client static void ternary_op(expr &lhs, expr &rhs1, expr &rhs2) { - bool value = false; if (lhs.type != TYPE_BOOL) lhs.throw_exception("Not a boolean expression"); if (lhs.b()) @@ -610,6 +607,7 @@ namespace client } struct MyContext { + const DynamicConfig *external_config = nullptr; const DynamicConfig *config = nullptr; const DynamicConfig *config_override = nullptr; size_t current_extruder_id = 0; @@ -630,6 +628,8 @@ namespace client opt = config_override->option(opt_key); if (opt == nullptr) opt = config->option(opt_key); + if (opt == nullptr && external_config != nullptr) + opt = external_config->option(opt_key); return opt; } @@ -936,7 +936,7 @@ namespace client /////////////////////////////////////////////////////////////////////////// // Inspired by the C grammar rules https://www.lysator.liu.se/c/ANSI-C-grammar-y.html template - struct macro_processor : qi::grammar, spirit::ascii::space_type> + struct macro_processor : qi::grammar, spirit_encoding::space_type> { macro_processor() : macro_processor::base_type(start) { @@ -949,12 +949,12 @@ namespace client qi::lexeme_type lexeme; qi::no_skip_type no_skip; qi::real_parser strict_double; - spirit::ascii::char_type char_; + spirit_encoding::char_type char_; utf8_char_skipper_parser utf8char; spirit::bool_type bool_; spirit::int_type int_; spirit::double_type double_; - spirit::ascii::string_type string; + spirit_encoding::string_type string; spirit::eoi_type eoi; spirit::repository::qi::iter_pos_type iter_pos; auto kw = spirit::repository::qi::distinct(qi::copy(alnum | '_')); @@ -975,7 +975,7 @@ namespace client // depending on the context->just_boolean_expression flag. This way a single static expression parser // could serve both purposes. start = eps[px::bind(&MyContext::evaluate_full_macro, _r1, _a)] > - ( eps(_a==true) > text_block(_r1) [_val=_1] + ( (eps(_a==true) > text_block(_r1) [_val=_1]) | conditional_expression(_r1) [ px::bind(&expr::evaluate_boolean_to_string, _1, _val) ] ) > eoi; start.name("start"); @@ -1183,20 +1183,20 @@ namespace client } // Generic expression over expr. - typedef qi::rule(const MyContext*), spirit::ascii::space_type> RuleExpression; + typedef qi::rule(const MyContext*), spirit_encoding::space_type> RuleExpression; // The start of the grammar. - qi::rule, spirit::ascii::space_type> start; + qi::rule, spirit_encoding::space_type> start; // A free-form text. - qi::rule text; + qi::rule text; // A free-form text, possibly empty, possibly containing macro expansions. - qi::rule text_block; + qi::rule text_block; // Statements enclosed in curely braces {} - qi::rule macro; + qi::rule macro; // Legacy variable expansion of the original Slic3r, in the form of [scalar_variable] or [vector_variable_index]. - qi::rule legacy_variable_expansion; + qi::rule legacy_variable_expansion; // Parsed identifier name. - qi::rule(), spirit::ascii::space_type> identifier; + qi::rule(), spirit_encoding::space_type> identifier; // Ternary operator (?:) over logical_or_expression. RuleExpression conditional_expression; // Logical or over logical_and_expressions. @@ -1214,16 +1214,16 @@ namespace client // Number literals, functions, braced expressions, variable references, variable indexing references. RuleExpression unary_expression; // Rule to capture a regular expression enclosed in //. - qi::rule(), spirit::ascii::space_type> regular_expression; + qi::rule(), spirit_encoding::space_type> regular_expression; // Evaluate boolean expression into bool. - qi::rule bool_expr_eval; + qi::rule bool_expr_eval; // Reference of a scalar variable, or reference to a field of a vector variable. - qi::rule(const MyContext*), qi::locals, int>, spirit::ascii::space_type> scalar_variable_reference; + qi::rule(const MyContext*), qi::locals, int>, spirit_encoding::space_type> scalar_variable_reference; // Rule to translate an identifier to a ConfigOption, or to fail. - qi::rule(const MyContext*), spirit::ascii::space_type> variable_reference; + qi::rule(const MyContext*), spirit_encoding::space_type> variable_reference; - qi::rule, spirit::ascii::space_type> if_else_output; -// qi::rule, bool, std::string>, spirit::ascii::space_type> switch_output; + qi::rule, spirit_encoding::space_type> if_else_output; +// qi::rule, bool, std::string>, spirit_encoding::space_type> switch_output; qi::symbols keywords; }; @@ -1235,7 +1235,7 @@ static std::string process_macro(const std::string &templ, client::MyContext &co typedef client::macro_processor macro_processor; // Our whitespace skipper. - spirit::ascii::space_type space; + spirit_encoding::space_type space; // Our grammar, statically allocated inside the method, meaning it will be allocated the first time // PlaceholderParser::process() runs. //FIXME this kind of initialization is not thread safe! @@ -1245,7 +1245,7 @@ static std::string process_macro(const std::string &templ, client::MyContext &co std::string::const_iterator end = templ.end(); // Accumulator for the processed template. std::string output; - bool res = phrase_parse(iter, end, macro_processor_instance(&context), space, output); + phrase_parse(iter, end, macro_processor_instance(&context), space, output); if (!context.error_message.empty()) { if (context.error_message.back() != '\n' && context.error_message.back() != '\r') context.error_message += '\n'; @@ -1257,6 +1257,7 @@ static std::string process_macro(const std::string &templ, client::MyContext &co std::string PlaceholderParser::process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) const { client::MyContext context; + context.external_config = this->external_config(); context.config = &this->config(); context.config_override = config_override; context.current_extruder_id = current_extruder_id; @@ -1268,8 +1269,8 @@ std::string PlaceholderParser::process(const std::string &templ, unsigned int cu bool PlaceholderParser::evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config, const DynamicConfig *config_override) { client::MyContext context; - context.config = &config; - context.config_override = config_override; + context.config = &config; + context.config_override = config_override; // Let the macro processor parse just a boolean expression, not the full macro language. context.just_boolean_expression = true; return process_macro(templ, context) == "true"; diff --git a/src/libslic3r/PlaceholderParser.hpp b/src/libslic3r/PlaceholderParser.hpp index 22c790e6b5..14fdc5c285 100644 --- a/src/libslic3r/PlaceholderParser.hpp +++ b/src/libslic3r/PlaceholderParser.hpp @@ -12,13 +12,14 @@ namespace Slic3r { class PlaceholderParser { public: - PlaceholderParser(); + PlaceholderParser(const DynamicConfig *external_config = nullptr); // Return a list of keys, which should be changed in m_config from rhs. // This contains keys, which are found in rhs, but not in m_config. std::vector config_diff(const DynamicPrintConfig &rhs); // Return true if modified. bool apply_config(const DynamicPrintConfig &config); + void apply_config(DynamicPrintConfig &&config); // To be called on the values returned by PlaceholderParser::config_diff(). // The keys should already be valid. void apply_only(const DynamicPrintConfig &config, const std::vector &keys); @@ -35,6 +36,8 @@ public: DynamicConfig& config_writable() { return m_config; } const DynamicConfig& config() const { return m_config; } const ConfigOption* option(const std::string &key) const { return m_config.option(key); } + // External config is not owned by PlaceholderParser. It has a lowest priority when looking up an option. + const DynamicConfig* external_config() const { return m_external_config; } // Fill in the template using a macro processing language. // Throws std::runtime_error on syntax or runtime error. @@ -50,7 +53,9 @@ public: void update_timestamp() { update_timestamp(m_config); } private: - DynamicConfig m_config; + // config has a higher priority than external_config when looking up a symbol. + DynamicConfig m_config; + const DynamicConfig *m_external_config; }; } diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index b02ead2994..994f45e59d 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -62,8 +62,8 @@ inline Vec2i64 to_2d(const Vec3i64 &pt3) { return Vec2i64(pt3(0), pt3(1)); } inline Vec2f to_2d(const Vec3f &pt3) { return Vec2f (pt3(0), pt3(1)); } inline Vec2d to_2d(const Vec3d &pt3) { return Vec2d (pt3(0), pt3(1)); } -inline Vec3d to_3d(const Vec2d &v, double z) { return Vec3d(v(0), v(1), z); } -inline Vec3f to_3d(const Vec2f &v, float z) { return Vec3f(v(0), v(1), z); } +inline Vec3d to_3d(const Vec2d &v, double z) { return Vec3d(v(0), v(1), z); } +inline Vec3f to_3d(const Vec2f &v, float z) { return Vec3f(v(0), v(1), z); } inline Vec3i64 to_3d(const Vec2i64 &v, float z) { return Vec3i64(int64_t(v(0)), int64_t(v(1)), int64_t(z)); } inline Vec3crd to_3d(const Vec3crd &p, coord_t z) { return Vec3crd(p(0), p(1), z); } @@ -291,4 +291,21 @@ namespace boost { namespace polygon { } } // end Boost +// Serialization through the Cereal library +namespace cereal { +// template void serialize(Archive& archive, Slic3r::Vec2crd &v) { archive(v.x(), v.y()); } +// template void serialize(Archive& archive, Slic3r::Vec3crd &v) { archive(v.x(), v.y(), v.z()); } + template void serialize(Archive& archive, Slic3r::Vec2i &v) { archive(v.x(), v.y()); } + template void serialize(Archive& archive, Slic3r::Vec3i &v) { archive(v.x(), v.y(), v.z()); } +// template void serialize(Archive& archive, Slic3r::Vec2i64 &v) { archive(v.x(), v.y()); } +// template void serialize(Archive& archive, Slic3r::Vec3i64 &v) { archive(v.x(), v.y(), v.z()); } + template void serialize(Archive& archive, Slic3r::Vec2f &v) { archive(v.x(), v.y()); } + template void serialize(Archive& archive, Slic3r::Vec3f &v) { archive(v.x(), v.y(), v.z()); } + template void serialize(Archive& archive, Slic3r::Vec2d &v) { archive(v.x(), v.y()); } + template void serialize(Archive& archive, Slic3r::Vec3d &v) { archive(v.x(), v.y(), v.z()); } + + template void load(Archive& archive, Slic3r::Matrix2f &m) { archive.loadBinary((char*)m.data(), sizeof(float) * 4); } + template void save(Archive& archive, Slic3r::Matrix2f &m) { archive.saveBinary((char*)m.data(), sizeof(float) * 4); } +} + #endif diff --git a/src/libslic3r/PolygonTrimmer.cpp b/src/libslic3r/PolygonTrimmer.cpp new file mode 100644 index 0000000000..3e3c9b4982 --- /dev/null +++ b/src/libslic3r/PolygonTrimmer.cpp @@ -0,0 +1,56 @@ +#include "PolygonTrimmer.hpp" +#include "EdgeGrid.hpp" +#include "Geometry.hpp" + +namespace Slic3r { + +TrimmedLoop trim_loop(const Polygon &loop, const EdgeGrid::Grid &grid) +{ + assert(! loop.empty()); + assert(loop.size() >= 2); + + TrimmedLoop out; + + if (loop.size() >= 2) { + size_t cnt = loop.points.size(); + + struct Visitor { + Visitor(const EdgeGrid::Grid &grid, const Slic3r::Point *pt_prev, const Slic3r::Point *pt_this) : grid(grid), pt_prev(pt_prev), pt_this(pt_this) {} + + void operator()(coord_t iy, coord_t ix) { + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = grid.cell_data_range(iy, ix); + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++ it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = grid.segment(*it_contour_and_segment); + if (Geometry::segments_intersect(segment.first, segment.second, *pt_prev, *pt_this)) { + // The two segments intersect. Add them to the output. + } + } + } + + const EdgeGrid::Grid &grid; + const Slic3r::Point *pt_this; + const Slic3r::Point *pt_prev; + } visitor(grid, &loop.points.back(), nullptr); + + for (const Point &pt_this : loop.points) { + visitor.pt_this = &pt_this; + grid.visit_cells_intersecting_line(*visitor.pt_prev, pt_this, visitor); + visitor.pt_prev = &pt_this; + } + } + + return out; +} + +std::vector trim_loops(const Polygons &loops, const EdgeGrid::Grid &grid) +{ + std::vector out; + out.reserve(loops.size()); + for (const Polygon &loop : loops) + out.emplace_back(trim_loop(loop, grid)); + return out; +} + +} diff --git a/src/libslic3r/PolygonTrimmer.hpp b/src/libslic3r/PolygonTrimmer.hpp new file mode 100644 index 0000000000..eddffbc7fa --- /dev/null +++ b/src/libslic3r/PolygonTrimmer.hpp @@ -0,0 +1,32 @@ +#ifndef slic3r_PolygonTrimmer_hpp_ +#define slic3r_PolygonTrimmer_hpp_ + +#include "libslic3r.h" +#include +#include +#include "Line.hpp" +#include "MultiPoint.hpp" +#include "Polyline.hpp" +#include "Polygon.hpp" + +namespace Slic3r { + +namespace EdgeGrid { + class Grid; +} + +struct TrimmedLoop +{ + std::vector points; + // Number of points per segment. Empty if the loop is + std::vector segments; + + bool is_trimmed() const { return ! segments.empty(); } +}; + +TrimmedLoop trim_loop(const Polygon &loop, const EdgeGrid::Grid &grid); +std::vector trim_loops(const Polygons &loops, const EdgeGrid::Grid &grid); + +} // namespace Slic3r + +#endif /* slic3r_PolygonTrimmer_hpp_ */ diff --git a/src/libslic3r/PolylineCollection.cpp b/src/libslic3r/PolylineCollection.cpp index 1304161c3f..0c66c371a9 100644 --- a/src/libslic3r/PolylineCollection.cpp +++ b/src/libslic3r/PolylineCollection.cpp @@ -61,7 +61,7 @@ Polylines PolylineCollection::_chained_path_from( while (! endpoints.empty()) { // find nearest point int endpoint_index = nearest_point_index(endpoints, start_near, no_reverse); - assert(endpoint_index >= 0 && endpoint_index < endpoints.size() * 2); + assert(endpoint_index >= 0 && size_t(endpoint_index) < endpoints.size() * 2); if (move_from_src) { retval.push_back(std::move(src[endpoints[endpoint_index/2].idx])); } else { diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index f9129f15a1..71529cff15 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -7,18 +7,21 @@ #include "I18N.hpp" #include "SupportMaterial.hpp" #include "GCode.hpp" -#include "GCode/WipeTowerPrusaMM.hpp" +#include "GCode/WipeTower.hpp" #include "Utils.hpp" //#include "PrintExport.hpp" +#include + #include #include #include #include +#include #include -//! macro used to mark string used at localization, +//! macro used to mark string used at localization, //! return same string #define L(s) Slic3r::I18N::translate(s) @@ -41,36 +44,6 @@ void Print::clear() m_model.clear_objects(); } -// Only used by the Perl test cases. -void Print::reload_object(size_t /* idx */) -{ - ModelObjectPtrs model_objects; - { - tbb::mutex::scoped_lock lock(this->state_mutex()); - // The following call should stop background processing if it is running. - this->invalidate_all_steps(); - /* TODO: this method should check whether the per-object config and per-material configs - have changed in such a way that regions need to be rearranged or we can just apply - the diff and invalidate something. Same logic as apply() - For now we just re-add all objects since we haven't implemented this incremental logic yet. - This should also check whether object volumes (parts) have changed. */ - // collect all current model objects - model_objects.reserve(m_objects.size()); - for (PrintObject *object : m_objects) - model_objects.push_back(object->model_object()); - // remove our print objects - for (PrintObject *object : m_objects) - delete object; - m_objects.clear(); - for (PrintRegion *region : m_regions) - delete region; - m_regions.clear(); - } - // re-add model objects - for (ModelObject *mo : model_objects) - this->add_model_object(mo); -} - PrintRegion* Print::add_region() { m_regions.emplace_back(new PrintRegion(this)); @@ -121,7 +94,6 @@ bool Print::invalidate_state_by_config_options(const std::vector Print::object_extruders() const { std::vector extruders; extruders.reserve(m_regions.size() * 3); - for (const PrintRegion *region : m_regions) - region->collect_object_printing_extruders(extruders); + std::vector region_used(m_regions.size(), false); + for (const PrintObject *object : m_objects) + for (const std::vector> &volumes_per_region : object->region_volumes) + if (! volumes_per_region.empty()) + region_used[&volumes_per_region - &object->region_volumes.front()] = true; + for (size_t idx_region = 0; idx_region < m_regions.size(); ++ idx_region) + if (region_used[idx_region]) + m_regions[idx_region]->collect_object_printing_extruders(extruders); sort_remove_duplicates(extruders); return extruders; } @@ -300,17 +279,24 @@ std::vector Print::support_material_extruders() const { std::vector extruders; bool support_uses_current_extruder = false; + auto num_extruders = (unsigned int)m_config.nozzle_diameter.size(); for (PrintObject *object : m_objects) { if (object->has_support_material()) { + assert(object->config().support_material_extruder >= 0); if (object->config().support_material_extruder == 0) support_uses_current_extruder = true; - else - extruders.push_back(object->config().support_material_extruder - 1); + else { + unsigned int i = (unsigned int)object->config().support_material_extruder - 1; + extruders.emplace_back((i >= num_extruders) ? 0 : i); + } + assert(object->config().support_material_interface_extruder >= 0); if (object->config().support_material_interface_extruder == 0) support_uses_current_extruder = true; - else - extruders.push_back(object->config().support_material_interface_extruder - 1); + else { + unsigned int i = (unsigned int)object->config().support_material_interface_extruder - 1; + extruders.emplace_back((i >= num_extruders) ? 0 : i); + } } } @@ -335,7 +321,7 @@ unsigned int Print::num_object_instances() const { unsigned int instances = 0; for (const PrintObject *print_object : m_objects) - instances += print_object->copies().size(); + instances += (unsigned int)print_object->copies().size(); return instances; } @@ -358,198 +344,6 @@ double Print::max_allowed_layer_height() const return nozzle_diameter_max; } -// Caller is responsible for supplying models whose objects don't collide -// and have explicit instance positions. -void Print::add_model_object(ModelObject* model_object, int idx) -{ - tbb::mutex::scoped_lock lock(this->state_mutex()); - // Add a copy of this ModelObject to this Print. - m_model.objects.emplace_back(ModelObject::new_copy(*model_object)); - m_model.objects.back()->set_model(&m_model); - // Initialize a new print object and store it at the given position. - PrintObject *object = new PrintObject(this, model_object, true); - if (idx != -1) { - delete m_objects[idx]; - m_objects[idx] = object; - } else - m_objects.emplace_back(object); - // Invalidate all print steps. - this->invalidate_all_steps(); - - // Set the transformation matrix without translation from the first instance. - if (! model_object->instances.empty()) { - // Trafo and bounding box, both in world coordinate system. - Transform3d trafo = model_object->instances.front()->get_matrix(); - BoundingBoxf3 bbox = model_object->instance_bounding_box(0); - // Now shift the object up to align it with the print bed. - trafo.data()[14] -= bbox.min(2); - // and reset the XY translation. - trafo.data()[12] = 0; - trafo.data()[13] = 0; - object->set_trafo(trafo); - } - - size_t volume_id = 0; - for (const ModelVolume *volume : model_object->volumes) { - if (! volume->is_model_part() && ! volume->is_modifier()) - continue; - // Get the config applied to this volume. - PrintRegionConfig config = PrintObject::region_config_from_model_volume(m_default_region_config, *volume, 99999); - // Find an existing print region with the same config. - size_t region_id = size_t(-1); - for (size_t i = 0; i < m_regions.size(); ++ i) - if (config.equals(m_regions[i]->config())) { - region_id = i; - break; - } - // If no region exists with the same config, create a new one. - if (region_id == size_t(-1)) { - region_id = m_regions.size(); - this->add_region(config); - } - // Assign volume to a region. - object->add_region_volume(region_id, volume_id); - ++ volume_id; - } - - // Apply config to print object. - object->config_apply(this->default_object_config()); - { - //normalize_and_apply_config(object->config(), model_object->config); - DynamicPrintConfig src_normalized(model_object->config); - src_normalized.normalize(); - object->config_apply(src_normalized, true); - } -} - -// This function is only called through the Perl-C++ binding from the unit tests, should be -// removed when unit tests are rewritten to C++. -bool Print::apply_config_perl_tests_only(DynamicPrintConfig config) -{ - tbb::mutex::scoped_lock lock(this->state_mutex()); - - - // Perl unit tests were failing in case the preset was not normalized (e.g. https://github.com/prusa3d/PrusaSlicer/issues/2288 was caused - // by too short max_layer_height vector. Calling the necessary function Preset::normalize(...) is not currently possible because there is no - // access to preset. This should be solved when the unit tests are rewritten to C++. For now we just copy-pasted code from Preset.cpp - // to make sure the unit tests pass (functions set_num_extruders and nozzle_options()). - auto *nozzle_diameter = dynamic_cast(config.option("nozzle_diameter", true)); - assert(nozzle_diameter != nullptr); - const auto &defaults = FullPrintConfig::defaults(); - for (const std::string &key : { "nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_offset", - "retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed", - "retract_before_wipe", "retract_restart_extra", "retract_before_travel", "wipe", - "retract_layer_change", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour" }) - { - auto *opt = config.option(key, true); - assert(opt != nullptr); - assert(opt->is_vector()); - unsigned int num_extruders = (unsigned int)nozzle_diameter->values.size(); - static_cast(opt)->resize(num_extruders, defaults.option(key)); - } - - // we get a copy of the config object so we can modify it safely - config.normalize(); - - // apply variables to placeholder parser - this->placeholder_parser().apply_config(config); - - // handle changes to print config - t_config_option_keys print_diff = m_config.diff(config); - m_config.apply_only(config, print_diff, true); - bool invalidated = this->invalidate_state_by_config_options(print_diff); - - // handle changes to object config defaults - m_default_object_config.apply(config, true); - for (PrintObject *object : m_objects) { - // we don't assume that config contains a full ObjectConfig, - // so we base it on the current print-wise default - PrintObjectConfig new_config = this->default_object_config(); - // we override the new config with object-specific options - normalize_and_apply_config(new_config, object->model_object()->config); - // check whether the new config is different from the current one - t_config_option_keys diff = object->config().diff(new_config); - object->config_apply_only(new_config, diff, true); - invalidated |= object->invalidate_state_by_config_options(diff); - } - - // handle changes to regions config defaults - m_default_region_config.apply(config, true); - - // All regions now have distinct settings. - // Check whether applying the new region config defaults we'd get different regions. - bool rearrange_regions = false; - { - // Collect the already visited region configs into other_region_configs, - // so one may check for duplicates. - std::vector other_region_configs; - for (size_t region_id = 0; region_id < m_regions.size(); ++ region_id) { - PrintRegion ®ion = *m_regions[region_id]; - PrintRegionConfig this_region_config; - bool this_region_config_set = false; - for (PrintObject *object : m_objects) { - if (region_id < object->region_volumes.size()) { - for (int volume_id : object->region_volumes[region_id]) { - const ModelVolume &volume = *object->model_object()->volumes[volume_id]; - if (this_region_config_set) { - // If the new config for this volume differs from the other - // volume configs currently associated to this region, it means - // the region subdivision does not make sense anymore. - if (! this_region_config.equals(PrintObject::region_config_from_model_volume(m_default_region_config, volume, 99999))) { - rearrange_regions = true; - goto exit_for_rearrange_regions; - } - } else { - this_region_config = PrintObject::region_config_from_model_volume(m_default_region_config, volume, 99999); - this_region_config_set = true; - } - for (const PrintRegionConfig &cfg : other_region_configs) { - // If the new config for this volume equals any of the other - // volume configs that are not currently associated to this - // region, it means the region subdivision does not make - // sense anymore. - if (cfg.equals(this_region_config)) { - rearrange_regions = true; - goto exit_for_rearrange_regions; - } - } - } - } - } - if (this_region_config_set) { - t_config_option_keys diff = region.config().diff(this_region_config); - if (! diff.empty()) { - region.config_apply_only(this_region_config, diff, false); - for (PrintObject *object : m_objects) - if (region_id < object->region_volumes.size() && ! object->region_volumes[region_id].empty()) - invalidated |= object->invalidate_state_by_config_options(diff); - } - other_region_configs.emplace_back(std::move(this_region_config)); - } - } - } - -exit_for_rearrange_regions: - - if (rearrange_regions) { - // The current subdivision of regions does not make sense anymore. - // We need to remove all objects and re-add them. - ModelObjectPtrs model_objects; - model_objects.reserve(m_objects.size()); - for (PrintObject *object : m_objects) - model_objects.push_back(object->model_object()); - this->clear(); - for (ModelObject *mo : model_objects) - this->add_model_object(mo); - invalidated = true; - } - - for (PrintObject *object : m_objects) - object->update_slicing_parameters(); - - return invalidated; -} - // Add or remove support modifier ModelVolumes from model_object_dst to match the ModelVolumes of model_object_new // in the exact order and with the same IDs. // It is expected, that the model_object_dst already contains the non-support volumes of model_object_new in the correct order. @@ -612,7 +406,7 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, assert(mv_src.id() == mv_dst.id()); // Copy the ModelVolume data. mv_dst.name = mv_src.name; - mv_dst.config = mv_src.config; + static_cast(mv_dst.config) = static_cast(mv_src.config); //FIXME what to do with the materials? // mv_dst.m_material_id = mv_src.m_material_id; ++ i_src; @@ -620,6 +414,20 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, } } +static inline void layer_height_ranges_copy_configs(t_layer_config_ranges &lr_dst, const t_layer_config_ranges &lr_src) +{ + assert(lr_dst.size() == lr_src.size()); + auto it_src = lr_src.cbegin(); + for (auto &kvp_dst : lr_dst) { + const auto &kvp_src = *it_src ++; + assert(std::abs(kvp_dst.first.first - kvp_src.first.first ) <= EPSILON); + assert(std::abs(kvp_dst.first.second - kvp_src.first.second) <= EPSILON); + // Layer heights are allowed do differ in case the layer height table is being overriden by the smooth profile. + // assert(std::abs(kvp_dst.second.option("layer_height")->getFloat() - kvp_src.second.option("layer_height")->getFloat()) <= EPSILON); + kvp_dst.second = kvp_src.second; + } +} + static inline bool transform3d_lower(const Transform3d &lhs, const Transform3d &rhs) { typedef Transform3d::Scalar T; @@ -674,25 +482,99 @@ static std::vector print_objects_from_model_object(const ModelOb return std::vector(trafos.begin(), trafos.end()); } -Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &config_in) +// Compare just the layer ranges and their layer heights, not the associated configs. +// Ignore the layer heights if check_layer_heights is false. +bool layer_height_ranges_equal(const t_layer_config_ranges &lr1, const t_layer_config_ranges &lr2, bool check_layer_height) +{ + if (lr1.size() != lr2.size()) + return false; + auto it2 = lr2.begin(); + for (const auto &kvp1 : lr1) { + const auto &kvp2 = *it2 ++; + if (std::abs(kvp1.first.first - kvp2.first.first ) > EPSILON || + std::abs(kvp1.first.second - kvp2.first.second) > EPSILON || + (check_layer_height && std::abs(kvp1.second.option("layer_height")->getFloat() - kvp2.second.option("layer_height")->getFloat()) > EPSILON)) + return false; + } + return true; +} + +// Collect diffs of configuration values at various containers, +// resolve the filament rectract overrides of extruder retract values. +void Print::config_diffs( + const DynamicPrintConfig &new_full_config, + t_config_option_keys &print_diff, t_config_option_keys &object_diff, t_config_option_keys ®ion_diff, + t_config_option_keys &full_config_diff, + DynamicPrintConfig &placeholder_parser_overrides, + DynamicPrintConfig &filament_overrides) const +{ + // Collect changes to print config, account for overrides of extruder retract values by filament presets. + { + const std::vector &extruder_retract_keys = print_config_def.extruder_retract_keys(); + const std::string filament_prefix = "filament_"; + for (const t_config_option_key &opt_key : m_config.keys()) { + const ConfigOption *opt_old = m_config.option(opt_key); + assert(opt_old != nullptr); + const ConfigOption *opt_new = new_full_config.option(opt_key); + // assert(opt_new != nullptr); + if (opt_new == nullptr) + //FIXME This may happen when executing some test cases. + continue; + const ConfigOption *opt_new_filament = std::binary_search(extruder_retract_keys.begin(), extruder_retract_keys.end(), opt_key) ? new_full_config.option(filament_prefix + opt_key) : nullptr; + if (opt_new_filament != nullptr && ! opt_new_filament->is_nil()) { + // An extruder retract override is available at some of the filament presets. + if (*opt_old != *opt_new || opt_new->overriden_by(opt_new_filament)) { + auto opt_copy = opt_new->clone(); + opt_copy->apply_override(opt_new_filament); + if (*opt_old == *opt_copy) + delete opt_copy; + else { + filament_overrides.set_key_value(opt_key, opt_copy); + print_diff.emplace_back(opt_key); + } + } + } else if (*opt_new != *opt_old) + print_diff.emplace_back(opt_key); + } + } + // Collect changes to object and region configs. + object_diff = m_default_object_config.diff(new_full_config); + region_diff = m_default_region_config.diff(new_full_config); + // Prepare for storing of the full print config into new_full_config to be exported into the G-code and to be used by the PlaceholderParser. + // As the PlaceholderParser does not interpret the FloatOrPercent values itself, these values are stored into the PlaceholderParser converted to floats. + for (const t_config_option_key &opt_key : new_full_config.keys()) { + const ConfigOption *opt_old = m_full_print_config.option(opt_key); + const ConfigOption *opt_new = new_full_config.option(opt_key); + if (opt_old == nullptr || *opt_new != *opt_old) + full_config_diff.emplace_back(opt_key); + if (opt_new->type() == coFloatOrPercent) { + // The m_placeholder_parser is never modified by the background processing, GCode.cpp/hpp makes a copy. + const ConfigOption *opt_old_pp = this->placeholder_parser().config().option(opt_key); + double new_value = new_full_config.get_abs_value(opt_key); + if (opt_old_pp == nullptr || static_cast(opt_old_pp)->value != new_value) + placeholder_parser_overrides.set_key_value(opt_key, new ConfigOptionFloat(new_value)); + } + } +} + +Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_config) { #ifdef _DEBUG check_model_ids_validity(model); #endif /* _DEBUG */ - // Make a copy of the config, normalize it. - DynamicPrintConfig config(config_in); - config.option("print_settings_id", true); - config.option("filament_settings_id", true); - config.option("printer_settings_id", true); - config.normalize(); - // Collect changes to print config. - t_config_option_keys print_diff = m_config.diff(config); - t_config_option_keys object_diff = m_default_object_config.diff(config); - t_config_option_keys region_diff = m_default_region_config.diff(config); - t_config_option_keys placeholder_parser_diff = this->placeholder_parser().config_diff(config); + // Normalize the config. + new_full_config.option("print_settings_id", true); + new_full_config.option("filament_settings_id", true); + new_full_config.option("printer_settings_id", true); + new_full_config.normalize(); - // Do not use the ApplyStatus as we will use the max function when updating apply_status. + // Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles. + t_config_option_keys print_diff, object_diff, region_diff, full_config_diff; + DynamicPrintConfig placeholder_parser_overrides, filament_overrides; + this->config_diffs(new_full_config, print_diff, object_diff, region_diff, full_config_diff, placeholder_parser_overrides, filament_overrides); + + // Do not use the ApplyStatus as we will use the max function when updating apply_status. unsigned int apply_status = APPLY_STATUS_UNCHANGED; auto update_apply_status = [&apply_status](bool invalidated) { apply_status = std::max(apply_status, invalidated ? APPLY_STATUS_INVALIDATED : APPLY_STATUS_CHANGED); }; @@ -705,25 +587,74 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co // The following call may stop the background processing. if (! print_diff.empty()) update_apply_status(this->invalidate_state_by_config_options(print_diff)); + // Apply variables to placeholder parser. The placeholder parser is used by G-code export, // which should be stopped if print_diff is not empty. - if (! placeholder_parser_diff.empty()) { + size_t num_extruders = m_config.nozzle_diameter.size(); + bool num_extruders_changed = false; + if (! full_config_diff.empty() || ! placeholder_parser_overrides.empty()) { update_apply_status(this->invalidate_step(psGCodeExport)); - PlaceholderParser &pp = this->placeholder_parser(); - pp.apply_only(config, placeholder_parser_diff); + m_placeholder_parser.apply_config(std::move(placeholder_parser_overrides)); // Set the profile aliases for the PrintBase::output_filename() - pp.set("print_preset", config.option("print_settings_id")->clone()); - pp.set("filament_preset", config.option("filament_settings_id")->clone()); - pp.set("printer_preset", config.option("printer_settings_id")->clone()); + m_placeholder_parser.set("print_preset", new_full_config.option("print_settings_id")->clone()); + m_placeholder_parser.set("filament_preset", new_full_config.option("filament_settings_id")->clone()); + m_placeholder_parser.set("printer_preset", new_full_config.option("printer_settings_id")->clone()); + // It is also safe to change m_config now after this->invalidate_state_by_config_options() call. + m_config.apply_only(new_full_config, print_diff, true); + m_config.apply(filament_overrides); + // Handle changes to object config defaults + m_default_object_config.apply_only(new_full_config, object_diff, true); + // Handle changes to regions config defaults + m_default_region_config.apply_only(new_full_config, region_diff, true); + m_full_print_config = std::move(new_full_config); + if (num_extruders != m_config.nozzle_diameter.size()) { + num_extruders = m_config.nozzle_diameter.size(); + num_extruders_changed = true; + } } - - // It is also safe to change m_config now after this->invalidate_state_by_config_options() call. - m_config.apply_only(config, print_diff, true); - // Handle changes to object config defaults - m_default_object_config.apply_only(config, object_diff, true); - // Handle changes to regions config defaults - m_default_region_config.apply_only(config, region_diff, true); + class LayerRanges + { + public: + LayerRanges() {} + // Convert input config ranges into continuous non-overlapping sorted vector of intervals and their configs. + void assign(const t_layer_config_ranges &in) { + m_ranges.clear(); + m_ranges.reserve(in.size()); + // Input ranges are sorted lexicographically. First range trims the other ranges. + coordf_t last_z = 0; + for (const std::pair &range : in) + if (range.first.second > last_z) { + coordf_t min_z = std::max(range.first.first, 0.); + if (min_z > last_z + EPSILON) { + m_ranges.emplace_back(t_layer_height_range(last_z, min_z), nullptr); + last_z = min_z; + } + if (range.first.second > last_z + EPSILON) { + const DynamicPrintConfig* cfg = &range.second; + m_ranges.emplace_back(t_layer_height_range(last_z, range.first.second), cfg); + last_z = range.first.second; + } + } + if (m_ranges.empty()) + m_ranges.emplace_back(t_layer_height_range(0, DBL_MAX), nullptr); + else if (m_ranges.back().second == nullptr) + m_ranges.back().first.second = DBL_MAX; + else + m_ranges.emplace_back(t_layer_height_range(m_ranges.back().first.second, DBL_MAX), nullptr); + } + const DynamicPrintConfig* config(const t_layer_height_range &range) const { + auto it = std::lower_bound(m_ranges.begin(), m_ranges.end(), std::make_pair< t_layer_height_range, const DynamicPrintConfig*>(t_layer_height_range(range.first - EPSILON, range.second - EPSILON), nullptr)); + assert(it != m_ranges.end()); + assert(it == m_ranges.end() || std::abs(it->first.first - range.first ) < EPSILON); + assert(it == m_ranges.end() || std::abs(it->first.second - range.second) < EPSILON); + return (it == m_ranges.end()) ? nullptr : it->second; + } + std::vector>::const_iterator begin() const { return m_ranges.cbegin(); } + std::vector>::const_iterator end() const { return m_ranges.cend(); } + private: + std::vector> m_ranges; + }; struct ModelObjectStatus { enum Status { Unknown, @@ -732,9 +663,10 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co Moved, Deleted, }; - ModelObjectStatus(ModelID id, Status status = Unknown) : id(id), status(status) {} - ModelID id; - Status status; + ModelObjectStatus(ObjectID id, Status status = Unknown) : id(id), status(status) {} + ObjectID id; + Status status; + LayerRanges layer_ranges; // Search by id. bool operator<(const ModelObjectStatus &rhs) const { return id < rhs.id; } }; @@ -839,9 +771,9 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co print_object(print_object), trafo(print_object->trafo()), status(status) {} - PrintObjectStatus(ModelID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {} + PrintObjectStatus(ObjectID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {} // ID of the ModelObject & PrintObject - ModelID id; + ObjectID id; // Pointer to the old PrintObject PrintObject *print_object; // Trafo generated with model_object->world_matrix(true) @@ -855,27 +787,28 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co print_object_status.emplace(PrintObjectStatus(print_object)); // 3) Synchronize ModelObjects & PrintObjects. - size_t num_extruders = m_config.nozzle_diameter.size(); for (size_t idx_model_object = 0; idx_model_object < model.objects.size(); ++ idx_model_object) { ModelObject &model_object = *m_model.objects[idx_model_object]; auto it_status = model_object_status.find(ModelObjectStatus(model_object.id())); assert(it_status != model_object_status.end()); assert(it_status->status != ModelObjectStatus::Deleted); + const ModelObject& model_object_new = *model.objects[idx_model_object]; + const_cast(*it_status).layer_ranges.assign(model_object_new.layer_config_ranges); if (it_status->status == ModelObjectStatus::New) // PrintObject instances will be added in the next loop. continue; // Update the ModelObject instance, possibly invalidate the linked PrintObjects. assert(it_status->status == ModelObjectStatus::Old || it_status->status == ModelObjectStatus::Moved); - const ModelObject &model_object_new = *model.objects[idx_model_object]; // Check whether a model part volume was added or removed, their transformations or order changed. + // Only volume IDs, volume types and their order are checked, configuration and other parameters are NOT checked. bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::MODEL_PART); bool modifiers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::PARAMETER_MODIFIER); bool support_blockers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER); bool support_enforcers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER); if (model_parts_differ || modifiers_differ || model_object.origin_translation != model_object_new.origin_translation || - model_object.layer_height_ranges != model_object_new.layer_height_ranges || - model_object.layer_height_profile != model_object_new.layer_height_profile) { + model_object.layer_height_profile != model_object_new.layer_height_profile || + ! layer_height_ranges_equal(model_object.layer_config_ranges, model_object_new.layer_config_ranges, model_object_new.layer_height_profile.empty())) { // The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects. auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); for (auto it = range.first; it != range.second; ++ it) { @@ -899,8 +832,8 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co // Synchronize Object's config. bool object_config_changed = model_object.config != model_object_new.config; if (object_config_changed) - model_object.config = model_object_new.config; - if (! object_diff.empty() || object_config_changed) { + static_cast(model_object.config) = static_cast(model_object_new.config); + if (! object_diff.empty() || object_config_changed || num_extruders_changed) { PrintObjectConfig new_config = PrintObject::object_config_from_model_object(m_default_object_config, model_object, num_extruders); auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); for (auto it = range.first; it != range.second; ++ it) { @@ -915,7 +848,8 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co //FIXME What to do with m_material_id? model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::MODEL_PART); model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::PARAMETER_MODIFIER); - // Copy the ModelObject name, input_file and instances. The instances will compared against PrintObject instances in the next step. + layer_height_ranges_copy_configs(model_object.layer_config_ranges /* dst */, model_object_new.layer_config_ranges /* src */); + // Copy the ModelObject name, input_file and instances. The instances will be compared against PrintObject instances in the next step. model_object.name = model_object_new.name; model_object.input_file = model_object_new.input_file; model_object.clear_instances(); @@ -1027,19 +961,27 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co PrintRegionConfig this_region_config; bool this_region_config_set = false; for (PrintObject *print_object : m_objects) { + const LayerRanges *layer_ranges; + { + auto it_status = model_object_status.find(ModelObjectStatus(print_object->model_object()->id())); + assert(it_status != model_object_status.end()); + assert(it_status->status != ModelObjectStatus::Deleted); + layer_ranges = &it_status->layer_ranges; + } if (region_id < print_object->region_volumes.size()) { - for (int volume_id : print_object->region_volumes[region_id]) { - const ModelVolume &volume = *print_object->model_object()->volumes[volume_id]; + for (const std::pair &volume_and_range : print_object->region_volumes[region_id]) { + const ModelVolume &volume = *print_object->model_object()->volumes[volume_and_range.second]; + const DynamicPrintConfig *layer_range_config = layer_ranges->config(volume_and_range.first); if (this_region_config_set) { // If the new config for this volume differs from the other // volume configs currently associated to this region, it means // the region subdivision does not make sense anymore. - if (! this_region_config.equals(PrintObject::region_config_from_model_volume(m_default_region_config, volume, num_extruders))) + if (! this_region_config.equals(PrintObject::region_config_from_model_volume(m_default_region_config, layer_range_config, volume, num_extruders))) // Regions were split. Reset this print_object. goto print_object_end; } else { - this_region_config = PrintObject::region_config_from_model_volume(m_default_region_config, volume, num_extruders); - for (size_t i = 0; i < region_id; ++i) { + this_region_config = PrintObject::region_config_from_model_volume(m_default_region_config, layer_range_config, volume, num_extruders); + for (size_t i = 0; i < region_id; ++ i) { const PrintRegion ®ion_other = *m_regions[i]; if (region_other.m_refcnt != 0 && region_other.config().equals(this_region_config)) // Regions were merged. Reset this print_object. @@ -1054,7 +996,7 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co update_apply_status(print_object->invalidate_all_steps()); // Decrease the references to regions from this volume. int ireg = 0; - for (const std::vector &volumes : print_object->region_volumes) { + for (const std::vector> &volumes : print_object->region_volumes) { if (! volumes.empty()) -- m_regions[ireg]->m_refcnt; ++ ireg; @@ -1076,52 +1018,65 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co for (size_t idx_print_object = 0; idx_print_object < m_objects.size(); ++ idx_print_object) { PrintObject &print_object0 = *m_objects[idx_print_object]; const ModelObject &model_object = *print_object0.model_object(); - std::vector map_volume_to_region(model_object.volumes.size(), -1); + const LayerRanges *layer_ranges; + { + auto it_status = model_object_status.find(ModelObjectStatus(model_object.id())); + assert(it_status != model_object_status.end()); + assert(it_status->status != ModelObjectStatus::Deleted); + layer_ranges = &it_status->layer_ranges; + } + std::vector regions_in_object; + regions_in_object.reserve(64); for (size_t i = idx_print_object; i < m_objects.size() && m_objects[i]->model_object() == &model_object; ++ i) { PrintObject &print_object = *m_objects[i]; bool fresh = print_object.region_volumes.empty(); unsigned int volume_id = 0; + unsigned int idx_region_in_object = 0; for (const ModelVolume *volume : model_object.volumes) { if (! volume->is_model_part() && ! volume->is_modifier()) { ++ volume_id; continue; } - int region_id = -1; - if (&print_object == &print_object0) { - // Get the config applied to this volume. - PrintRegionConfig config = PrintObject::region_config_from_model_volume(m_default_region_config, *volume, num_extruders); - // Find an existing print region with the same config. - int idx_empty_slot = -1; - for (int i = 0; i < (int)m_regions.size(); ++ i) { - if (m_regions[i]->m_refcnt == 0) { - if (idx_empty_slot == -1) - idx_empty_slot = i; - } else if (config.equals(m_regions[i]->config())) { - region_id = i; - break; + // Filter the layer ranges, so they do not overlap and they contain at least a single layer. + // Now insert a volume with a layer range to its own region. + for (auto it_range = layer_ranges->begin(); it_range != layer_ranges->end(); ++ it_range) { + int region_id = -1; + if (&print_object == &print_object0) { + // Get the config applied to this volume. + PrintRegionConfig config = PrintObject::region_config_from_model_volume(m_default_region_config, it_range->second, *volume, num_extruders); + // Find an existing print region with the same config. + int idx_empty_slot = -1; + for (int i = 0; i < (int)m_regions.size(); ++ i) { + if (m_regions[i]->m_refcnt == 0) { + if (idx_empty_slot == -1) + idx_empty_slot = i; + } else if (config.equals(m_regions[i]->config())) { + region_id = i; + break; + } + } + // If no region exists with the same config, create a new one. + if (region_id == -1) { + if (idx_empty_slot == -1) { + region_id = (int)m_regions.size(); + this->add_region(config); + } else { + region_id = idx_empty_slot; + m_regions[region_id]->set_config(std::move(config)); + } } - } - // If no region exists with the same config, create a new one. - if (region_id == -1) { - if (idx_empty_slot == -1) { - region_id = (int)m_regions.size(); - this->add_region(config); - } else { - region_id = idx_empty_slot; - m_regions[region_id]->set_config(std::move(config)); - } - } - map_volume_to_region[volume_id] = region_id; - } else - region_id = map_volume_to_region[volume_id]; - // Assign volume to a region. - if (fresh) { - if (region_id >= print_object.region_volumes.size() || print_object.region_volumes[region_id].empty()) - ++ m_regions[region_id]->m_refcnt; - print_object.add_region_volume(region_id, volume_id); - } - ++ volume_id; - } + regions_in_object.emplace_back(region_id); + } else + region_id = regions_in_object[idx_region_in_object ++]; + // Assign volume to a region. + if (fresh) { + if ((size_t)region_id >= print_object.region_volumes.size() || print_object.region_volumes[region_id].empty()) + ++ m_regions[region_id]->m_refcnt; + print_object.add_region_volume(region_id, volume_id, it_range->first); + } + } + ++ volume_id; + } } } @@ -1175,9 +1130,9 @@ std::string Print::validate() const Polygon convex_hull0 = offset( print_object->model_object()->convex_hull_2d( Geometry::assemble_transform(Vec3d::Zero(), rotation, model_instance0->get_scaling_factor(), model_instance0->get_mirror())), - scale_(m_config.extruder_clearance_radius.value) / 2., jtRound, scale_(0.1)).front(); + float(scale_(0.5 * m_config.extruder_clearance_radius.value)), jtRound, float(scale_(0.1))).front(); // Now we check that no instance of convex_hull intersects any of the previously checked object instances. - for (const Point © : print_object->m_copies) { + for (const Point © : print_object->copies()) { Polygon convex_hull = convex_hull0; convex_hull.translate(copy); if (! intersection(convex_hulls_other, convex_hull).empty()) @@ -1207,27 +1162,39 @@ std::string Print::validate() const // #4043 if (total_copies_count > 1 && ! m_config.complete_objects.value) return L("The Spiral Vase option can only be used when printing a single object."); - if (m_regions.size() > 1) + assert(m_objects.size() == 1); + size_t num_regions = 0; + for (const std::vector> &volumes_per_region : m_objects.front()->region_volumes) + if (! volumes_per_region.empty()) + ++ num_regions; + if (num_regions > 1) return L("The Spiral Vase option can only be used when printing single material objects."); } - if (m_config.single_extruder_multi_material) { - for (size_t i=1; ihas_wipe_tower() && ! m_objects.empty()) { + // Make sure all extruders use same diameter filament and have the same nozzle diameter + // EPSILON comparison is used for nozzles and 10 % tolerance is used for filaments + double first_nozzle_diam = m_config.nozzle_diameter.get_at(extruders().front()); + double first_filament_diam = m_config.filament_diameter.get_at(extruders().front()); + for (const auto& extruder_idx : extruders()) { + double nozzle_diam = m_config.nozzle_diameter.get_at(extruder_idx); + double filament_diam = m_config.filament_diameter.get_at(extruder_idx); + if (nozzle_diam - EPSILON > first_nozzle_diam || nozzle_diam + EPSILON < first_nozzle_diam + || std::abs((filament_diam-first_filament_diam)/first_filament_diam) > 0.1) + return L("The wipe tower is only supported if all extruders have the same nozzle diameter " + "and use filaments of the same diameter."); + } + if (m_config.gcode_flavor != gcfRepRap && m_config.gcode_flavor != gcfRepetier && m_config.gcode_flavor != gcfMarlin) return L("The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter and Repetier G-code flavors."); if (! m_config.use_relative_e_distances) return L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1)."); - + if (m_objects.size() > 1) { bool has_custom_layering = false; std::vector> layer_height_profiles; for (const PrintObject *object : m_objects) { - has_custom_layering = ! object->model_object()->layer_height_ranges.empty() || ! object->model_object()->layer_height_profile.empty(); + has_custom_layering = ! object->model_object()->layer_config_ranges.empty() || ! object->model_object()->layer_height_profile.empty(); if (has_custom_layering) { layer_height_profiles.assign(m_objects.size(), std::vector()); break; @@ -1259,11 +1226,10 @@ std::string Print::validate() const if (has_custom_layering) { const std::vector &layer_height_profile_tallest = layer_height_profiles[tallest_object_idx]; for (size_t idx_object = 0; idx_object < m_objects.size(); ++ idx_object) { - const PrintObject *object = m_objects[idx_object]; const std::vector &layer_height_profile = layer_height_profiles[idx_object]; bool failed = false; if (layer_height_profile_tallest.size() >= layer_height_profile.size()) { - int i = 0; + size_t i = 0; while (i < layer_height_profile.size() && i < layer_height_profile_tallest.size()) { if (std::abs(layer_height_profile_tallest[i] - layer_height_profile[i])) { failed = true; @@ -1307,6 +1273,20 @@ std::string Print::validate() const return L("One or more object were assigned an extruder that the printer does not have."); #endif + auto validate_extrusion_width = [min_nozzle_diameter, max_nozzle_diameter](const ConfigBase &config, const char *opt_key, double layer_height, std::string &err_msg) -> bool { + double extrusion_width_min = config.get_abs_value(opt_key, min_nozzle_diameter); + double extrusion_width_max = config.get_abs_value(opt_key, max_nozzle_diameter); + if (extrusion_width_min == 0) { + // Default "auto-generated" extrusion width is always valid. + } else if (extrusion_width_min <= layer_height) { + err_msg = (boost::format(L("%1%=%2% mm is too low to be printable at a layer height %3% mm")) % opt_key % extrusion_width_min % layer_height).str(); + return false; + } else if (extrusion_width_max >= max_nozzle_diameter * 3.) { + err_msg = (boost::format(L("Excessive %1%=%2% mm to be printable with a nozzle diameter %3% mm")) % opt_key % extrusion_width_max % max_nozzle_diameter).str(); + return false; + } + return true; + }; for (PrintObject *object : m_objects) { if (object->config().raft_layers > 0 || object->config().support_material.value) { if ((object->config().support_material_extruder == 0 || object->config().support_material_interface_extruder == 0) && max_nozzle_diameter - min_nozzle_diameter > EPSILON) { @@ -1350,8 +1330,20 @@ std::string Print::validate() const return L("First layer height can't be greater than nozzle diameter"); // validate layer_height - if (object->config().layer_height.value > min_nozzle_diameter) + double layer_height = object->config().layer_height.value; + if (layer_height > min_nozzle_diameter) return L("Layer height can't be greater than nozzle diameter"); + + // Validate extrusion widths. + std::string err_msg; + if (! validate_extrusion_width(object->config(), "extrusion_width", layer_height, err_msg)) + return err_msg; + if ((object->config().support_material || object->config().raft_layers > 0) && ! validate_extrusion_width(object->config(), "support_material_extrusion_width", layer_height, err_msg)) + return err_msg; + for (const char *opt_key : { "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", "top_infill_extrusion_width" }) + for (size_t i = 0; i < object->region_volumes.size(); ++ i) + if (! object->region_volumes[i].empty() && ! validate_extrusion_width(this->get_region(i)->config(), opt_key, layer_height, err_msg)) + return err_msg; } } @@ -1435,9 +1427,9 @@ Flow Print::brim_flow() const generation as well. */ return Flow::new_from_config_width( frPerimeter, - width, - m_config.nozzle_diameter.get_at(m_regions.front()->config().perimeter_extruder-1), - this->skirt_first_layer_height(), + width, + (float)m_config.nozzle_diameter.get_at(m_regions.front()->config().perimeter_extruder-1), + (float)this->skirt_first_layer_height(), 0 ); } @@ -1457,9 +1449,9 @@ Flow Print::skirt_flow() const generation as well. */ return Flow::new_from_config_width( frPerimeter, - width, - m_config.nozzle_diameter.get_at(m_objects.front()->config().support_material_extruder-1), - this->skirt_first_layer_height(), + width, + (float)m_config.nozzle_diameter.get_at(m_objects.front()->config().support_material_extruder-1), + (float)this->skirt_first_layer_height(), 0 ); } @@ -1628,26 +1620,26 @@ void Print::_make_skirt() } // Number of skirt loops per skirt layer. - int n_skirts = m_config.skirts.value; + size_t n_skirts = m_config.skirts.value; if (this->has_infinite_skirt() && n_skirts == 0) n_skirts = 1; // Initial offset of the brim inner edge from the object (possible with a support & raft). // The skirt will touch the brim if the brim is extruded. - Flow brim_flow = this->brim_flow(); + Flow brim_flow = this->brim_flow(); double actual_brim_width = brim_flow.spacing() * floor(m_config.brim_width.value / brim_flow.spacing()); - coord_t distance = scale_(std::max(m_config.skirt_distance.value, actual_brim_width) - spacing/2.); + auto distance = float(scale_(std::max(m_config.skirt_distance.value, actual_brim_width) - spacing/2.)); // Draw outlines from outside to inside. // Loop while we have less skirts than required or any extruder hasn't reached the min length if any. std::vector extruded_length(extruders.size(), 0.); - for (int i = n_skirts, extruder_idx = 0; i > 0; -- i) { + for (size_t i = n_skirts, extruder_idx = 0; i > 0; -- i) { this->throw_if_canceled(); // Offset the skirt outside. - distance += coord_t(scale_(spacing)); + distance += float(scale_(spacing)); // Generate the skirt centerline. Polygon loop; { - Polygons loops = offset(convex_hull, distance, ClipperLib::jtRound, scale_(0.1)); + Polygons loops = offset(convex_hull, distance, ClipperLib::jtRound, float(scale_(0.1))); Geometry::simplify_polygons(loops, scale_(0.05), &loops); if (loops.empty()) break; @@ -1658,9 +1650,9 @@ void Print::_make_skirt() eloop.paths.emplace_back(ExtrusionPath( ExtrusionPath( erSkirt, - mm3_per_mm, // this will be overridden at G-code export time + (float)mm3_per_mm, // this will be overridden at G-code export time flow.width, - first_layer_height // this will be overridden at G-code export time + (float)first_layer_height // this will be overridden at G-code export time ))); eloop.paths.back().polyline = loop.split_at_first_point(); m_skirt.append(eloop); @@ -1730,7 +1722,6 @@ void Print::_make_brim() bool Print::has_wipe_tower() const { return - m_config.single_extruder_multi_material.value && ! m_config.spiral_vase.value && m_config.wipe_tower.value && m_config.nozzle_diameter.values.size() > 1; @@ -1784,9 +1775,9 @@ void Print::_make_wipe_tower() break; lt.has_support = true; // Insert the new support layer. - double height = lt.print_z - m_wipe_tower_data.tool_ordering.layer_tools()[i-1].print_z; + double height = lt.print_z - (i == 0 ? 0. : m_wipe_tower_data.tool_ordering.layer_tools()[i-1].print_z); //FIXME the support layer ID is set to -1, as Vojtech hopes it is not being used anyway. - it_layer = m_objects.front()->insert_support_layer(it_layer, size_t(-1), height, lt.print_z, lt.print_z - 0.5 * height); + it_layer = m_objects.front()->insert_support_layer(it_layer, -1, height, lt.print_z, lt.print_z - 0.5 * height); ++ it_layer; } } @@ -1794,38 +1785,19 @@ void Print::_make_wipe_tower() this->throw_if_canceled(); // Initialize the wipe tower. - WipeTowerPrusaMM wipe_tower( - float(m_config.wipe_tower_x.value), float(m_config.wipe_tower_y.value), - float(m_config.wipe_tower_width.value), - float(m_config.wipe_tower_rotation_angle.value), float(m_config.cooling_tube_retraction.value), - float(m_config.cooling_tube_length.value), float(m_config.parking_pos_retraction.value), - float(m_config.extra_loading_move.value), float(m_config.wipe_tower_bridging), - m_config.high_current_on_filament_swap.value, m_config.gcode_flavor, wipe_volumes, - m_wipe_tower_data.tool_ordering.first_extruder()); + WipeTower wipe_tower(m_config, wipe_volumes, m_wipe_tower_data.tool_ordering.first_extruder()); //wipe_tower.set_retract(); //wipe_tower.set_zhop(); // Set the extruder & material properties at the wipe tower object. for (size_t i = 0; i < number_of_extruders; ++ i) - wipe_tower.set_extruder( - i, - WipeTowerPrusaMM::parse_material(m_config.filament_type.get_at(i).c_str()), - m_config.temperature.get_at(i), - m_config.first_layer_temperature.get_at(i), - m_config.filament_loading_speed.get_at(i), - m_config.filament_loading_speed_start.get_at(i), - m_config.filament_unloading_speed.get_at(i), - m_config.filament_unloading_speed_start.get_at(i), - m_config.filament_toolchange_delay.get_at(i), - m_config.filament_cooling_moves.get_at(i), - m_config.filament_cooling_initial_speed.get_at(i), - m_config.filament_cooling_final_speed.get_at(i), - m_config.filament_ramming_parameters.get_at(i), - m_config.nozzle_diameter.get_at(i)); - m_wipe_tower_data.priming = Slic3r::make_unique( - wipe_tower.prime(this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false)); + wipe_tower.set_extruder( + i, m_config); + + m_wipe_tower_data.priming = Slic3r::make_unique>( + wipe_tower.prime((float)this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false)); // Lets go through the wipe tower layers and determine pairs of extruder changes for each // to pass to wipe_tower (so that it can use it for planning the layout of the tower) @@ -1834,21 +1806,21 @@ void Print::_make_wipe_tower() for (auto &layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) { // for all layers if (!layer_tools.has_wipe_tower) continue; bool first_layer = &layer_tools == &m_wipe_tower_data.tool_ordering.front(); - wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, current_extruder_id,false); + wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_extruder_id, current_extruder_id, false); for (const auto extruder_id : layer_tools.extruders) { if ((first_layer && extruder_id == m_wipe_tower_data.tool_ordering.all_extruders().back()) || extruder_id != current_extruder_id) { float volume_to_wipe = wipe_volumes[current_extruder_id][extruder_id]; // total volume to wipe after this toolchange // Not all of that can be used for infill purging: - volume_to_wipe -= m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); + volume_to_wipe -= (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); // try to assign some infills/objects for the wiping: volume_to_wipe = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_extruder_id, extruder_id, volume_to_wipe); // add back the minimal amount toforce on the wipe tower: - volume_to_wipe += m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); + volume_to_wipe += (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); // request a toolchange at the wipe tower with at least volume_to_wipe purging amount - wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, extruder_id, + wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_extruder_id, extruder_id, first_layer && extruder_id == m_wipe_tower_data.tool_ordering.all_extruders().back(), volume_to_wipe); current_extruder_id = extruder_id; } @@ -1904,47 +1876,7 @@ std::string Print::output_filename(const std::string &filename_base) const DynamicConfig config = this->finished() ? this->print_statistics().config() : this->print_statistics().placeholders(); return this->PrintBase::output_filename(m_config.output_filename_format.value, ".gcode", filename_base, &config); } -/* -// Shorten the dhms time by removing the seconds, rounding the dhm to full minutes -// and removing spaces. -static std::string short_time(const std::string &time) -{ - // Parse the dhms time format. - int days = 0; - int hours = 0; - int minutes = 0; - int seconds = 0; - if (time.find('d') != std::string::npos) - ::sscanf(time.c_str(), "%dd %dh %dm %ds", &days, &hours, &minutes, &seconds); - else if (time.find('h') != std::string::npos) - ::sscanf(time.c_str(), "%dh %dm %ds", &hours, &minutes, &seconds); - else if (time.find('m') != std::string::npos) - ::sscanf(time.c_str(), "%dm %ds", &minutes, &seconds); - else if (time.find('s') != std::string::npos) - ::sscanf(time.c_str(), "%ds", &seconds); - // Round to full minutes. - if (days + hours + minutes > 0 && seconds >= 30) { - if (++ minutes == 60) { - minutes = 0; - if (++ hours == 24) { - hours = 0; - ++ days; - } - } - } - // Format the dhm time. - char buffer[64]; - if (days > 0) - ::sprintf(buffer, "%dd%dh%dm", days, hours, minutes); - else if (hours > 0) - ::sprintf(buffer, "%dh%dm", hours, minutes); - else if (minutes > 0) - ::sprintf(buffer, "%dm", minutes); - else - ::sprintf(buffer, "%ds", seconds); - return buffer; -} -*/ + DynamicConfig PrintStatistics::config() const { DynamicConfig config; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 53d6d692db..b6d7b678d2 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -80,8 +80,8 @@ private: // Prevents erroneous use by other classes. typedef PrintObjectBaseWithState Inherited; public: - // vector of (vectors of volume ids), indexed by region_id - std::vector> region_volumes; + // vector of (layer height ranges and vectors of volume ids), indexed by region_id + std::vector>> region_volumes; // this is set to true when LayerRegion->slices is split in top/internal/bottom // so that next call to make_perimeters() performs a union() before computing loops @@ -99,10 +99,10 @@ public: BoundingBox bounding_box() const { return BoundingBox(Point(0,0), to_2d(this->size)); } // adds region_id, too, if necessary - void add_region_volume(unsigned int region_id, int volume_id) { + void add_region_volume(unsigned int region_id, int volume_id, const t_layer_height_range &layer_range) { if (region_id >= region_volumes.size()) region_volumes.resize(region_id + 1); - region_volumes[region_id].emplace_back(volume_id); + region_volumes[region_id].emplace_back(layer_range, volume_id); } // This is the *total* layer count (including support layers) // this value is not supposed to be compared with Layer::id @@ -120,7 +120,7 @@ public: void clear_support_layers(); SupportLayer* get_support_layer(int idx) { return m_support_layers[idx]; } SupportLayer* add_support_layer(int id, coordf_t height, coordf_t print_z); - SupportLayerPtrs::const_iterator insert_support_layer(SupportLayerPtrs::const_iterator pos, int id, coordf_t height, coordf_t print_z, coordf_t slice_z); + SupportLayerPtrs::const_iterator insert_support_layer(SupportLayerPtrs::const_iterator pos, size_t id, coordf_t height, coordf_t print_z, coordf_t slice_z); void delete_support_layer(int idx); // Initialize the layer_height_profile from the model_object's layer_height_profile, from model_object's layer height table, or from slicing parameters. @@ -141,8 +141,9 @@ public: void slice(); // Helpers to slice support enforcer / blocker meshes by the support generator. - std::vector slice_support_enforcers() const; - std::vector slice_support_blockers() const; + std::vector slice_support_volumes(const ModelVolumeType &model_volume_type) const; + std::vector slice_support_blockers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_BLOCKER); } + std::vector slice_support_enforcers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_ENFORCER); } protected: // to be called from Print only. @@ -165,7 +166,7 @@ protected: void update_slicing_parameters(); static PrintObjectConfig object_config_from_model_object(const PrintObjectConfig &default_object_config, const ModelObject &object, size_t num_extruders); - static PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_region_config, const ModelVolume &volume, size_t num_extruders); + static PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders); private: void make_perimeters(); @@ -201,9 +202,11 @@ private: LayerPtrs m_layers; SupportLayerPtrs m_support_layers; - std::vector _slice_region(size_t region_id, const std::vector &z, bool modifier); - std::vector _slice_volumes(const std::vector &z, const std::vector &volumes) const; - std::vector _slice_volume(const std::vector &z, const ModelVolume &volume) const; + std::vector slice_region(size_t region_id, const std::vector &z) const; + std::vector slice_modifiers(size_t region_id, const std::vector &z) const; + std::vector slice_volumes(const std::vector &z, const std::vector &volumes) const; + std::vector slice_volume(const std::vector &z, const ModelVolume &volume) const; + std::vector slice_volume(const std::vector &z, const std::vector &ranges, const ModelVolume &volume) const; }; struct WipeTowerData @@ -213,7 +216,7 @@ struct WipeTowerData // Cache it here, so it does not need to be recalculated during the G-code generation. ToolOrdering tool_ordering; // Cache of tool changes per print layer. - std::unique_ptr priming; + std::unique_ptr> priming; std::vector> tool_changes; std::unique_ptr final_purge; std::vector used_filament; @@ -238,6 +241,8 @@ struct PrintStatistics PrintStatistics() { clear(); } std::string estimated_normal_print_time; std::string estimated_silent_print_time; + std::vector estimated_normal_color_print_times; + std::vector estimated_silent_color_print_times; double total_used_filament; double total_extruded_volume; double total_cost; @@ -256,6 +261,8 @@ struct PrintStatistics void clear() { estimated_normal_print_time.clear(); estimated_silent_print_time.clear(); + estimated_normal_color_print_times.clear(); + estimated_silent_color_print_times.clear(); total_used_filament = 0.; total_extruded_volume = 0.; total_cost = 0.; @@ -289,12 +296,7 @@ public: void clear() override; bool empty() const override { return m_objects.empty(); } - ApplyStatus apply(const Model &model, const DynamicPrintConfig &config) override; - - // The following three methods are used by the Perl tests only. Get rid of them! - void reload_object(size_t idx); - void add_model_object(ModelObject* model_object, int idx = -1); - bool apply_config_perl_tests_only(DynamicPrintConfig config); + ApplyStatus apply(const Model &model, DynamicPrintConfig config) override; void process() override; // Exports G-code into a file name based on the path_template, returns the file path of the generated G-code file. @@ -366,6 +368,13 @@ protected: bool invalidate_step(PrintStep step); private: + void config_diffs( + const DynamicPrintConfig &new_full_config, + t_config_option_keys &print_diff, t_config_option_keys &object_diff, t_config_option_keys ®ion_diff, + t_config_option_keys &full_config_diff, + DynamicPrintConfig &placeholder_parser_overrides, + DynamicPrintConfig &filament_overrides) const; + bool invalidate_state_by_config_options(const std::vector &opt_keys); void _make_skirt(); diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index d4c39499c8..aebc879044 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -217,7 +217,7 @@ protected: class PrintBase { public: - PrintBase() { this->restart(); } + PrintBase() : m_placeholder_parser(&m_full_print_config) { this->restart(); } inline virtual ~PrintBase() {} virtual PrinterTechnology technology() const noexcept = 0; @@ -240,13 +240,13 @@ public: // Some data was changed, which in turn invalidated already calculated steps. APPLY_STATUS_INVALIDATED, }; - virtual ApplyStatus apply(const Model &model, const DynamicPrintConfig &config) = 0; + virtual ApplyStatus apply(const Model &model, DynamicPrintConfig config) = 0; const Model& model() const { return m_model; } struct TaskParams { TaskParams() : single_model_object(0), single_model_instance_only(false), to_object_step(-1), to_print_step(-1) {} // If non-empty, limit the processing to this ModelObject. - ModelID single_model_object; + ObjectID single_model_object; // If set, only process single_model_object. Otherwise process everything, but single_model_object first. bool single_model_instance_only; // If non-negative, stop processing at the successive object step. @@ -316,7 +316,7 @@ public: virtual bool finished() const = 0; const PlaceholderParser& placeholder_parser() const { return m_placeholder_parser; } - PlaceholderParser& placeholder_parser() { return m_placeholder_parser; } + const DynamicPrintConfig& full_print_config() const { return m_full_print_config; } virtual std::string output_filename(const std::string &filename_base = std::string()) const = 0; // If the filename_base is set, it is used as the input for the template processing. In that case the path is expected to be the directory (may be empty). @@ -341,6 +341,8 @@ protected: void update_object_placeholders(DynamicConfig &config, const std::string &default_ext) const; Model m_model; + DynamicPrintConfig m_full_print_config; + PlaceholderParser m_placeholder_parser; private: tbb::atomic m_cancel_status; @@ -354,8 +356,6 @@ private: // The mutex will be used to guard the worker thread against entering a stage // while the data influencing the stage is modified. mutable tbb::mutex m_state_mutex; - - PlaceholderParser m_placeholder_parser; }; template diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 87ea263012..8f56c1b83d 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -12,7 +12,7 @@ namespace Slic3r { -//! macro used to mark string used at localization, +//! macro used to mark string used at localization, //! return same string #define L(s) (s) #define _(s) Slic3r::I18N::translate(s) @@ -29,6 +29,7 @@ PrintConfigDef::PrintConfigDef() this->init_common_params(); assign_printer_technology_to_unknown(this->options, ptAny); this->init_fff_params(); + this->init_extruder_retract_keys(); assign_printer_technology_to_unknown(this->options, ptFFF); this->init_sla_params(); assign_printer_technology_to_unknown(this->options, ptSLA); @@ -36,7 +37,6 @@ PrintConfigDef::PrintConfigDef() void PrintConfigDef::init_common_params() { - t_optiondef_map &Options = this->options; ConfigOptionDef* def; def = this->add("printer_technology", coEnum); @@ -51,7 +51,17 @@ void PrintConfigDef::init_common_params() def->label = L("Bed shape"); def->mode = comAdvanced; def->set_default_value(new ConfigOptionPoints{ Vec2d(0, 0), Vec2d(200, 0), Vec2d(200, 200), Vec2d(0, 200) }); - + + def = this->add("bed_custom_texture", coString); + def->label = L("Bed custom texture"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("")); + + def = this->add("bed_custom_model", coString); + def->label = L("Bed custom model"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("")); + def = this->add("layer_height", coFloat); def->label = L("Layer height"); def->category = L("Layers and Perimeters"); @@ -75,8 +85,8 @@ void PrintConfigDef::init_common_params() "The gap closing operation may reduce the final print resolution, therefore it is advisable to keep the value reasonably low."); def->sidetext = L("mm"); def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.049)); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.049)); def = this->add("print_host", coString); def->label = L("Hostname, IP or URL"); @@ -91,7 +101,7 @@ void PrintConfigDef::init_common_params() "the API Key or the password required for authentication."); def->mode = comAdvanced; def->set_default_value(new ConfigOptionString("")); - + def = this->add("printhost_cafile", coString); def->label = L("HTTPS CA File"); def->tooltip = L("Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. " @@ -102,15 +112,14 @@ void PrintConfigDef::init_common_params() void PrintConfigDef::init_fff_params() { - t_optiondef_map &Options = this->options; ConfigOptionDef* def; // Maximum extruder temperature, bumped to 1500 to support printing of glass. const int max_temp = 1500; - def = this->add("avoid_crossing_perimeters", coBool); + def = this->add("avoid_crossing_perimeters", coBool); def->label = L("Avoid crossing perimeters"); - def->tooltip = L("Optimize travel moves in order to minimize the crossing of perimeters. " + def->tooltip = L("Optimize travel moves in order to minimize the crossing of perimeters. " "This is mostly useful with Bowden extruders which suffer from oozing. " "This feature slows down both the print and the G-code generation."); def->mode = comExpert; @@ -169,7 +178,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Bridging angle override. If left to zero, the bridging angle will be calculated " "automatically. Otherwise the provided angle will be used for all bridges. " "Use 180° for zero angle."); - def->sidetext = L("°"); + def->sidetext = L("°"); def->min = 0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.)); @@ -191,9 +200,9 @@ void PrintConfigDef::init_fff_params() "although default settings are usually good and you should experiment " "with cooling (use a fan) before tweaking this."); def->min = 0; - def->max = 2; + def->max = 2; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(1)); + def->set_default_value(new ConfigOptionFloat(1)); def = this->add("bridge_speed", coFloat); def->label = L("Bridges"); @@ -368,7 +377,8 @@ void PrintConfigDef::init_fff_params() def = this->add("end_filament_gcode", coStrings); def->label = L("End G-code"); - def->tooltip = L("This end procedure is inserted at the end of the output file, before the printer end gcode. " + def->tooltip = L("This end procedure is inserted at the end of the output file, before the printer end gcode (and " + "before any toolchange from this filament in case of multimaterial printers). " "Note that you can use placeholder variables for all Slic3r settings. " "If you have multiple extruders, the gcode is processed in extruder order."); def->multiline = true; @@ -406,10 +416,14 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionEnum(ipRectilinear)); def = this->add("bottom_fill_pattern", coEnum); - *def = *def_top_fill_pattern; def->label = L("Bottom fill pattern"); + def->category = L("Infill"); def->tooltip = L("Fill pattern for bottom infill. This only affects the bottom external visible layer, and not its adjacent solid shells."); def->cli = "bottom-fill-pattern|external-fill-pattern|solid-fill-pattern"; + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values = def_top_fill_pattern->enum_values; + def->enum_labels = def_top_fill_pattern->enum_labels; + def->aliases = def_top_fill_pattern->aliases; def->set_default_value(new ConfigOptionEnum(ipRectilinear)); def = this->add("external_perimeter_extrusion_width", coFloatOrPercent); @@ -518,7 +532,7 @@ void PrintConfigDef::init_fff_params() "check filament diameter and your firmware E steps."); def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloats { 1. }); - + def = this->add("extrusion_width", coFloatOrPercent); def->label = L("Default extrusion width"); def->category = L("Extrusion Width"); @@ -664,7 +678,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("This string is edited by RammingDialog and contains ramming specific parameters."); def->mode = comExpert; def->set_default_value(new ConfigOptionStrings { "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0|" - " 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" }); + " 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" }); def = this->add("filament_unload_time", coFloats); def->label = L("Filament unload time"); @@ -730,7 +744,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("money/kg"); def->min = 0; def->set_default_value(new ConfigOptionFloats { 0. }); - + def = this->add("filament_settings_id", coStrings); def->set_default_value(new ConfigOptionStrings { "" }); def->cli = ConfigOptionDef::nocli; @@ -876,7 +890,7 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->max = max_temp; def->set_default_value(new ConfigOptionInts { 200 }); - + def = this->add("gap_fill_speed", coFloat); def->label = L("Gap fill"); def->category = L("Speed"); @@ -1059,85 +1073,85 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert; def->set_default_value(new ConfigOptionBool(false)); - def = this->add("silent_mode", coBool); - def->label = L("Supports stealth mode"); - def->tooltip = L("The firmware supports stealth mode"); + def = this->add("silent_mode", coBool); + def->label = L("Supports stealth mode"); + def->tooltip = L("The firmware supports stealth mode"); def->mode = comExpert; - def->set_default_value(new ConfigOptionBool(true)); + def->set_default_value(new ConfigOptionBool(true)); - const int machine_limits_opt_width = 7; - { - struct AxisDefault { - std::string name; - std::vector max_feedrate; - std::vector max_acceleration; - std::vector max_jerk; - }; - std::vector axes { - // name, max_feedrate, max_acceleration, max_jerk - { "x", { 500., 200. }, { 9000., 1000. }, { 10. , 10. } }, - { "y", { 500., 200. }, { 9000., 1000. }, { 10. , 10. } }, - { "z", { 12., 12. }, { 500., 200. }, { 0.2, 0.4 } }, - { "e", { 120., 120. }, { 10000., 5000. }, { 2.5, 2.5 } } - }; - for (const AxisDefault &axis : axes) { - std::string axis_upper = boost::to_upper_copy(axis.name); - // Add the machine feedrate limits for XYZE axes. (M203) - def = this->add("machine_max_feedrate_" + axis.name, coFloats); - def->full_label = (boost::format("Maximum feedrate %1%") % axis_upper).str(); - L("Maximum feedrate X"); - L("Maximum feedrate Y"); - L("Maximum feedrate Z"); - L("Maximum feedrate E"); - def->category = L("Machine limits"); - def->tooltip = (boost::format("Maximum feedrate of the %1% axis") % axis_upper).str(); - L("Maximum feedrate of the X axis"); - L("Maximum feedrate of the Y axis"); - L("Maximum feedrate of the Z axis"); - L("Maximum feedrate of the E axis"); - def->sidetext = L("mm/s"); - def->min = 0; - def->width = machine_limits_opt_width; + const int machine_limits_opt_width = 7; + { + struct AxisDefault { + std::string name; + std::vector max_feedrate; + std::vector max_acceleration; + std::vector max_jerk; + }; + std::vector axes { + // name, max_feedrate, max_acceleration, max_jerk + { "x", { 500., 200. }, { 9000., 1000. }, { 10. , 10. } }, + { "y", { 500., 200. }, { 9000., 1000. }, { 10. , 10. } }, + { "z", { 12., 12. }, { 500., 200. }, { 0.2, 0.4 } }, + { "e", { 120., 120. }, { 10000., 5000. }, { 2.5, 2.5 } } + }; + for (const AxisDefault &axis : axes) { + std::string axis_upper = boost::to_upper_copy(axis.name); + // Add the machine feedrate limits for XYZE axes. (M203) + def = this->add("machine_max_feedrate_" + axis.name, coFloats); + def->full_label = (boost::format("Maximum feedrate %1%") % axis_upper).str(); + (void)L("Maximum feedrate X"); + (void)L("Maximum feedrate Y"); + (void)L("Maximum feedrate Z"); + (void)L("Maximum feedrate E"); + def->category = L("Machine limits"); + def->tooltip = (boost::format("Maximum feedrate of the %1% axis") % axis_upper).str(); + (void)L("Maximum feedrate of the X axis"); + (void)L("Maximum feedrate of the Y axis"); + (void)L("Maximum feedrate of the Z axis"); + (void)L("Maximum feedrate of the E axis"); + def->sidetext = L("mm/s"); + def->min = 0; + def->width = machine_limits_opt_width; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats(axis.max_feedrate)); - // Add the machine acceleration limits for XYZE axes (M201) - def = this->add("machine_max_acceleration_" + axis.name, coFloats); - def->full_label = (boost::format("Maximum acceleration %1%") % axis_upper).str(); - L("Maximum acceleration X"); - L("Maximum acceleration Y"); - L("Maximum acceleration Z"); - L("Maximum acceleration E"); - def->category = L("Machine limits"); - def->tooltip = (boost::format("Maximum acceleration of the %1% axis") % axis_upper).str(); - L("Maximum acceleration of the X axis"); - L("Maximum acceleration of the Y axis"); - L("Maximum acceleration of the Z axis"); - L("Maximum acceleration of the E axis"); - def->sidetext = L("mm/s²"); - def->min = 0; - def->width = machine_limits_opt_width; + def->set_default_value(new ConfigOptionFloats(axis.max_feedrate)); + // Add the machine acceleration limits for XYZE axes (M201) + def = this->add("machine_max_acceleration_" + axis.name, coFloats); + def->full_label = (boost::format("Maximum acceleration %1%") % axis_upper).str(); + (void)L("Maximum acceleration X"); + (void)L("Maximum acceleration Y"); + (void)L("Maximum acceleration Z"); + (void)L("Maximum acceleration E"); + def->category = L("Machine limits"); + def->tooltip = (boost::format("Maximum acceleration of the %1% axis") % axis_upper).str(); + (void)L("Maximum acceleration of the X axis"); + (void)L("Maximum acceleration of the Y axis"); + (void)L("Maximum acceleration of the Z axis"); + (void)L("Maximum acceleration of the E axis"); + def->sidetext = L("mm/s²"); + def->min = 0; + def->width = machine_limits_opt_width; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats(axis.max_acceleration)); - // Add the machine jerk limits for XYZE axes (M205) - def = this->add("machine_max_jerk_" + axis.name, coFloats); - def->full_label = (boost::format("Maximum jerk %1%") % axis_upper).str(); - L("Maximum jerk X"); - L("Maximum jerk Y"); - L("Maximum jerk Z"); - L("Maximum jerk E"); - def->category = L("Machine limits"); - def->tooltip = (boost::format("Maximum jerk of the %1% axis") % axis_upper).str(); - L("Maximum jerk of the X axis"); - L("Maximum jerk of the Y axis"); - L("Maximum jerk of the Z axis"); - L("Maximum jerk of the E axis"); - def->sidetext = L("mm/s"); - def->min = 0; - def->width = machine_limits_opt_width; + def->set_default_value(new ConfigOptionFloats(axis.max_acceleration)); + // Add the machine jerk limits for XYZE axes (M205) + def = this->add("machine_max_jerk_" + axis.name, coFloats); + def->full_label = (boost::format("Maximum jerk %1%") % axis_upper).str(); + (void)L("Maximum jerk X"); + (void)L("Maximum jerk Y"); + (void)L("Maximum jerk Z"); + (void)L("Maximum jerk E"); + def->category = L("Machine limits"); + def->tooltip = (boost::format("Maximum jerk of the %1% axis") % axis_upper).str(); + (void)L("Maximum jerk of the X axis"); + (void)L("Maximum jerk of the Y axis"); + (void)L("Maximum jerk of the Z axis"); + (void)L("Maximum jerk of the E axis"); + def->sidetext = L("mm/s"); + def->min = 0; + def->width = machine_limits_opt_width; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats(axis.max_jerk)); - } - } + def->set_default_value(new ConfigOptionFloats(axis.max_jerk)); + } + } // M205 S... [mm/sec] def = this->add("machine_min_extruding_rate", coFloats); @@ -1146,9 +1160,9 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Minimum feedrate when extruding (M205 S)"); def->sidetext = L("mm/s"); def->min = 0; - def->width = machine_limits_opt_width; + def->width = machine_limits_opt_width; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats{ 0., 0. }); + def->set_default_value(new ConfigOptionFloats{ 0., 0. }); // M205 T... [mm/sec] def = this->add("machine_min_travel_rate", coFloats); @@ -1157,9 +1171,9 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Minimum travel feedrate (M205 T)"); def->sidetext = L("mm/s"); def->min = 0; - def->width = machine_limits_opt_width; + def->width = machine_limits_opt_width; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats{ 0., 0. }); + def->set_default_value(new ConfigOptionFloats{ 0., 0. }); // M204 S... [mm/sec^2] def = this->add("machine_max_acceleration_extruding", coFloats); @@ -1168,7 +1182,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Maximum acceleration when extruding (M204 S)"); def->sidetext = L("mm/s²"); def->min = 0; - def->width = machine_limits_opt_width; + def->width = machine_limits_opt_width; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloats{ 1500., 1250. }); @@ -1179,7 +1193,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Maximum acceleration when retracting (M204 T)"); def->sidetext = L("mm/s²"); def->min = 0; - def->width = machine_limits_opt_width; + def->width = machine_limits_opt_width; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloats{ 1500., 1250. }); @@ -1423,9 +1437,9 @@ void PrintConfigDef::init_fff_params() def->gui_flags = "serialized"; def->multiline = true; def->full_width = true; - def->height = 6; + def->height = 6; def->mode = comExpert; - def->set_default_value(new ConfigOptionStrings()); + def->set_default_value(new ConfigOptionStrings()); def = this->add("printer_model", coString); def->label = L("Printer type"); @@ -1457,7 +1471,7 @@ void PrintConfigDef::init_fff_params() def = this->add("print_settings_id", coString); def->set_default_value(new ConfigOptionString("")); def->cli = ConfigOptionDef::nocli; - + def = this->add("printer_settings_id", coString); def->set_default_value(new ConfigOptionString("")); def->cli = ConfigOptionDef::nocli; @@ -1497,7 +1511,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("%"); def->mode = comAdvanced; def->set_default_value(new ConfigOptionPercents { 0. }); - + def = this->add("retract_layer_change", coBools); def->label = L("Retract on layer change"); def->tooltip = L("This flag enforces a retraction whenever a Z move is done."); @@ -1594,7 +1608,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Random")); def->enum_labels.push_back(L("Nearest")); def->enum_labels.push_back(L("Aligned")); - def->enum_labels.push_back(L("Rear")); + def->enum_labels.push_back(L("Rear")); def->mode = comSimple; def->set_default_value(new ConfigOptionEnum(spAligned)); @@ -1665,7 +1679,7 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionInt(1)); - + def = this->add("slowdown_below_layer_time", coInts); def->label = L("Slow down if layer print time is below"); def->tooltip = L("If layer print time is estimated below this number of seconds, print moves " @@ -1761,7 +1775,7 @@ void PrintConfigDef::init_fff_params() def->label = L("Temperature variation"); def->tooltip = L("Temperature difference to be applied when an extruder is not active. " "Enables a full-height \"sacrificial\" skirt on which the nozzles are periodically wiped."); - def->sidetext = "∆°C"; + def->sidetext = "∆°C"; def->min = -max_temp; def->max = max_temp; def->mode = comExpert; @@ -1784,7 +1798,8 @@ void PrintConfigDef::init_fff_params() def = this->add("start_filament_gcode", coStrings); def->label = L("Start G-code"); - def->tooltip = L("This start procedure is inserted at the beginning, after any printer start gcode. " + def->tooltip = L("This start procedure is inserted at the beginning, after any printer start gcode (and " + "after any toolchange to this filament in case of multi-material printers). " "This is used to override settings for a specific filament. If Slic3r detects " "M104, M109, M140 or M190 in your custom codes, such commands will " "not be prepended automatically so you're free to customize the order " @@ -1802,7 +1817,7 @@ void PrintConfigDef::init_fff_params() def->label = L("Single Extruder Multi Material"); def->tooltip = L("The printer multiplexes filaments into a single hot end."); def->mode = comExpert; - def->set_default_value(new ConfigOptionBool(false)); + def->set_default_value(new ConfigOptionBool(false)); def = this->add("single_extruder_multi_material_priming", coBool); def->label = L("Prime all printing extruders"); @@ -1864,8 +1879,8 @@ void PrintConfigDef::init_fff_params() // def->min = 0; def->enum_values.push_back("0"); def->enum_values.push_back("0.2"); - def->enum_labels.push_back(L("0 (soluble)")); - def->enum_labels.push_back(L("0.2 (detachable)")); + def->enum_labels.push_back(L("0 (soluble)")); + def->enum_labels.push_back(L("0.2 (detachable)")); def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.2)); @@ -1954,7 +1969,7 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("rectilinear"); def->enum_values.push_back("rectilinear-grid"); def->enum_values.push_back("honeycomb"); - def->enum_labels.push_back(L("Rectilinear")); + def->enum_labels.push_back(L("Rectilinear")); def->enum_labels.push_back(L("Rectilinear grid")); def->enum_labels.push_back(L("Honeycomb")); def->mode = comAdvanced; @@ -2016,7 +2031,7 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->max = max_temp; def->set_default_value(new ConfigOptionInts { 200 }); - + def = this->add("thin_walls", coBool); def->label = L("Detect thin walls"); def->category = L("Layers and Perimeters"); @@ -2036,12 +2051,13 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionInt(threads > 0 ? threads : 2)); def->cli == ConfigOptionDef::nocli; } - + def = this->add("toolchange_gcode", coString); def->label = L("Tool change G-code"); - def->tooltip = L("This custom code is inserted right before every extruder change. " - "Note that you can use placeholder variables for all Slic3r settings as well " - "as [previous_extruder] and [next_extruder]."); + def->tooltip = L("This custom code is inserted at every extruder change. If you don't leave this empty, you are " + "expected to take care of the toolchange yourself - PrusaSlicer will not output any other G-code to " + "change the filament. You can use placeholder variables for all Slic3r settings as well as [previous_extruder] " + "and [next_extruder], so e.g. the standard toolchange command can be scripted as T[next_extruder]."); def->multiline = true; def->full_width = true; def->height = 5; @@ -2183,7 +2199,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionFloat(0.)); def = this->add("wipe_into_infill", coBool); - def->category = L("Extruders"); + def->category = L("Wipe options"); def->label = L("Wipe into this object's infill"); def->tooltip = L("Purging after toolchange will done inside this object's infills. " "This lowers the amount of waste but may result in longer print time " @@ -2191,7 +2207,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionBool(false)); def = this->add("wipe_into_objects", coBool); - def->category = L("Extruders"); + def->category = L("Wipe options"); def->label = L("Wipe into this object"); def->tooltip = L("Object will be used to purge the nozzle after a toolchange to save material " "that would otherwise end up in the wipe tower and decrease print time. " @@ -2224,11 +2240,52 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0)); + + // Declare retract values for filament profile, overriding the printer's extruder profile. + for (const char *opt_key : { + // floats + "retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed", "retract_restart_extra", "retract_before_travel", + // bools + "retract_layer_change", "wipe", + // percents + "retract_before_wipe"}) { + auto it_opt = options.find(opt_key); + assert(it_opt != options.end()); + def = this->add_nullable(std::string("filament_") + opt_key, it_opt->second.type); + def->label = it_opt->second.label; + def->full_label = it_opt->second.full_label; + def->tooltip = it_opt->second.tooltip; + def->sidetext = it_opt->second.sidetext; + def->mode = it_opt->second.mode; + switch (def->type) { + case coFloats : def->set_default_value(new ConfigOptionFloatsNullable (static_cast(it_opt->second.default_value.get())->values)); break; + case coPercents : def->set_default_value(new ConfigOptionPercentsNullable(static_cast(it_opt->second.default_value.get())->values)); break; + case coBools : def->set_default_value(new ConfigOptionBoolsNullable (static_cast(it_opt->second.default_value.get())->values)); break; + default: assert(false); + } + } +} + +void PrintConfigDef::init_extruder_retract_keys() +{ + m_extruder_retract_keys = { + "deretract_speed", + "retract_before_travel", + "retract_before_wipe", + "retract_layer_change", + "retract_length", + "retract_lift", + "retract_lift_above", + "retract_lift_below", + "retract_restart_extra", + "retract_speed", + "wipe" + }; + assert(std::is_sorted(m_extruder_retract_keys.begin(), m_extruder_retract_keys.end())); } void PrintConfigDef::init_sla_params() { - t_optiondef_map &Options = this->options; ConfigOptionDef* def; // SLA Printer settings @@ -2258,6 +2315,20 @@ void PrintConfigDef::init_sla_params() def->min = 100; def->set_default_value(new ConfigOptionInt(1440)); + def = this->add("display_mirror_x", coBool); + def->full_label = L("Display horizontal mirroring"); + def->label = L("Mirror horizontally"); + def->tooltip = L("Enable horizontal mirroring of output images"); + def->mode = comExpert; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("display_mirror_y", coBool); + def->full_label = L("Display vertical mirroring"); + def->label = L("Mirror vertically"); + def->tooltip = L("Enable vertical mirroring of output images"); + def->mode = comExpert; + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("display_orientation", coEnum); def->label = L("Display orientation"); def->tooltip = L("Set the actual LCD display orientation inside the SLA printer." @@ -2304,7 +2375,7 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->mode = comExpert; def->set_default_value(new ConfigOptionFloats( { 1., 1. } )); - + def = this->add("absolute_correction", coFloat); def->label = L("Printer absolute correction"); def->full_label = L("Printer absolute correction"); @@ -2312,7 +2383,7 @@ void PrintConfigDef::init_sla_params() "to the sign of the correction."); def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(0.0)); - + def = this->add("gamma_correction", coFloat); def->label = L("Printer gamma correction"); def->full_label = L("Printer gamma correction"); @@ -2323,7 +2394,7 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(1.0)); - + // SLA Material settings. def = this->add("initial_layer_height", coFloat); @@ -2341,6 +2412,22 @@ void PrintConfigDef::init_sla_params() def->mode = comExpert; def->set_default_value(new ConfigOptionInt(10)); + def = this->add("min_exposure_time", coFloat); + def->label = L("Minimum exposure time"); + def->tooltip = L("Minimum exposure time"); + def->sidetext = L("s"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("max_exposure_time", coFloat); + def->label = L("Maximum exposure time"); + def->tooltip = L("Maximum exposure time"); + def->sidetext = L("s"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(100)); + def = this->add("exposure_time", coFloat); def->label = L("Exposure time"); def->tooltip = L("Exposure time"); @@ -2348,6 +2435,22 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->set_default_value(new ConfigOptionFloat(10)); + def = this->add("min_initial_exposure_time", coFloat); + def->label = L("Minimum initial exposure time"); + def->tooltip = L("Minimum initial exposure time"); + def->sidetext = L("s"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("max_initial_exposure_time", coFloat); + def->label = L("Maximum initial exposure time"); + def->tooltip = L("Maximum initial exposure time"); + def->sidetext = L("s"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(150)); + def = this->add("initial_exposure_time", coFloat); def->label = L("Initial exposure time"); def->tooltip = L("Initial exposure time"); @@ -2491,6 +2594,19 @@ void PrintConfigDef::init_sla_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(1.0)); + def = this->add("support_base_safety_distance", coFloat); + def->label = L("Support base safety distance"); + def->category = L("Supports"); + def->tooltip = L( + "The minimum distance of the pillar base from the model in mm. " + "Makes sense in zero elevation mode where a gap according " + "to this parameter is inserted between the model and the pad."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 10; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(1)); + def = this->add("support_critical_angle", coFloat); def->label = L("Critical angle"); def->category = L("Supports"); @@ -2523,7 +2639,9 @@ void PrintConfigDef::init_sla_params() def = this->add("support_object_elevation", coFloat); def->label = L("Object elevation"); def->category = L("Supports"); - def->tooltip = L("How much the supports should lift up the supported object."); + def->tooltip = L("How much the supports should lift up the supported object. " + "If this value is zero, the bottom of the model geometry " + "will be considered as part of the pad."); def->sidetext = L("mm"); def->min = 0; def->max = 150; // This is the max height of print on SL1 @@ -2590,14 +2708,14 @@ void PrintConfigDef::init_sla_params() def->set_default_value(new ConfigOptionFloat(50.0)); // This is disabled on the UI. I hope it will never be enabled. - def = this->add("pad_edge_radius", coFloat); - def->label = L("Pad edge radius"); - def->category = L("Pad"); -// def->tooltip = L(""); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(1.0)); +// def = this->add("pad_edge_radius", coFloat); +// def->label = L("Pad edge radius"); +// def->category = L("Pad"); +//// def->tooltip = L(""); +// def->sidetext = L("mm"); +// def->min = 0; +// def->mode = comAdvanced; +// def->set_default_value(new ConfigOptionFloat(1.0)); def = this->add("pad_wall_slope", coFloat); def->label = L("Pad wall slope"); @@ -2609,6 +2727,54 @@ void PrintConfigDef::init_sla_params() def->max = 90; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(45.0)); + + def = this->add("pad_zero_elevation", coBool); + def->label = L("Pad around object"); + def->category = L("Pad"); + def->tooltip = L("Create pad around object and ignore the support elevation"); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("pad_object_gap", coFloat); + def->label = L("Pad object gap"); + def->category = L("Pad"); + def->tooltip = L("The gap between the object bottom and the generated " + "pad in zero elevation mode."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 10; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(1)); + + def = this->add("pad_object_connector_stride", coFloat); + def->label = L("Pad object connector stride"); + def->category = L("Pad"); + def->tooltip = L("Distance between two connector sticks between " + "the object pad and the generated pad."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(10)); + + def = this->add("pad_object_connector_width", coFloat); + def->label = L("Pad object connector width"); + def->category = L("Pad"); + def->tooltip = L("The width of the connectors sticks which connect the " + "object pad and the generated pad."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(0.5)); + + def = this->add("pad_object_connector_penetration", coFloat); + def->label = L("Pad object connector penetration"); + def->category = L("Pad"); + def->tooltip = L( + "How much should the tiny connectors penetrate into the model body."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(0.3)); } void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value) @@ -2620,7 +2786,7 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va if (opt_key == "bottom_layer_speed") opt_key = "first_layer_speed"; try { float v = boost::lexical_cast(value); - if (v != 0) + if (v != 0) value = boost::lexical_cast(v*100) + "%"; } catch (boost::bad_lexical_cast &) { value = "0"; @@ -2660,14 +2826,14 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va } else if (opt_key == "octoprint_apikey") { opt_key = "printhost_apikey"; } - + // Ignore the following obsolete configuration keys: static std::set ignore = { "duplicate_x", "duplicate_y", "gcode_arcs", "multiply_x", "multiply_y", - "support_material_tool", "acceleration", "adjust_overhang_flow", + "support_material_tool", "acceleration", "adjust_overhang_flow", "standby_temperature", "scale", "rotate", "duplicate", "duplicate_grid", - "start_perimeters_at_concave_points", "start_perimeters_at_non_overhang", "randomize_start", - "seal_position", "vibration_limit", "bed_size", + "start_perimeters_at_concave_points", "start_perimeters_at_non_overhang", "randomize_start", + "seal_position", "vibration_limit", "bed_size", "print_center", "g0", "threads", "pressure_advance", "wipe_tower_per_color_wipe" #ifndef HAS_PRESSURE_EQUALIZER , "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative" @@ -2678,7 +2844,7 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va opt_key = ""; return; } - + if (! print_config_def.has(opt_key)) { opt_key = ""; return; @@ -2718,10 +2884,10 @@ void DynamicPrintConfig::normalize() // this->option("support_material_interface_extruder", true)->setInt(extruder); } } - + if (!this->has("solid_infill_extruder") && this->has("infill_extruder")) this->option("solid_infill_extruder", true)->setInt(this->option("infill_extruder")->getInt()); - + if (this->has("spiral_vase") && this->opt("spiral_vase", true)->value) { { // this should be actually done only on the spiral layers instead of all @@ -2739,8 +2905,8 @@ void DynamicPrintConfig::normalize() std::string DynamicPrintConfig::validate() { // Full print config is initialized from the defaults. - const ConfigOption *opt = this->option("printer_technology", false); - auto printer_technology = (opt == nullptr) ? ptFFF : static_cast(dynamic_cast(opt)->value); + const ConfigOption *opt = this->option("printer_technology", false); + auto printer_technology = (opt == nullptr) ? ptFFF : static_cast(dynamic_cast(opt)->value); switch (printer_technology) { case ptFFF: { @@ -2764,7 +2930,7 @@ double PrintConfig::min_object_distance(const ConfigBase *config) { double extruder_clearance_radius = config->option("extruder_clearance_radius")->getFloat(); double duplicate_distance = config->option("duplicate_distance")->getFloat(); - + // min object distance is max(duplicate_distance, clearance_radius) return (config->option("complete_objects")->getBool() && extruder_clearance_radius > duplicate_distance) ? extruder_clearance_radius @@ -2793,7 +2959,7 @@ std::string FullPrintConfig::validate() for (double nd : this->nozzle_diameter.values) if (nd < 0.005) return "Invalid value for --nozzle-diameter"; - + // --perimeters if (this->perimeters.value < 0) return "Invalid value for --perimeters"; @@ -2803,8 +2969,8 @@ std::string FullPrintConfig::validate() return "Invalid value for --top-solid-layers"; if (this->bottom_solid_layers < 0) return "Invalid value for --bottom-solid-layers"; - - if (this->use_firmware_retraction.value && + + if (this->use_firmware_retraction.value && this->gcode_flavor.value != gcfSmoothie && this->gcode_flavor.value != gcfRepRap && this->gcode_flavor.value != gcfMarlin && @@ -2816,15 +2982,15 @@ std::string FullPrintConfig::validate() for (unsigned char wipe : this->wipe.values) if (wipe) return "--use-firmware-retraction is not compatible with --wipe"; - + // --gcode-flavor if (! print_config_def.get("gcode_flavor")->has_enum_value(this->gcode_flavor.serialize())) return "Invalid value for --gcode-flavor"; - + // --fill-pattern if (! print_config_def.get("fill_pattern")->has_enum_value(this->fill_pattern.serialize())) return "Invalid value for --fill-pattern"; - + // --top-fill-pattern if (! print_config_def.get("top_fill_pattern")->has_enum_value(this->top_fill_pattern.serialize())) return "Invalid value for --top-fill-pattern"; @@ -2837,7 +3003,7 @@ std::string FullPrintConfig::validate() if (fabs(this->fill_density.value - 100.) < EPSILON && ! print_config_def.get("top_fill_pattern")->has_enum_value(this->fill_pattern.serialize())) return "The selected fill pattern is not supposed to work at 100% density"; - + // --infill-every-layers if (this->infill_every_layers < 1) return "Invalid value for --infill-every-layers"; @@ -2845,11 +3011,11 @@ std::string FullPrintConfig::validate() // --skirt-height if (this->skirt_height < -1) // -1 means as tall as the object return "Invalid value for --skirt-height"; - + // --bridge-flow-ratio if (this->bridge_flow_ratio <= 0) return "Invalid value for --bridge-flow-ratio"; - + // extruder clearance if (this->extruder_clearance_radius <= 0) return "Invalid value for --extruder-clearance-radius"; @@ -2881,7 +3047,7 @@ std::string FullPrintConfig::validate() if (this->support_material || this->support_material_enforce_layers > 0) return "Spiral vase mode is not compatible with support material"; } - + // extrusion widths { double max_nozzle_diameter = 0.; @@ -2914,7 +3080,7 @@ std::string FullPrintConfig::validate() } case coFloats: case coPercents: - for (double v : static_cast(opt)->values) + for (double v : static_cast*>(opt)->values) if (v < optdef->min || v > optdef->max) { out_of_range = true; break; @@ -2927,7 +3093,7 @@ std::string FullPrintConfig::validate() break; } case coInts: - for (int v : static_cast(opt)->values) + for (int v : static_cast*>(opt)->values) if (v < optdef->min || v > optdef->max) { out_of_range = true; break; @@ -2938,7 +3104,7 @@ std::string FullPrintConfig::validate() if (out_of_range) return std::string("Value out of range: " + opt_key); } - + // The configuration is valid. return ""; } @@ -2961,20 +3127,20 @@ StaticPrintConfig::StaticCache SLAFullPrint CLIActionsConfigDef::CLIActionsConfigDef() { ConfigOptionDef* def; - + // Actions: def = this->add("export_obj", coBool); def->label = L("Export OBJ"); def->tooltip = L("Export the model(s) as OBJ."); def->set_default_value(new ConfigOptionBool(false)); - + /* def = this->add("export_svg", coBool); def->label = L("Export SVG"); def->tooltip = L("Slice the model and export solid slices as SVG."); def->set_default_value(new ConfigOptionBool(false)); */ - + def = this->add("export_sla", coBool); def->label = L("Export SLA"); def->tooltip = L("Slice the model and export SLA printing layers as PNG."); @@ -3023,12 +3189,12 @@ CLIActionsConfigDef::CLIActionsConfigDef() def->label = L("Help (SLA options)"); def->tooltip = L("Show the full list of SLA print configuration options."); def->set_default_value(new ConfigOptionBool(false)); - + def = this->add("info", coBool); def->label = L("Output Model Info"); def->tooltip = L("Write information about the model to the console."); def->set_default_value(new ConfigOptionBool(false)); - + def = this->add("save", coString); def->label = L("Save config file"); def->tooltip = L("Save configuration to the specified file."); @@ -3038,35 +3204,35 @@ CLIActionsConfigDef::CLIActionsConfigDef() CLITransformConfigDef::CLITransformConfigDef() { ConfigOptionDef* def; - + // Transform options: def = this->add("align_xy", coPoint); def->label = L("Align XY"); def->tooltip = L("Align the model to the given point."); def->set_default_value(new ConfigOptionPoint(Vec2d(100,100))); - + def = this->add("cut", coFloat); def->label = L("Cut"); def->tooltip = L("Cut model at the given Z."); def->set_default_value(new ConfigOptionFloat(0)); - + /* def = this->add("cut_grid", coFloat); def->label = L("Cut"); def->tooltip = L("Cut model in the XY plane into tiles of the specified max size."); def->set_default_value(new ConfigOptionPoint()); - + def = this->add("cut_x", coFloat); def->label = L("Cut"); def->tooltip = L("Cut model at the given X."); def->set_default_value(new ConfigOptionFloat(0)); - + def = this->add("cut_y", coFloat); def->label = L("Cut"); def->tooltip = L("Cut model at the given Y."); def->set_default_value(new ConfigOptionFloat(0)); */ - + def = this->add("center", coPoint); def->label = L("Center"); def->tooltip = L("Center the print around the given center."); @@ -3075,12 +3241,12 @@ CLITransformConfigDef::CLITransformConfigDef() def = this->add("dont_arrange", coBool); def->label = L("Don't arrange"); def->tooltip = L("Do not rearrange the given models before merging and keep their original XY coordinates."); - + def = this->add("duplicate", coInt); def->label = L("Duplicate"); def->tooltip =L("Multiply copies by this factor."); def->min = 1; - + def = this->add("duplicate_grid", coPoint); def->label = L("Duplicate by grid"); def->tooltip = L("Multiply copies by creating a grid."); @@ -3093,22 +3259,22 @@ CLITransformConfigDef::CLITransformConfigDef() def = this->add("repair", coBool); def->label = L("Repair"); def->tooltip = L("Try to repair any non-manifold meshes (this option is implicitly added whenever we need to slice the model to perform the requested action)."); - + def = this->add("rotate", coFloat); def->label = L("Rotate"); def->tooltip = L("Rotation angle around the Z axis in degrees."); def->set_default_value(new ConfigOptionFloat(0)); - + def = this->add("rotate_x", coFloat); def->label = L("Rotate around X"); def->tooltip = L("Rotation angle around the X axis in degrees."); def->set_default_value(new ConfigOptionFloat(0)); - + def = this->add("rotate_y", coFloat); def->label = L("Rotate around Y"); def->tooltip = L("Rotation angle around the Y axis in degrees."); def->set_default_value(new ConfigOptionFloat(0)); - + def = this->add("scale", coFloatOrPercent); def->label = L("Scale"); def->tooltip = L("Scaling factor or percentage."); @@ -3117,7 +3283,7 @@ CLITransformConfigDef::CLITransformConfigDef() def = this->add("split", coBool); def->label = L("Split"); def->tooltip = L("Detect unconnected parts in the given model(s) and split them into separate objects."); - + def = this->add("scale_to_fit", coPoint3); def->label = L("Scale to Fit"); def->tooltip = L("Scale to fit the given volume."); @@ -3127,26 +3293,26 @@ CLITransformConfigDef::CLITransformConfigDef() CLIMiscConfigDef::CLIMiscConfigDef() { ConfigOptionDef* def; - + def = this->add("ignore_nonexistent_config", coBool); def->label = L("Ignore non-existent config files"); def->tooltip = L("Do not fail if a file supplied to --load does not exist."); - + def = this->add("load", coStrings); def->label = L("Load config file"); def->tooltip = L("Load configuration from the specified file. It can be used more than once to load options from multiple files."); - + def = this->add("output", coString); def->label = L("Output File"); def->tooltip = L("The file where the output will be written (if not specified, it will be based on the input file)."); def->cli = "output|o"; -/* +/* def = this->add("autosave", coString); def->label = L("Autosave"); def->tooltip = L("Automatically export current configuration to the specified file."); */ - + def = this->add("datadir", coString); def->label = L("Data directory"); def->tooltip = L("Load and store settings at the given directory. This is useful for maintaining different profiles or including configurations from a network storage."); @@ -3156,7 +3322,7 @@ CLIMiscConfigDef::CLIMiscConfigDef() def->tooltip = L("Messages with severity lower or eqal to the loglevel will be printed out. 0:trace, 1:debug, 2:info, 3:warning, 4:error, 5:fatal"); def->min = 0; -#if defined(_MSC_VER) && defined(SLIC3R_GUI) +#if (defined(_MSC_VER) || defined(__MINGW32__)) && defined(SLIC3R_GUI) def = this->add("sw_renderer", coBool); def->label = L("Render with a software renderer"); def->tooltip = L("Render with a software renderer. The bundled MESA software renderer is loaded instead of the default OpenGL driver."); @@ -3172,11 +3338,15 @@ DynamicPrintAndCLIConfig::PrintAndCLIConfigDef DynamicPrintAndCLIConfig::s_def; void DynamicPrintAndCLIConfig::handle_legacy(t_config_option_key &opt_key, std::string &value) const { - if (cli_actions_config_def .options.find(opt_key) == cli_actions_config_def .options.end() && - cli_transform_config_def.options.find(opt_key) == cli_transform_config_def.options.end() && - cli_misc_config_def .options.find(opt_key) == cli_misc_config_def .options.end()) { - PrintConfigDef::handle_legacy(opt_key, value); - } + if (cli_actions_config_def .options.find(opt_key) == cli_actions_config_def .options.end() && + cli_transform_config_def.options.find(opt_key) == cli_transform_config_def.options.end() && + cli_misc_config_def .options.find(opt_key) == cli_misc_config_def .options.end()) { + PrintConfigDef::handle_legacy(opt_key, value); + } } } + +#include +CEREAL_REGISTER_TYPE(Slic3r::DynamicPrintConfig) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::DynamicConfig, Slic3r::DynamicPrintConfig) diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 1da22b377f..35025fcd10 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -185,10 +185,18 @@ public: static void handle_legacy(t_config_option_key &opt_key, std::string &value); + // Options defining the extruder retract properties. These keys are sorted lexicographically. + // The extruder retract keys could be overidden by the same values defined at the Filament level + // (then the key is further prefixed with the "filament_" prefix). + const std::vector& extruder_retract_keys() const { return m_extruder_retract_keys; } + private: void init_common_params(); void init_fff_params(); + void init_extruder_retract_keys(); void init_sla_params(); + + std::vector m_extruder_retract_keys; }; // The one and only global definition of SLic3r configuration options. @@ -732,7 +740,7 @@ protected: class PrintConfig : public MachineEnvelopeConfig, public GCodeConfig { STATIC_PRINT_CONFIG_CACHE_DERIVED(PrintConfig) - PrintConfig() : MachineEnvelopeConfig(0), GCodeConfig(0) { initialize_cache(); *this = s_cache_PrintConfig.defaults(); } + PrintConfig() : MachineEnvelopeConfig(0), GCodeConfig(0) { initialize_cache(); *this = s_cache_PrintConfig.defaults(); } public: double min_object_distance() const; static double min_object_distance(const ConfigBase *config); @@ -804,7 +812,7 @@ public: ConfigOptionFloat z_offset; protected: - PrintConfig(int) : MachineEnvelopeConfig(1), GCodeConfig(1) {} + PrintConfig(int) : MachineEnvelopeConfig(1), GCodeConfig(1) {} void initialize(StaticCacheBase &cache, const char *base_ptr) { this->MachineEnvelopeConfig::initialize(cache, base_ptr); @@ -984,6 +992,9 @@ public: // The height of the pillar base cone in mm. ConfigOptionFloat support_base_height /*= 1.0*/; + // The minimum distance of the pillar base from the model in mm. + ConfigOptionFloat support_base_safety_distance; /*= 1.0*/; + // The default angle for connecting support sticks and junctions. ConfigOptionFloat support_critical_angle /*= 45*/; @@ -1017,11 +1028,34 @@ public: ConfigOptionFloat pad_max_merge_distance /*= 50*/; // The smoothing radius of the pad edges - ConfigOptionFloat pad_edge_radius /*= 1*/; + // ConfigOptionFloat pad_edge_radius /*= 1*/; // The slope of the pad wall... ConfigOptionFloat pad_wall_slope; + // ///////////////////////////////////////////////////////////////////////// + // Zero elevation mode parameters: + // - The object pad will be derived from the the model geometry. + // - There will be a gap between the object pad and the generated pad + // according to the support_base_safety_distance parameter. + // - The two pads will be connected with tiny connector sticks + // ///////////////////////////////////////////////////////////////////////// + + // Disable the elevation (ignore its value) and use the zero elevation mode + ConfigOptionBool pad_zero_elevation; + + // This is the gap between the object bottom and the generated pad + ConfigOptionFloat pad_object_gap; + + // How far to place the connector sticks on the object pad perimeter + ConfigOptionFloat pad_object_connector_stride; + + // The width of the connectors sticks + ConfigOptionFloat pad_object_connector_width; + + // How much should the tiny connectors penetrate into the model body + ConfigOptionFloat pad_object_connector_penetration; + protected: void initialize(StaticCacheBase &cache, const char *base_ptr) { @@ -1038,6 +1072,7 @@ protected: OPT_PTR(support_pillar_widening_factor); OPT_PTR(support_base_diameter); OPT_PTR(support_base_height); + OPT_PTR(support_base_safety_distance); OPT_PTR(support_critical_angle); OPT_PTR(support_max_bridge_length); OPT_PTR(support_max_pillar_link_distance); @@ -1048,8 +1083,13 @@ protected: OPT_PTR(pad_wall_thickness); OPT_PTR(pad_wall_height); OPT_PTR(pad_max_merge_distance); - OPT_PTR(pad_edge_radius); + // OPT_PTR(pad_edge_radius); OPT_PTR(pad_wall_slope); + OPT_PTR(pad_zero_elevation); + OPT_PTR(pad_object_gap); + OPT_PTR(pad_object_connector_stride); + OPT_PTR(pad_object_connector_width); + OPT_PTR(pad_object_connector_penetration); } }; @@ -1083,12 +1123,18 @@ public: ConfigOptionInt display_pixels_x; ConfigOptionInt display_pixels_y; ConfigOptionEnum display_orientation; + ConfigOptionBool display_mirror_x; + ConfigOptionBool display_mirror_y; ConfigOptionFloats relative_correction; ConfigOptionFloat absolute_correction; ConfigOptionFloat gamma_correction; ConfigOptionFloat fast_tilt_time; ConfigOptionFloat slow_tilt_time; ConfigOptionFloat area_fill; + ConfigOptionFloat min_exposure_time; + ConfigOptionFloat max_exposure_time; + ConfigOptionFloat min_initial_exposure_time; + ConfigOptionFloat max_initial_exposure_time; protected: void initialize(StaticCacheBase &cache, const char *base_ptr) { @@ -1099,6 +1145,8 @@ protected: OPT_PTR(display_height); OPT_PTR(display_pixels_x); OPT_PTR(display_pixels_y); + OPT_PTR(display_mirror_x); + OPT_PTR(display_mirror_y); OPT_PTR(display_orientation); OPT_PTR(relative_correction); OPT_PTR(absolute_correction); @@ -1106,6 +1154,10 @@ protected: OPT_PTR(fast_tilt_time); OPT_PTR(slow_tilt_time); OPT_PTR(area_fill); + OPT_PTR(min_exposure_time); + OPT_PTR(max_exposure_time); + OPT_PTR(min_initial_exposure_time); + OPT_PTR(max_initial_exposure_time); } }; @@ -1165,8 +1217,8 @@ extern const CLIMiscConfigDef cli_misc_config_def; class DynamicPrintAndCLIConfig : public DynamicPrintConfig { public: - DynamicPrintAndCLIConfig() {} - DynamicPrintAndCLIConfig(const DynamicPrintAndCLIConfig &other) : DynamicPrintConfig(other) {} + DynamicPrintAndCLIConfig() {} + DynamicPrintAndCLIConfig(const DynamicPrintAndCLIConfig &other) : DynamicPrintConfig(other) {} // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. const ConfigDef* def() const override { return &s_def; } @@ -1186,6 +1238,8 @@ private: this->options.insert(cli_actions_config_def.options.begin(), cli_actions_config_def.options.end()); this->options.insert(cli_transform_config_def.options.begin(), cli_transform_config_def.options.end()); this->options.insert(cli_misc_config_def.options.begin(), cli_misc_config_def.options.end()); + for (const auto &kvp : this->options) + this->by_serialization_key_ordinal[kvp.second.serialization_key_ordinal] = &kvp.second; } // Do not release the default values, they are handled by print_config_def & cli_actions_config_def / cli_transform_config_def / cli_misc_config_def. ~PrintAndCLIConfigDef() { this->options.clear(); } @@ -1195,4 +1249,38 @@ private: } // namespace Slic3r +// Serialization through the Cereal library +namespace cereal { + // Let cereal know that there are load / save non-member functions declared for DynamicPrintConfig, ignore serialize / load / save from parent class DynamicConfig. + template struct specialize {}; + + template void load(Archive& archive, Slic3r::DynamicPrintConfig &config) + { + size_t cnt; + archive(cnt); + config.clear(); + for (size_t i = 0; i < cnt; ++ i) { + size_t serialization_key_ordinal; + archive(serialization_key_ordinal); + assert(serialization_key_ordinal > 0); + auto it = Slic3r::print_config_def.by_serialization_key_ordinal.find(serialization_key_ordinal); + assert(it != Slic3r::print_config_def.by_serialization_key_ordinal.end()); + config.set_key_value(it->second->opt_key, it->second->load_option_from_archive(archive)); + } + } + + template void save(Archive& archive, const Slic3r::DynamicPrintConfig &config) + { + size_t cnt = config.size(); + archive(cnt); + for (auto it = config.cbegin(); it != config.cend(); ++it) { + const Slic3r::ConfigOptionDef* optdef = Slic3r::print_config_def.get(it->first); + assert(optdef != nullptr); + assert(optdef->serialization_key_ordinal > 0); + archive(optdef->serialization_key_ordinal); + optdef->save_option_to_archive(archive, it->second.get()); + } + } +} + #endif diff --git a/src/libslic3r/PrintExport.hpp b/src/libslic3r/PrintExport.hpp deleted file mode 100644 index f6537ed324..0000000000 --- a/src/libslic3r/PrintExport.hpp +++ /dev/null @@ -1,327 +0,0 @@ -#ifndef PRINTEXPORT_HPP -#define PRINTEXPORT_HPP - -// For png export of the sliced model -#include -#include -#include - -#include -#include - -#include "Rasterizer/Rasterizer.hpp" -//#include -//#include //#include "tbb/mutex.h" - -namespace Slic3r { - -// Used for addressing parameters of FilePrinter::set_statistics() -enum ePrintStatistics -{ - psUsedMaterial = 0, - psNumFade, - psNumSlow, - psNumFast, - - psCnt -}; - -enum class FilePrinterFormat { - SLA_PNGZIP, - SVG -}; - -/* - * Interface for a file printer of the slices. Implementation can be an SVG - * or PNG printer or any other format. - * - * The format argument specifies the output format of the printer and it enables - * different implementations of this class template for each supported format. - * - */ -template -class FilePrinter { -public: - - // Draw a polygon which is a polygon inside a slice on the specified layer. - void draw_polygon(const ExPolygon& p, unsigned lyr); - void draw_polygon(const ClipperLib::Polygon& p, unsigned lyr); - - // Tell the printer how many layers should it consider. - void layers(unsigned layernum); - - // Get the number of layers in the print. - unsigned layers() const; - - /* Switch to a particular layer. If there where less layers then the - * specified layer number than an appropriate number of layers will be - * allocated in the printer. - */ - void begin_layer(unsigned layer); - - // Allocate a new layer on top of the last and switch to it. - void begin_layer(); - - /* - * Finish the selected layer. It means that no drawing is allowed on that - * layer anymore. This fact can be used to prepare the file system output - * data like png comprimation and so on. - */ - void finish_layer(unsigned layer); - - // Finish the top layer. - void finish_layer(); - - // Save all the layers into the file (or dir) specified in the path argument - // An optional project name can be added to be used for the layer file names - void save(const std::string& path, const std::string& projectname = ""); - - // Save only the selected layer to the file specified in path argument. - void save_layer(unsigned lyr, const std::string& path); -}; - -// Provokes static_assert in the right way. -template struct VeryFalse { static const bool value = false; }; - -// This can be explicitly implemented in the gui layer or the default Zipper -// API in libslic3r with minz. -template class LayerWriter { -public: - - LayerWriter(const std::string& /*zipfile_path*/) - { - static_assert(VeryFalse::value, - "No layer writer implementation provided!"); - } - - // Should create a new file within the zip with the given filename. It - // should also finish any previous entry. - void next_entry(const std::string& /*fname*/) {} - - // Should create a new file within the archive and write the provided data. - void binary_entry(const std::string& /*fname*/, - const std::uint8_t* buf, size_t len); - - // Test whether the object can still be used for writing. - bool is_ok() { return false; } - - // Write some data (text) into the current file (entry) within the archive. - template LayerWriter& operator<<(T&& /*arg*/) { - return *this; - } - - // Flush the current entry into the archive. - void finalize() {} -}; - -// Implementation for PNG raster output -// Be aware that if a large number of layers are allocated, it can very well -// exhaust the available memory especially on 32 bit platform. -template<> class FilePrinter -{ - struct Layer { - Raster raster; - RawBytes rawbytes; - - Layer() {} - - Layer(const Layer&) = delete; - Layer(Layer&& m): - raster(std::move(m.raster)) {} - }; - - // We will save the compressed PNG data into stringstreams which can be done - // in parallel. Later we can write every layer to the disk sequentially. - std::vector m_layers_rst; - Raster::Resolution m_res; - Raster::PixelDim m_pxdim; - double m_exp_time_s = .0, m_exp_time_first_s = .0; - double m_layer_height = .0; - Raster::Origin m_o = Raster::Origin::TOP_LEFT; - double m_gamma; - - double m_used_material = 0.0; - int m_cnt_fade_layers = 0; - int m_cnt_slow_layers = 0; - int m_cnt_fast_layers = 0; - - std::string createIniContent(const std::string& projectname) { - using std::string; - using std::to_string; - - auto expt_str = to_string(m_exp_time_s); - auto expt_first_str = to_string(m_exp_time_first_s); - auto layerh_str = to_string(m_layer_height); - - const std::string cnt_fade_layers = to_string(m_cnt_fade_layers); - const std::string cnt_slow_layers = to_string(m_cnt_slow_layers); - const std::string cnt_fast_layers = to_string(m_cnt_fast_layers); - const std::string used_material = to_string(m_used_material); - - return string( - "action = print\n" - "jobDir = ") + projectname + "\n" + - "expTime = " + expt_str + "\n" - "expTimeFirst = " + expt_first_str + "\n" - "numFade = " + cnt_fade_layers + "\n" - "layerHeight = " + layerh_str + "\n" - "usedMaterial = " + used_material + "\n" - "numSlow = " + cnt_slow_layers + "\n" - "numFast = " + cnt_fast_layers + "\n"; - } - -public: - - enum RasterOrientation { - RO_LANDSCAPE, - RO_PORTRAIT - }; - - // We will play with the raster's coordinate origin parameter. When the - // printer should print in landscape mode it should have the Y axis flipped - // because the layers should be displayed upside down. PNG has its - // coordinate origin in the top-left corner so normally the Raster objects - // should be instantiated with the TOP_LEFT flag. However, in landscape mode - // we do want the pictures to be upside down so we will make BOTTOM_LEFT - // type rasters and the PNG format will do the flipping automatically. - - // In case of portrait images, we have to rotate the image by a 90 degrees - // and flip the y axis. To get the correct upside-down orientation of the - // slice images, we can flip the x and y coordinates of the input polygons - // and do the Y flipping of the image. This will generate the correct - // orientation in portrait mode. - - inline FilePrinter(double width_mm, double height_mm, - unsigned width_px, unsigned height_px, - double layer_height, - double exp_time, double exp_time_first, - RasterOrientation ro = RO_PORTRAIT, - double gamma = 1.0): - m_res(width_px, height_px), - m_pxdim(width_mm/width_px, height_mm/height_px), - m_exp_time_s(exp_time), - m_exp_time_first_s(exp_time_first), - m_layer_height(layer_height), - - // Here is the trick with the orientation. - m_o(ro == RO_LANDSCAPE? Raster::Origin::BOTTOM_LEFT : - Raster::Origin::TOP_LEFT ), - m_gamma(gamma) - { - } - - FilePrinter(const FilePrinter& ) = delete; - FilePrinter(FilePrinter&& m): - m_layers_rst(std::move(m.m_layers_rst)), - m_res(m.m_res), - m_pxdim(m.m_pxdim) {} - - inline void layers(unsigned cnt) { if(cnt > 0) m_layers_rst.resize(cnt); } - inline unsigned layers() const { return unsigned(m_layers_rst.size()); } - - inline void draw_polygon(const ExPolygon& p, unsigned lyr) { - assert(lyr < m_layers_rst.size()); - m_layers_rst[lyr].raster.draw(p); - } - - inline void draw_polygon(const ClipperLib::Polygon& p, unsigned lyr) { - assert(lyr < m_layers_rst.size()); - m_layers_rst[lyr].raster.draw(p); - } - - inline void begin_layer(unsigned lyr) { - if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1); - m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_o, m_gamma); - } - - inline void begin_layer() { - m_layers_rst.emplace_back(); - m_layers_rst.front().raster.reset(m_res, m_pxdim, m_o, m_gamma); - } - - inline void finish_layer(unsigned lyr_id) { - assert(lyr_id < m_layers_rst.size()); - m_layers_rst[lyr_id].rawbytes = - m_layers_rst[lyr_id].raster.save(Raster::Compression::PNG); - m_layers_rst[lyr_id].raster.reset(); - } - - inline void finish_layer() { - if(!m_layers_rst.empty()) { - m_layers_rst.back().rawbytes = - m_layers_rst.back().raster.save(Raster::Compression::PNG); - m_layers_rst.back().raster.reset(); - } - } - - template - inline void save(const std::string& fpath, const std::string& prjname = "") - { - try { - LayerWriter writer(fpath); - if(!writer.is_ok()) return; - - std::string project = prjname.empty()? - boost::filesystem::path(fpath).stem().string() : prjname; - - writer.next_entry("config.ini"); - if(!writer.is_ok()) return; - - writer << createIniContent(project); - - for(unsigned i = 0; i < m_layers_rst.size() && writer.is_ok(); i++) - { - if(m_layers_rst[i].rawbytes.size() > 0) { - char lyrnum[6]; - std::sprintf(lyrnum, "%.5d", i); - auto zfilename = project + lyrnum + ".png"; - if(!writer.is_ok()) break; - - writer.binary_entry(zfilename, - m_layers_rst[i].rawbytes.data(), - m_layers_rst[i].rawbytes.size()); - } - } - - writer.finalize(); - } catch(std::exception& e) { - BOOST_LOG_TRIVIAL(error) << e.what(); - // Rethrow the exception - throw; - } - } - - void save_layer(unsigned lyr, const std::string& path) { - unsigned i = lyr; - assert(i < m_layers_rst.size()); - - char lyrnum[6]; - std::sprintf(lyrnum, "%.5d", lyr); - std::string loc = path + "layer" + lyrnum + ".png"; - - std::fstream out(loc, std::fstream::out | std::fstream::binary); - if(out.good()) { - m_layers_rst[i].raster.save(out, Raster::Compression::PNG); - } else { - BOOST_LOG_TRIVIAL(error) << "Can't create file for layer"; - } - - out.close(); - m_layers_rst[i].raster.reset(); - } - - void set_statistics(const std::vector statistics) - { - if (statistics.size() != psCnt) - return; - - m_used_material = statistics[psUsedMaterial]; - m_cnt_fade_layers = int(statistics[psNumFade]); - m_cnt_slow_layers = int(statistics[psNumSlow]); - m_cnt_fast_layers = int(statistics[psNumFast]); - } -}; - -} - -#endif // PRINTEXPORT_HPP diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 660a2d9398..e19bceeb76 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -18,7 +18,7 @@ #include -//! macro used to mark string used at localization, +//! macro used to mark string used at localization, //! return same string #define L(s) Slic3r::I18N::translate(s) @@ -49,7 +49,7 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, bool add_insta { // Translate meshes so that our toolpath generation algorithms work with smaller // XY coordinates; this translation is an optimization and not strictly required. - // A cloned mesh will be aligned to 0 before slicing in _slice_region() since we + // A cloned mesh will be aligned to 0 before slicing in slice_region() since we // don't assume it's already aligned and we don't alter the original position in model. // We store the XY translation so that we can place copies correctly in the output G-code // (copies are expressed in G-code coordinates and this translation is not publicly exposed). @@ -161,7 +161,7 @@ void PrintObject::make_perimeters() const PrintRegion ®ion = *m_print->regions()[region_id]; if (! region.config().extra_perimeters || region.config().perimeters == 0 || region.config().fill_density == 0 || this->layer_count() < 2) continue; - + BOOST_LOG_TRIVIAL(debug) << "Generating extra perimeters for region " << region_id << " in parallel - start"; tbb::parallel_for( tbb::blocked_range(0, m_layers.size() - 1), @@ -430,7 +430,7 @@ SupportLayer* PrintObject::add_support_layer(int id, coordf_t height, coordf_t p return m_support_layers.back(); } -SupportLayerPtrs::const_iterator PrintObject::insert_support_layer(SupportLayerPtrs::const_iterator pos, int id, coordf_t height, coordf_t print_z, coordf_t slice_z) +SupportLayerPtrs::const_iterator PrintObject::insert_support_layer(SupportLayerPtrs::const_iterator pos, size_t id, coordf_t height, coordf_t print_z, coordf_t slice_z) { return m_support_layers.insert(pos, new SupportLayer(id, this, height, print_z, slice_z)); } @@ -590,7 +590,12 @@ bool PrintObject::invalidate_step(PrintObjectStep step) bool PrintObject::invalidate_all_steps() { - return Inherited::invalidate_all_steps() | m_print->invalidate_all_steps(); + // First call the "invalidate" functions, which may cancel background processing. + bool result = Inherited::invalidate_all_steps() | m_print->invalidate_all_steps(); + // Then reset some of the depending values. + this->m_slicing_params.valid = false; + this->region_volumes.clear(); + return result; } bool PrintObject::has_support_material() const @@ -620,7 +625,7 @@ void PrintObject::detect_surfaces_type() // should be visible. bool interface_shells = m_config.interface_shells.value; - for (int idx_region = 0; idx_region < this->region_volumes.size(); ++ idx_region) { + for (size_t idx_region = 0; idx_region < this->region_volumes.size(); ++ idx_region) { BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << idx_region << " in parallel - start"; #ifdef SLIC3R_DEBUG_SLICE_PROCESSING for (Layer *layer : m_layers) @@ -806,8 +811,6 @@ void PrintObject::process_external_surfaces() BOOST_LOG_TRIVIAL(info) << "Processing external surfaces..." << log_memory_info(); for (size_t region_id = 0; region_id < this->region_volumes.size(); ++region_id) { - const PrintRegion ®ion = *m_print->regions()[region_id]; - BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - start"; tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), @@ -1028,7 +1031,6 @@ void PrintObject::discover_vertical_shells() bool hole_first = true; for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n) if (n >= 0 && n < (int)m_layers.size()) { - Layer &neighbor_layer = *m_layers[n]; const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[n]; if (hole_first) { hole_first = false; @@ -1354,10 +1356,12 @@ PrintObjectConfig PrintObject::object_config_from_model_object(const PrintObject return config; } -PrintRegionConfig PrintObject::region_config_from_model_volume(const PrintRegionConfig &default_region_config, const ModelVolume &volume, size_t num_extruders) +PrintRegionConfig PrintObject::region_config_from_model_volume(const PrintRegionConfig &default_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders) { PrintRegionConfig config = default_region_config; normalize_and_apply_config(config, volume.get_object()->config); + if (layer_range_config != nullptr) + normalize_and_apply_config(config, *layer_range_config); normalize_and_apply_config(config, volume.config); if (! volume.material_id().empty()) normalize_and_apply_config(config, volume.material()->config); @@ -1375,28 +1379,37 @@ void PrintObject::update_slicing_parameters() this->print()->config(), m_config, unscale(this->size(2)), this->object_extruders()); } -SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object, float object_max_z) +SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig& full_config, const ModelObject& model_object, float object_max_z) { - PrintConfig print_config; - PrintObjectConfig object_config; - PrintRegionConfig default_region_config; - print_config .apply(full_config, true); - object_config.apply(full_config, true); - default_region_config.apply(full_config, true); - size_t num_extruders = print_config.nozzle_diameter.size(); - object_config = object_config_from_model_object(object_config, model_object, num_extruders); + PrintConfig print_config; + PrintObjectConfig object_config; + PrintRegionConfig default_region_config; + print_config.apply(full_config, true); + object_config.apply(full_config, true); + default_region_config.apply(full_config, true); + size_t num_extruders = print_config.nozzle_diameter.size(); + object_config = object_config_from_model_object(object_config, model_object, num_extruders); - std::vector object_extruders; - for (const ModelVolume *model_volume : model_object.volumes) - if (model_volume->is_model_part()) - PrintRegion::collect_object_printing_extruders( - print_config, - region_config_from_model_volume(default_region_config, *model_volume, num_extruders), - object_extruders); + std::vector object_extruders; + for (const ModelVolume* model_volume : model_object.volumes) + if (model_volume->is_model_part()) { + PrintRegion::collect_object_printing_extruders( + print_config, + region_config_from_model_volume(default_region_config, nullptr, *model_volume, num_extruders), + object_extruders); + for (const std::pair &range_and_config : model_object.layer_config_ranges) + if (range_and_config.second.has("perimeter_extruder") || + range_and_config.second.has("infill_extruder") || + range_and_config.second.has("solid_infill_extruder")) + PrintRegion::collect_object_printing_extruders( + print_config, + region_config_from_model_volume(default_region_config, &range_and_config.second, *model_volume, num_extruders), + object_extruders); + } sort_remove_duplicates(object_extruders); if (object_max_z <= 0.f) - object_max_z = model_object.raw_bounding_box().size().z(); + object_max_z = (float)model_object.raw_bounding_box().size().z(); return SlicingParameters::create_from_config(print_config, object_config, object_max_z, object_extruders); } @@ -1430,12 +1443,12 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c layer_height_profile.clear(); if (layer_height_profile.empty()) { - if (0) + if (0) // if (this->layer_height_profile.empty()) - layer_height_profile = layer_height_profile_adaptive(slicing_parameters, model_object.layer_height_ranges, model_object.volumes); + layer_height_profile = layer_height_profile_adaptive(slicing_parameters, model_object.layer_config_ranges, model_object.volumes); else - layer_height_profile = layer_height_profile_from_ranges(slicing_parameters, model_object.layer_height_ranges); - updated = true; + layer_height_profile = layer_height_profile_from_ranges(slicing_parameters, model_object.layer_config_ranges); // #ys_FIXME_experiment + updated = true; } return updated; } @@ -1489,22 +1502,28 @@ void PrintObject::_slice(const std::vector &layer_height_profile) } // Count model parts and modifier meshes, check whether the model parts are of the same region. - int single_volume_region = -2; // not set yet + int all_volumes_single_region = -2; // not set yet + bool has_z_ranges = false; size_t num_volumes = 0; size_t num_modifiers = 0; - std::vector map_volume_to_region(this->model_object()->volumes.size()); for (int region_id = 0; region_id < (int)this->region_volumes.size(); ++ region_id) { - for (int volume_id : this->region_volumes[region_id]) { + int last_volume_id = -1; + for (const std::pair &volume_and_range : this->region_volumes[region_id]) { + const int volume_id = volume_and_range.second; const ModelVolume *model_volume = this->model_object()->volumes[volume_id]; if (model_volume->is_model_part()) { - map_volume_to_region[volume_id] = region_id; - if (single_volume_region == -2) - // first model volume met - single_volume_region = region_id; - else if (single_volume_region != region_id) - // multiple volumes met and they are not equal - single_volume_region = -1; - ++ num_volumes; + if (last_volume_id == volume_id) { + has_z_ranges = true; + } else { + last_volume_id = volume_id; + if (all_volumes_single_region == -2) + // first model volume met + all_volumes_single_region = region_id; + else if (all_volumes_single_region != region_id) + // multiple volumes met and they are not equal + all_volumes_single_region = -1; + ++ num_volumes; + } } else if (model_volume->is_modifier()) ++ num_modifiers; } @@ -1514,13 +1533,13 @@ void PrintObject::_slice(const std::vector &layer_height_profile) // Slice all non-modifier volumes. bool clipped = false; bool upscaled = false; - if (! m_config.clip_multipart_objects.value || single_volume_region >= 0) { + if (! has_z_ranges && (! m_config.clip_multipart_objects.value || all_volumes_single_region >= 0)) { // Cheap path: Slice regions without mutual clipping. // The cheap path is possible if no clipping is allowed or if slicing volumes of just a single region. for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { BOOST_LOG_TRIVIAL(debug) << "Slicing objects - region " << region_id; // slicing in parallel - std::vector expolygons_by_layer = this->_slice_region(region_id, slice_zs, false); + std::vector expolygons_by_layer = this->slice_region(region_id, slice_zs); m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Slicing objects - append slices " << region_id << " start"; for (size_t layer_id = 0; layer_id < expolygons_by_layer.size(); ++ layer_id) @@ -1541,15 +1560,29 @@ void PrintObject::_slice(const std::vector &layer_height_profile) }; std::vector sliced_volumes; sliced_volumes.reserve(num_volumes); - for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) - for (int volume_id : this->region_volumes[region_id]) { + for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { + const std::vector> &volumes_and_ranges = this->region_volumes[region_id]; + for (size_t i = 0; i < volumes_and_ranges.size(); ) { + int volume_id = volumes_and_ranges[i].second; const ModelVolume *model_volume = this->model_object()->volumes[volume_id]; if (model_volume->is_model_part()) { BOOST_LOG_TRIVIAL(debug) << "Slicing objects - volume " << volume_id; + // Find the ranges of this volume. Ranges in volumes_and_ranges must not overlap for a single volume. + std::vector ranges; + ranges.emplace_back(volumes_and_ranges[i].first); + size_t j = i + 1; + for (; j < volumes_and_ranges.size() && volume_id == volumes_and_ranges[j].second; ++ j) + if (! ranges.empty() && std::abs(ranges.back().second - volumes_and_ranges[j].first.first) < EPSILON) + ranges.back().second = volumes_and_ranges[j].first.second; + else + ranges.emplace_back(volumes_and_ranges[j].first); // slicing in parallel - sliced_volumes.emplace_back(volume_id, map_volume_to_region[volume_id], this->_slice_volume(slice_zs, *model_volume)); - } + sliced_volumes.emplace_back(volume_id, (int)region_id, this->slice_volume(slice_zs, ranges, *model_volume)); + i = j; + } else + ++ i; } + } // Second clip the volumes in the order they are presented at the user interface. BOOST_LOG_TRIVIAL(debug) << "Slicing objects - parallel clipping - start"; tbb::parallel_for( @@ -1603,7 +1636,7 @@ void PrintObject::_slice(const std::vector &layer_height_profile) for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { BOOST_LOG_TRIVIAL(debug) << "Slicing modifier volumes - region " << region_id; // slicing in parallel - std::vector expolygons_by_layer = this->_slice_region(region_id, slice_zs, true); + std::vector expolygons_by_layer = this->slice_modifiers(region_id, slice_zs); m_print->throw_if_canceled(); if (expolygons_by_layer.empty()) continue; @@ -1619,7 +1652,7 @@ void PrintObject::_slice(const std::vector &layer_height_profile) Layer *layer = m_layers[layer_id]; LayerRegion *layerm = layer->m_regions[region_id]; LayerRegion *other_layerm = layer->m_regions[other_region_id]; - if (layerm == nullptr || other_layerm == nullptr) + if (layerm == nullptr || other_layerm == nullptr || other_layerm->slices.empty() || expolygons_by_layer[layer_id].empty()) continue; Polygons other_slices = to_polygons(other_layerm->slices); ExPolygons my_parts = intersection_ex(other_slices, to_polygons(expolygons_by_layer[layer_id])); @@ -1752,52 +1785,133 @@ end: BOOST_LOG_TRIVIAL(debug) << "Slicing objects - make_slices in parallel - end"; } -std::vector PrintObject::_slice_region(size_t region_id, const std::vector &z, bool modifier) +// To be used only if there are no layer span specific configurations applied, which would lead to z ranges being generated for this region. +std::vector PrintObject::slice_region(size_t region_id, const std::vector &z) const { - std::vector volumes; + std::vector volumes; if (region_id < this->region_volumes.size()) { - for (int volume_id : this->region_volumes[region_id]) { - const ModelVolume *volume = this->model_object()->volumes[volume_id]; - if (modifier ? volume->is_modifier() : volume->is_model_part()) - volumes.emplace_back(volume); - } + for (const std::pair &volume_and_range : this->region_volumes[region_id]) { + const ModelVolume *volume = this->model_object()->volumes[volume_and_range.second]; + if (volume->is_model_part()) + volumes.emplace_back(volume); + } } - return this->_slice_volumes(z, volumes); + return this->slice_volumes(z, volumes); } -std::vector PrintObject::slice_support_enforcers() const +// Z ranges are not applicable to modifier meshes, therefore a sinle volume will be found in volume_and_range at most once. +std::vector PrintObject::slice_modifiers(size_t region_id, const std::vector &slice_zs) const +{ + std::vector out; + if (region_id < this->region_volumes.size()) + { + std::vector> volume_ranges; + const std::vector> &volumes_and_ranges = this->region_volumes[region_id]; + volume_ranges.reserve(volumes_and_ranges.size()); + for (size_t i = 0; i < volumes_and_ranges.size(); ) { + int volume_id = volumes_and_ranges[i].second; + const ModelVolume *model_volume = this->model_object()->volumes[volume_id]; + if (model_volume->is_modifier()) { + std::vector ranges; + ranges.emplace_back(volumes_and_ranges[i].first); + size_t j = i + 1; + for (; j < volumes_and_ranges.size() && volume_id == volumes_and_ranges[j].second; ++ j) { + if (! ranges.empty() && std::abs(ranges.back().second - volumes_and_ranges[j].first.first) < EPSILON) + ranges.back().second = volumes_and_ranges[j].first.second; + else + ranges.emplace_back(volumes_and_ranges[j].first); + } + volume_ranges.emplace_back(std::move(ranges)); + i = j; + } else + ++ i; + } + + if (! volume_ranges.empty()) + { + bool equal_ranges = true; + for (size_t i = 1; i < volume_ranges.size(); ++ i) { + assert(! volume_ranges[i].empty()); + if (volume_ranges.front() != volume_ranges[i]) { + equal_ranges = false; + break; + } + } + + if (equal_ranges && volume_ranges.front().size() == 1 && volume_ranges.front().front() == t_layer_height_range(0, DBL_MAX)) { + // No modifier in this region was split to layer spans. + std::vector volumes; + for (const std::pair &volume_and_range : this->region_volumes[region_id]) { + const ModelVolume *volume = this->model_object()->volumes[volume_and_range.second]; + if (volume->is_modifier()) + volumes.emplace_back(volume); + } + out = this->slice_volumes(slice_zs, volumes); + } else { + // Some modifier in this region was split to layer spans. + std::vector merge; + for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { + const std::vector> &volumes_and_ranges = this->region_volumes[region_id]; + for (size_t i = 0; i < volumes_and_ranges.size(); ) { + int volume_id = volumes_and_ranges[i].second; + const ModelVolume *model_volume = this->model_object()->volumes[volume_id]; + if (model_volume->is_modifier()) { + BOOST_LOG_TRIVIAL(debug) << "Slicing modifiers - volume " << volume_id; + // Find the ranges of this volume. Ranges in volumes_and_ranges must not overlap for a single volume. + std::vector ranges; + ranges.emplace_back(volumes_and_ranges[i].first); + size_t j = i + 1; + for (; j < volumes_and_ranges.size() && volume_id == volumes_and_ranges[j].second; ++ j) + ranges.emplace_back(volumes_and_ranges[j].first); + // slicing in parallel + std::vector this_slices = this->slice_volume(slice_zs, ranges, *model_volume); + if (out.empty()) { + out = std::move(this_slices); + merge.assign(out.size(), false); + } else { + for (size_t i = 0; i < out.size(); ++ i) + if (! this_slices[i].empty()) + if (! out[i].empty()) { + append(out[i], this_slices[i]); + merge[i] = true; + } else + out[i] = std::move(this_slices[i]); + } + i = j; + } else + ++ i; + } + } + for (size_t i = 0; i < merge.size(); ++ i) + if (merge[i]) + out[i] = union_ex(out[i]); + } + } + } + + return out; +} + +std::vector PrintObject::slice_support_volumes(const ModelVolumeType &model_volume_type) const { std::vector volumes; for (const ModelVolume *volume : this->model_object()->volumes) - if (volume->is_support_enforcer()) + if (volume->type() == model_volume_type) volumes.emplace_back(volume); std::vector zs; zs.reserve(this->layers().size()); for (const Layer *l : this->layers()) zs.emplace_back((float)l->slice_z); - return this->_slice_volumes(zs, volumes); + return this->slice_volumes(zs, volumes); } -std::vector PrintObject::slice_support_blockers() const -{ - std::vector volumes; - for (const ModelVolume *volume : this->model_object()->volumes) - if (volume->is_support_blocker()) - volumes.emplace_back(volume); - std::vector zs; - zs.reserve(this->layers().size()); - for (const Layer *l : this->layers()) - zs.emplace_back((float)l->slice_z); - return this->_slice_volumes(zs, volumes); -} - -std::vector PrintObject::_slice_volumes(const std::vector &z, const std::vector &volumes) const +std::vector PrintObject::slice_volumes(const std::vector &z, const std::vector &volumes) const { std::vector layers; if (! volumes.empty()) { // Compose mesh. //FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them. - TriangleMesh mesh(volumes.front()->mesh); + TriangleMesh mesh(volumes.front()->mesh()); mesh.transform(volumes.front()->get_matrix(), true); assert(mesh.repaired); if (volumes.size() == 1 && mesh.repaired) { @@ -1806,7 +1920,7 @@ std::vector PrintObject::_slice_volumes(const std::vector &z, } for (size_t idx_volume = 1; idx_volume < volumes.size(); ++ idx_volume) { const ModelVolume &model_volume = *volumes[idx_volume]; - TriangleMesh vol_mesh(model_volume.mesh); + TriangleMesh vol_mesh(model_volume.mesh()); vol_mesh.transform(model_volume.get_matrix(), true); mesh.merge(vol_mesh); } @@ -1815,10 +1929,11 @@ std::vector PrintObject::_slice_volumes(const std::vector &z, // apply XY shift mesh.translate(- unscale(m_copies_shift(0)), - unscale(m_copies_shift(1)), 0); // perform actual slicing - TriangleMeshSlicer mslicer; const Print *print = this->print(); auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); - mesh.require_shared_vertices(); // TriangleMeshSlicer needs this + // TriangleMeshSlicer needs shared vertices, also this calls the repair() function. + mesh.require_shared_vertices(); + TriangleMeshSlicer mslicer; mslicer.init(&mesh, callback); mslicer.slice(z, float(m_config.slice_closing_radius.value), &layers, callback); m_print->throw_if_canceled(); @@ -1827,33 +1942,71 @@ std::vector PrintObject::_slice_volumes(const std::vector &z, return layers; } -std::vector PrintObject::_slice_volume(const std::vector &z, const ModelVolume &volume) const +std::vector PrintObject::slice_volume(const std::vector &z, const ModelVolume &volume) const { std::vector layers; - // Compose mesh. - //FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them. - TriangleMesh mesh(volume.mesh); - mesh.transform(volume.get_matrix(), true); - if (mesh.repaired) { - //FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it. - stl_check_facets_exact(&mesh.stl); + if (! z.empty()) { + // Compose mesh. + //FIXME better to split the mesh into separate shells, perform slicing over each shell separately and then to use a Boolean operation to merge them. + TriangleMesh mesh(volume.mesh()); + mesh.transform(volume.get_matrix(), true); + if (mesh.repaired) { + //FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it. + stl_check_facets_exact(&mesh.stl); + } + if (mesh.stl.stats.number_of_facets > 0) { + mesh.transform(m_trafo, true); + // apply XY shift + mesh.translate(- unscale(m_copies_shift(0)), - unscale(m_copies_shift(1)), 0); + // perform actual slicing + TriangleMeshSlicer mslicer; + const Print *print = this->print(); + auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); + // TriangleMeshSlicer needs the shared vertices. + mesh.require_shared_vertices(); + mslicer.init(&mesh, callback); + mslicer.slice(z, float(m_config.slice_closing_radius.value), &layers, callback); + m_print->throw_if_canceled(); + } } - if (mesh.stl.stats.number_of_facets > 0) { - mesh.transform(m_trafo, true); - // apply XY shift - mesh.translate(- unscale(m_copies_shift(0)), - unscale(m_copies_shift(1)), 0); - // perform actual slicing - TriangleMeshSlicer mslicer; - const Print *print = this->print(); - auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); - mesh.require_shared_vertices(); // TriangleMeshSlicer needs this - mslicer.init(&mesh, callback); - mslicer.slice(z, float(m_config.slice_closing_radius.value), &layers, callback); - m_print->throw_if_canceled(); - } return layers; } +// Filter the zs not inside the ranges. The ranges are closed at the botton and open at the top, they are sorted lexicographically and non overlapping. +std::vector PrintObject::slice_volume(const std::vector &z, const std::vector &ranges, const ModelVolume &volume) const +{ + std::vector out; + if (! z.empty() && ! ranges.empty()) { + if (ranges.size() == 1 && z.front() >= ranges.front().first && z.back() < ranges.front().second) { + // All layers fit into a single range. + out = this->slice_volume(z, volume); + } else { + std::vector z_filtered; + std::vector> n_filtered; + z_filtered.reserve(z.size()); + n_filtered.reserve(2 * ranges.size()); + size_t i = 0; + for (const t_layer_height_range &range : ranges) { + for (; i < z.size() && z[i] < range.first; ++ i) ; + size_t first = i; + for (; i < z.size() && z[i] < range.second; ++ i) + z_filtered.emplace_back(z[i]); + if (i > first) + n_filtered.emplace_back(std::make_pair(first, i)); + } + if (! n_filtered.empty()) { + std::vector layers = this->slice_volume(z_filtered, volume); + out.assign(z.size(), ExPolygons()); + i = 0; + for (const std::pair &span : n_filtered) + for (size_t j = span.first; j < span.second; ++ j) + out[j] = std::move(layers[i ++]); + } + } + } + return out; +} + std::string PrintObject::_fix_slicing_errors() { // Collect layers with slicing errors. @@ -2117,7 +2270,7 @@ void PrintObject::clip_fill_surfaces() //Should the pw not be half of the current value? float pw = FLT_MAX; for (const LayerRegion *layerm : layer->m_regions) - pw = std::min(pw, layerm->flow(frPerimeter).scaled_width()); + pw = std::min(pw, (float)layerm->flow(frPerimeter).scaled_width()); // Append such thick perimeters to the areas that need support polygons_append(overhangs, offset2(perimeters, -pw, +pw)); } @@ -2152,7 +2305,7 @@ void PrintObject::discover_horizontal_shells() BOOST_LOG_TRIVIAL(trace) << "discover_horizontal_shells()"; for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { - for (int i = 0; i < int(m_layers.size()); ++ i) { + for (size_t i = 0; i < m_layers.size(); ++ i) { m_print->throw_if_canceled(); LayerRegion *layerm = m_layers[i]->regions()[region_id]; const PrintRegionConfig ®ion_config = layerm->region()->config(); @@ -2169,7 +2322,7 @@ void PrintObject::discover_horizontal_shells() if (region_config.ensure_vertical_shell_thickness.value) continue; - for (int idx_surface_type = 0; idx_surface_type < 3; ++ idx_surface_type) { + for (size_t idx_surface_type = 0; idx_surface_type < 3; ++ idx_surface_type) { m_print->throw_if_canceled(); SurfaceType type = (idx_surface_type == 0) ? stTop : (idx_surface_type == 1) ? stBottom : stBottomBridge; // Find slices of current type for current layer. @@ -2198,7 +2351,7 @@ void PrintObject::discover_horizontal_shells() // Slic3r::debugf "Layer %d has %s surfaces\n", $i, ($type == S_TYPE_TOP) ? 'top' : 'bottom'; size_t solid_layers = (type == stTop) ? region_config.top_solid_layers.value : region_config.bottom_solid_layers.value; - for (int n = (type == stTop) ? i-1 : i+1; std::abs(n - i) < solid_layers; (type == stTop) ? -- n : ++ n) { + for (int n = (type == stTop) ? i-1 : i+1; std::abs(n - (int)i) < solid_layers; (type == stTop) ? -- n : ++ n) { if (n < 0 || n >= int(m_layers.size())) continue; // Slic3r::debugf " looking for neighbors on layer %d...\n", $n; @@ -2226,7 +2379,7 @@ void PrintObject::discover_horizontal_shells() if (new_internal_solid.empty()) { // No internal solid needed on this layer. In order to decide whether to continue // searching on the next neighbor (thus enforcing the configured number of solid - // layers, use different strategies according to configured infill density: + // layers, use different strategies according to configured infill density: if (region_config.fill_density.value == 0) { // If user expects the object to be void (for example a hollow sloping vase), // don't continue the search. In this case, we only generate the external solid @@ -2343,7 +2496,7 @@ void PrintObject::combine_infill() // Work on each region separately. for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { const PrintRegion *region = this->print()->regions()[region_id]; - const int every = region->config().infill_every_layers.value; + const size_t every = region->config().infill_every_layers.value; if (every < 2 || region->config().fill_density == 0.) continue; // Limit the number of combined layers to the maximum height allowed by this regions' nozzle. diff --git a/src/libslic3r/PrintRegion.cpp b/src/libslic3r/PrintRegion.cpp index 73b40487bc..fc2bdfa7d1 100644 --- a/src/libslic3r/PrintRegion.cpp +++ b/src/libslic3r/PrintRegion.cpp @@ -46,7 +46,7 @@ Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool fir } double nozzle_diameter = m_print->config().nozzle_diameter.get_at(extruder-1); - return Flow::new_from_config_width(role, config_width, nozzle_diameter, layer_height, bridge ? (float)m_config.bridge_flow_ratio : 0.0); + return Flow::new_from_config_width(role, config_width, (float)nozzle_diameter, (float)layer_height, bridge ? (float)m_config.bridge_flow_ratio : 0.0f); } coordf_t PrintRegion::nozzle_dmr_avg(const PrintConfig &print_config) const @@ -64,16 +64,27 @@ coordf_t PrintRegion::bridging_height_avg(const PrintConfig &print_config) const void PrintRegion::collect_object_printing_extruders(const PrintConfig &print_config, const PrintRegionConfig ®ion_config, std::vector &object_extruders) { // These checks reflect the same logic used in the GUI for enabling/disabling extruder selection fields. + auto num_extruders = (int)print_config.nozzle_diameter.size(); + auto emplace_extruder = [num_extruders, &object_extruders](int extruder_id) { + int i = std::max(0, extruder_id - 1); + object_extruders.emplace_back((i >= num_extruders) ? 0 : i); + }; if (region_config.perimeters.value > 0 || print_config.brim_width.value > 0) - object_extruders.emplace_back(region_config.perimeter_extruder - 1); + emplace_extruder(region_config.perimeter_extruder); if (region_config.fill_density.value > 0) - object_extruders.emplace_back(region_config.infill_extruder - 1); + emplace_extruder(region_config.infill_extruder); if (region_config.top_solid_layers.value > 0 || region_config.bottom_solid_layers.value > 0) - object_extruders.emplace_back(region_config.solid_infill_extruder - 1); + emplace_extruder(region_config.solid_infill_extruder); } void PrintRegion::collect_object_printing_extruders(std::vector &object_extruders) const { + auto num_extruders = (int)print()->config().nozzle_diameter.size(); + // PrintRegion, if used by some PrintObject, shall have all the extruders set to an existing printer extruder. + // If not, then there must be something wrong with the Print::apply() function. + assert(this->config().perimeter_extruder <= num_extruders); + assert(this->config().infill_extruder <= num_extruders); + assert(this->config().solid_infill_extruder <= num_extruders); collect_object_printing_extruders(print()->config(), this->config(), object_extruders); } diff --git a/src/libslic3r/SLA/SLAAutoSupports.cpp b/src/libslic3r/SLA/SLAAutoSupports.cpp index 0a2537b919..36378df395 100644 --- a/src/libslic3r/SLA/SLAAutoSupports.cpp +++ b/src/libslic3r/SLA/SLAAutoSupports.cpp @@ -1,5 +1,5 @@ -#include "igl/random_points_on_mesh.h" -#include "igl/AABB.h" +//#include "igl/random_points_on_mesh.h" +//#include "igl/AABB.h" #include @@ -59,8 +59,6 @@ SLAAutoSupports::SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3 void SLAAutoSupports::project_onto_mesh(std::vector& points) const { // The function makes sure that all the points are really exactly placed on the mesh. - igl::Hit hit_up{0, 0, 0.f, 0.f, 0.f}; - igl::Hit hit_down{0, 0, 0.f, 0.f, 0.f}; // Use a reasonable granularity to account for the worker thread synchronization cost. tbb::parallel_for(tbb::blocked_range(0, points.size(), 64), @@ -103,7 +101,7 @@ static std::vector make_layers( std::vector layers; layers.reserve(slices.size()); for (size_t i = 0; i < slices.size(); ++ i) - layers.emplace_back(i, heights[i]); + layers.emplace_back(i, heights[i]); // FIXME: calculate actual pixel area from printer config: //const float pixel_area = pow(wxGetApp().preset_bundle->project_config.option("display_width") / wxGetApp().preset_bundle->project_config.option("display_pixels_x"), 2.f); // @@ -116,48 +114,47 @@ static std::vector make_layers( if ((layer_id % 8) == 0) // Don't call the following function too often as it flushes CPU write caches due to synchronization primitves. throw_on_cancel(); - SLAAutoSupports::MyLayer &layer = layers[layer_id]; + SLAAutoSupports::MyLayer &layer = layers[layer_id]; const ExPolygons &islands = slices[layer_id]; //FIXME WTF? const float height = (layer_id>2 ? heights[layer_id-3] : heights[0]-(heights[1]-heights[0])); - layer.islands.reserve(islands.size()); + layer.islands.reserve(islands.size()); for (const ExPolygon &island : islands) { float area = float(island.area() * SCALING_FACTOR * SCALING_FACTOR); if (area >= pixel_area) //FIXME this is not a correct centroid of a polygon with holes. - layer.islands.emplace_back(layer, island, get_extents(island.contour), Slic3r::unscale(island.contour.centroid()).cast(), area, height); + layer.islands.emplace_back(layer, island, get_extents(island.contour), Slic3r::unscale(island.contour.centroid()).cast(), area, height); } } }); - // Calculate overlap of successive layers. Link overlapping islands. - tbb::parallel_for(tbb::blocked_range(1, layers.size(), 8), - [&layers, &heights, throw_on_cancel](const tbb::blocked_range& range) { - for (size_t layer_id = range.begin(); layer_id < range.end(); ++layer_id) { - if ((layer_id % 2) == 0) - // Don't call the following function too often as it flushes CPU write caches due to synchronization primitves. - throw_on_cancel(); - SLAAutoSupports::MyLayer &layer_above = layers[layer_id]; - SLAAutoSupports::MyLayer &layer_below = layers[layer_id - 1]; + // Calculate overlap of successive layers. Link overlapping islands. + tbb::parallel_for(tbb::blocked_range(1, layers.size(), 8), + [&layers, &heights, throw_on_cancel](const tbb::blocked_range& range) { + for (size_t layer_id = range.begin(); layer_id < range.end(); ++layer_id) { + if ((layer_id % 2) == 0) + // Don't call the following function too often as it flushes CPU write caches due to synchronization primitves. + throw_on_cancel(); + SLAAutoSupports::MyLayer &layer_above = layers[layer_id]; + SLAAutoSupports::MyLayer &layer_below = layers[layer_id - 1]; //FIXME WTF? - const float height = (layer_id>2 ? heights[layer_id-3] : heights[0]-(heights[1]-heights[0])); const float layer_height = (layer_id!=0 ? heights[layer_id]-heights[layer_id-1] : heights[0]); const float safe_angle = 5.f * (float(M_PI)/180.f); // smaller number - less supports const float between_layers_offset = float(scale_(layer_height / std::tan(safe_angle))); const float slope_angle = 75.f * (float(M_PI)/180.f); // smaller number - less supports const float slope_offset = float(scale_(layer_height / std::tan(slope_angle))); - //FIXME This has a quadratic time complexity, it will be excessively slow for many tiny islands. - for (SLAAutoSupports::Structure &top : layer_above.islands) { - for (SLAAutoSupports::Structure &bottom : layer_below.islands) { + //FIXME This has a quadratic time complexity, it will be excessively slow for many tiny islands. + for (SLAAutoSupports::Structure &top : layer_above.islands) { + for (SLAAutoSupports::Structure &bottom : layer_below.islands) { float overlap_area = top.overlap_area(bottom); if (overlap_area > 0) { - top.islands_below.emplace_back(&bottom, overlap_area); + top.islands_below.emplace_back(&bottom, overlap_area); bottom.islands_above.emplace_back(&top, overlap_area); } } if (! top.islands_below.empty()) { Polygons top_polygons = to_polygons(*top.polygon); - Polygons bottom_polygons = top.polygons_below(); + Polygons bottom_polygons = top.polygons_below(); top.overhangs = diff_ex(top_polygons, bottom_polygons); if (! top.overhangs.empty()) { top.overhangs_area = 0.f; @@ -167,21 +164,21 @@ static std::vector make_layers( expolys_with_areas.emplace_back(&ex, area); top.overhangs_area += area; } - std::sort(expolys_with_areas.begin(), expolys_with_areas.end(), + std::sort(expolys_with_areas.begin(), expolys_with_areas.end(), [](const std::pair &p1, const std::pair &p2) { return p1.second > p2.second; }); ExPolygons overhangs_sorted; for (auto &p : expolys_with_areas) overhangs_sorted.emplace_back(std::move(*p.first)); - top.overhangs = std::move(overhangs_sorted); + top.overhangs = std::move(overhangs_sorted); top.overhangs_area *= float(SCALING_FACTOR * SCALING_FACTOR); top.overhangs_slopes = diff_ex(top_polygons, offset(bottom_polygons, slope_offset)); top.dangling_areas = diff_ex(top_polygons, offset(bottom_polygons, between_layers_offset)); } } - } - } - }); + } + } + }); return layers; } @@ -210,14 +207,14 @@ void SLAAutoSupports::process(const std::vector& slices, const std:: support_force_bottom[i] = layer_bottom->islands[i].supports_force_total(); } for (Structure &top : layer_top->islands) - for (Structure::Link &bottom_link : top.islands_below) { + for (Structure::Link &bottom_link : top.islands_below) { Structure &bottom = *bottom_link.island; - float centroids_dist = (bottom.centroid - top.centroid).norm(); + //float centroids_dist = (bottom.centroid - top.centroid).norm(); // Penalization resulting from centroid offset: // bottom.supports_force *= std::min(1.f, 1.f - std::min(1.f, (1600.f * layer_height) * centroids_dist * centroids_dist / bottom.area)); float &support_force = support_force_bottom[&bottom - layer_bottom->islands.data()]; //FIXME this condition does not reflect a bifurcation into a one large island and one tiny island well, it incorrectly resets the support force to zero. -// One should rather work with the overlap area vs overhang area. +// One should rather work with the overlap area vs overhang area. // support_force *= std::min(1.f, 1.f - std::min(1.f, 0.1f * centroids_dist * centroids_dist / bottom.area)); // Penalization resulting from increasing polygon area: support_force *= std::min(1.f, 20.f * bottom.area / top.area); @@ -227,10 +224,10 @@ void SLAAutoSupports::process(const std::vector& slices, const std:: for (Structure &below : layer_bottom->islands) { float below_support_force = support_force_bottom[&below - layer_bottom->islands.data()]; float above_overlap_area = 0.f; - for (Structure::Link &above_link : below.islands_above) - above_overlap_area += above_link.overlap_area; - for (Structure::Link &above_link : below.islands_above) - above_link.island->supports_force_inherited += below_support_force * above_link.overlap_area / above_overlap_area; + for (Structure::Link &above_link : below.islands_above) + above_overlap_area += above_link.overlap_area; + for (Structure::Link &above_link : below.islands_above) + above_link.island->supports_force_inherited += below_support_force * above_link.overlap_area / above_overlap_area; } } // Now iterate over all polygons and append new points if needed. @@ -239,7 +236,7 @@ void SLAAutoSupports::process(const std::vector& slices, const std:: // s.supports_force_inherited /= std::max(1.f, (layer_height / 0.3f) * e_area / s.area); s.supports_force_inherited /= std::max(1.f, 0.17f * (s.overhangs_area) / s.area); - float force_deficit = s.support_force_deficit(m_config.tear_pressure()); + //float force_deficit = s.support_force_deficit(m_config.tear_pressure()); if (s.islands_below.empty()) { // completely new island - needs support no doubt uniformly_cover({ *s.polygon }, s, point_grid, true); } else if (! s.dangling_areas.empty()) { @@ -269,7 +266,7 @@ void SLAAutoSupports::process(const std::vector& slices, const std:: } std::vector sample_expolygon(const ExPolygon &expoly, float samples_per_mm2, std::mt19937 &rng) -{ +{ // Triangulate the polygon with holes into triplets of 3D points. std::vector triangles = Slic3r::triangulate_expolygon_2f(expoly); @@ -350,7 +347,7 @@ static inline std::vector poisson_disk_from_samples(const std::vector(); raw_samples_sorted.emplace_back(sample); } - std::sort(raw_samples_sorted.begin(), raw_samples_sorted.end(), [](const RawSample &lhs, const RawSample &rhs) + std::sort(raw_samples_sorted.begin(), raw_samples_sorted.end(), [](const RawSample &lhs, const RawSample &rhs) { return lhs.cell_id.x() < rhs.cell_id.x() || (lhs.cell_id.x() == rhs.cell_id.x() && lhs.cell_id.y() < rhs.cell_id.y()); }); struct PoissonDiskGridEntry { @@ -380,7 +377,7 @@ static inline std::vector poisson_disk_from_samples(const std::vector poisson_disk_from_samples(const std::vector raw_samples = sample_expolygon_with_boundary(islands, samples_per_mm2, 5.f / poisson_radius, rng); + std::vector raw_samples = sample_expolygon_with_boundary(islands, samples_per_mm2, 5.f / poisson_radius, rng); std::vector poisson_samples; for (size_t iter = 0; iter < 4; ++ iter) { - poisson_samples = poisson_disk_from_samples(raw_samples, poisson_radius, + poisson_samples = poisson_disk_from_samples(raw_samples, poisson_radius, [&structure, &grid3d, min_spacing](const Vec2f &pos) { return grid3d.collides_with(pos, &structure, min_spacing); }); @@ -484,21 +481,21 @@ void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& stru } #ifdef SLA_AUTOSUPPORTS_DEBUG - { - static int irun = 0; - Slic3r::SVG svg(debug_out_path("SLA_supports-uniformly_cover-%d.svg", irun ++), get_extents(islands)); + { + static int irun = 0; + Slic3r::SVG svg(debug_out_path("SLA_supports-uniformly_cover-%d.svg", irun ++), get_extents(islands)); for (const ExPolygon &island : islands) svg.draw(island); - for (const Vec2f &pt : raw_samples) - svg.draw(Point(scale_(pt.x()), scale_(pt.y())), "red"); - for (const Vec2f &pt : poisson_samples) - svg.draw(Point(scale_(pt.x()), scale_(pt.y())), "blue"); - } + for (const Vec2f &pt : raw_samples) + svg.draw(Point(scale_(pt.x()), scale_(pt.y())), "red"); + for (const Vec2f &pt : poisson_samples) + svg.draw(Point(scale_(pt.x()), scale_(pt.y())), "blue"); + } #endif /* NDEBUG */ // assert(! poisson_samples.empty()); if (poisson_samples_target < poisson_samples.size()) { - std::shuffle(poisson_samples.begin(), poisson_samples.end(), rng); + std::shuffle(poisson_samples.begin(), poisson_samples.end(), rng); poisson_samples.erase(poisson_samples.begin() + poisson_samples_target, poisson_samples.end()); } for (const Vec2f &pt : poisson_samples) { diff --git a/src/libslic3r/SLA/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp index 171d2b8d03..53dfef4045 100644 --- a/src/libslic3r/SLA/SLABasePool.cpp +++ b/src/libslic3r/SLA/SLABasePool.cpp @@ -5,11 +5,12 @@ #include "SLABoostAdapter.hpp" #include "ClipperUtils.hpp" #include "Tesselate.hpp" +#include "MTUtils.hpp" // For debugging: -//#include -//#include -//#include "SVG.hpp" +// #include +// #include +// #include "SVG.hpp" namespace Slic3r { namespace sla { @@ -53,7 +54,7 @@ Contour3D walls(const Polygon& lower, const Polygon& upper, // Shorthand for the vertex arrays auto& upoints = upper.points, &lpoints = lower.points; - auto& rpts = ret.points; auto& rfaces = ret.indices; + auto& rpts = ret.points; auto& ind = ret.indices; // If the Z levels are flipped, or the offset difference is negative, we // will interpret that as the triangles normals should be inverted. @@ -61,10 +62,11 @@ Contour3D walls(const Polygon& lower, const Polygon& upper, // Copy the points into the mesh, convert them from 2D to 3D rpts.reserve(upoints.size() + lpoints.size()); - rfaces.reserve(2*upoints.size() + 2*lpoints.size()); - const double sf = SCALING_FACTOR; - for(auto& p : upoints) rpts.emplace_back(p.x()*sf, p.y()*sf, upper_z_mm); - for(auto& p : lpoints) rpts.emplace_back(p.x()*sf, p.y()*sf, lower_z_mm); + ind.reserve(2 * upoints.size() + 2 * lpoints.size()); + for (auto &p : upoints) + rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), upper_z_mm); + for (auto &p : lpoints) + rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), lower_z_mm); // Create pointing indices into vertex arrays. u-upper, l-lower size_t uidx = 0, lidx = offs, unextidx = 1, lnextidx = offs + 1; @@ -121,9 +123,9 @@ Contour3D walls(const Polygon& lower, const Polygon& upper, case Proceed::UPPER: if(!ustarted || uidx != uendidx) { // there are vertices remaining // Get the 3D vertices in order - const Vec3d& p_up1 = rpts[size_t(uidx)]; - const Vec3d& p_low = rpts[size_t(lidx)]; - const Vec3d& p_up2 = rpts[size_t(unextidx)]; + const Vec3d& p_up1 = rpts[uidx]; + const Vec3d& p_low = rpts[lidx]; + const Vec3d& p_up2 = rpts[unextidx]; // Calculate fitness: the average of the two connecting edges double a = offsdiff2 - (distfn(p_up1, p_low) - zdiff2); @@ -133,8 +135,9 @@ Contour3D walls(const Polygon& lower, const Polygon& upper, if(current_fit > prev_fit) { // fit is worse than previously proceed = Proceed::LOWER; } else { // good to go, create the triangle - inverted? rfaces.emplace_back(unextidx, lidx, uidx) : - rfaces.emplace_back(uidx, lidx, unextidx) ; + inverted + ? ind.emplace_back(int(unextidx), int(lidx), int(uidx)) + : ind.emplace_back(int(uidx), int(lidx), int(unextidx)); // Increment the iterators, rotate if necessary ++uidx; ++unextidx; @@ -150,9 +153,9 @@ Contour3D walls(const Polygon& lower, const Polygon& upper, case Proceed::LOWER: // Mode with lower segment, upper vertex. Same structure: if(!lstarted || lidx != lendidx) { - const Vec3d& p_low1 = rpts[size_t(lidx)]; - const Vec3d& p_low2 = rpts[size_t(lnextidx)]; - const Vec3d& p_up = rpts[size_t(uidx)]; + const Vec3d& p_low1 = rpts[lidx]; + const Vec3d& p_low2 = rpts[lnextidx]; + const Vec3d& p_up = rpts[uidx]; double a = offsdiff2 - (distfn(p_up, p_low1) - zdiff2); double b = offsdiff2 - (distfn(p_up, p_low2) - zdiff2); @@ -161,8 +164,9 @@ Contour3D walls(const Polygon& lower, const Polygon& upper, if(current_fit > prev_fit) { proceed = Proceed::UPPER; } else { - inverted? rfaces.emplace_back(uidx, lnextidx, lidx) : - rfaces.emplace_back(lidx, lnextidx, uidx); + inverted + ? ind.emplace_back(int(uidx), int(lnextidx), int(lidx)) + : ind.emplace_back(int(lidx), int(lnextidx), int(uidx)); ++lidx; ++lnextidx; if(lnextidx == rpts.size()) lnextidx = offs; @@ -180,9 +184,10 @@ Contour3D walls(const Polygon& lower, const Polygon& upper, } /// Offsetting with clipper and smoothing the edges into a curvature. -void offset(ExPolygon& sh, coord_t distance) { +void offset(ExPolygon& sh, coord_t distance, bool edgerounding = true) { using ClipperLib::ClipperOffset; using ClipperLib::jtRound; + using ClipperLib::jtMiter; using ClipperLib::etClosedPolygon; using ClipperLib::Paths; using ClipperLib::Path; @@ -199,11 +204,13 @@ void offset(ExPolygon& sh, coord_t distance) { return; } + auto jointype = edgerounding? jtRound : jtMiter; + ClipperOffset offs; - offs.ArcTolerance = 0.01*mm(1); + offs.ArcTolerance = scaled(0.01); Paths result; - offs.AddPath(ctour, jtRound, etClosedPolygon); - offs.AddPaths(holes, jtRound, etClosedPolygon); + offs.AddPath(ctour, jointype, etClosedPolygon); + offs.AddPaths(holes, jointype, etClosedPolygon); offs.Execute(result, static_cast(distance)); // Offsetting reverts the orientation and also removes the last vertex @@ -233,6 +240,50 @@ void offset(ExPolygon& sh, coord_t distance) { } } +void offset(Polygon &sh, coord_t distance, bool edgerounding = true) +{ + using ClipperLib::ClipperOffset; + using ClipperLib::jtRound; + using ClipperLib::jtMiter; + using ClipperLib::etClosedPolygon; + using ClipperLib::Paths; + using ClipperLib::Path; + + auto &&ctour = Slic3rMultiPoint_to_ClipperPath(sh); + + // If the input is not at least a triangle, we can not do this algorithm + if (ctour.size() < 3) { + BOOST_LOG_TRIVIAL(error) << "Invalid geometry for offsetting!"; + return; + } + + ClipperOffset offs; + offs.ArcTolerance = 0.01 * scaled(1.); + Paths result; + offs.AddPath(ctour, edgerounding ? jtRound : jtMiter, etClosedPolygon); + offs.Execute(result, static_cast(distance)); + + // Offsetting reverts the orientation and also removes the last vertex + // so boost will not have a closed polygon. + + bool found_the_contour = false; + for (auto &r : result) { + if (ClipperLib::Orientation(r)) { + // We don't like if the offsetting generates more than one contour + // but throwing would be an overkill. Instead, we should warn the + // caller about the inability to create correct geometries + if (!found_the_contour) { + auto rr = ClipperPath_to_Slic3rPolygon(r); + sh.points.swap(rr.points); + found_the_contour = true; + } else { + BOOST_LOG_TRIVIAL(warning) + << "Warning: offsetting result is invalid!"; + } + } + } +} + /// Unification of polygons (with clipper) preserving holes as well. ExPolygons unify(const ExPolygons& shapes) { using ClipperLib::ptSubject; @@ -303,16 +354,116 @@ ExPolygons unify(const ExPolygons& shapes) { return retv; } -/// Only a debug function to generate top and bottom plates from a 2D shape. -/// It is not used in the algorithm directly. -inline Contour3D roofs(const ExPolygon& poly, coord_t z_distance) { - auto lower = triangulate_expolygon_3d(poly); - auto upper = triangulate_expolygon_3d(poly, z_distance*SCALING_FACTOR, true); - Contour3D ret; - ret.merge(lower); ret.merge(upper); +Polygons unify(const Polygons& shapes) { + using ClipperLib::ptSubject; + + bool closed = true; + bool valid = true; + + ClipperLib::Clipper clipper; + + for(auto& path : shapes) { + auto clipperpath = Slic3rMultiPoint_to_ClipperPath(path); + + if(!clipperpath.empty()) + valid &= clipper.AddPath(clipperpath, ptSubject, closed); + } + + if(!valid) BOOST_LOG_TRIVIAL(warning) << "Unification of invalid shapes!"; + + ClipperLib::Paths result; + clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNonZero); + + Polygons ret; + for (ClipperLib::Path &p : result) { + Polygon pp = ClipperPath_to_Slic3rPolygon(p); + if (!pp.is_clockwise()) ret.emplace_back(std::move(pp)); + } + return ret; } +// Function to cut tiny connector cavities for a given polygon. The input poly +// will be offsetted by "padding" and small rectangle shaped cavities will be +// inserted along the perimeter in every "stride" distance. The stick rectangles +// will have a with about "stick_width". The input dimensions are in world +// measure, not the scaled clipper units. +void breakstick_holes(ExPolygon& poly, + double padding, + double stride, + double stick_width, + double penetration) +{ + // SVG svg("bridgestick_plate.svg"); + // svg.draw(poly); + + auto transf = [stick_width, penetration, padding, stride](Points &pts) { + // The connector stick will be a small rectangle with dimensions + // stick_width x (penetration + padding) to have some penetration + // into the input polygon. + + Points out; + out.reserve(2 * pts.size()); // output polygon points + + // stick bottom and right edge dimensions + double sbottom = scaled(stick_width); + double sright = scaled(penetration + padding); + + // scaled stride distance + double sstride = scaled(stride); + double t = 0; + + // process pairs of vertices as an edge, start with the last and + // first point + for (size_t i = pts.size() - 1, j = 0; j < pts.size(); i = j, ++j) { + // Get vertices and the direction vectors + const Point &a = pts[i], &b = pts[j]; + Vec2d dir = b.cast() - a.cast(); + double nrm = dir.norm(); + dir /= nrm; + Vec2d dirp(-dir(Y), dir(X)); + + // Insert start point + out.emplace_back(a); + + // dodge the start point, do not make sticks on the joins + while (t < sbottom) t += sbottom; + double tend = nrm - sbottom; + + while (t < tend) { // insert the stick on the polygon perimeter + + // calculate the stick rectangle vertices and insert them + // into the output. + Point p1 = a + (t * dir).cast(); + Point p2 = p1 + (sright * dirp).cast(); + Point p3 = p2 + (sbottom * dir).cast(); + Point p4 = p3 + (sright * -dirp).cast(); + out.insert(out.end(), {p1, p2, p3, p4}); + + // continue along the perimeter + t += sstride; + } + + t = t - nrm; + + // Insert edge endpoint + out.emplace_back(b); + } + + // move the new points + out.shrink_to_fit(); + pts.swap(out); + }; + + if(stride > 0.0 && stick_width > 0.0 && padding > 0.0) { + transf(poly.contour.points); + for (auto &h : poly.holes) transf(h.points); + } + + // svg.draw(poly); + // svg.Close(); +} + /// This method will create a rounded edge around a flat polygon in 3d space. /// 'base_plate' parameter is the target plate. /// 'radius' is the radius of the edges. @@ -358,7 +509,7 @@ Contour3D round_edges(const ExPolygon& base_plate, double x2 = xx*xx; double stepy = std::sqrt(r2 - x2); - offset(ob, s*mm(xx)); + offset(ob, s * scaled(xx)); wh = ceilheight_mm - radius_mm + stepy; Contour3D pwalls; @@ -382,7 +533,7 @@ Contour3D round_edges(const ExPolygon& base_plate, double xx = radius_mm - i*stepx; double x2 = xx*xx; double stepy = std::sqrt(r2 - x2); - offset(ob, s*mm(xx)); + offset(ob, s * scaled(xx)); wh = ceilheight_mm - radius_mm - stepy; Contour3D pwalls; @@ -402,41 +553,6 @@ Contour3D round_edges(const ExPolygon& base_plate, return curvedwalls; } -/// Generating the concave part of the 3D pool with the bottom plate and the -/// side walls. -Contour3D inner_bed(const ExPolygon& poly, - double depth_mm, - double begin_h_mm = 0) -{ - Contour3D bottom; - Pointf3s triangles = triangulate_expolygon_3d(poly, -depth_mm + begin_h_mm); - bottom.merge(triangles); - - coord_t depth = mm(depth_mm); - coord_t begin_h = mm(begin_h_mm); - - auto lines = poly.lines(); - - // Generate outer walls - auto fp = [](const Point& p, Point::coord_type z) { - return unscale(x(p), y(p), z); - }; - - for(auto& l : lines) { - auto s = coord_t(bottom.points.size()); - - bottom.points.emplace_back(fp(l.a, -depth + begin_h)); - bottom.points.emplace_back(fp(l.b, -depth + begin_h)); - bottom.points.emplace_back(fp(l.a, begin_h)); - bottom.points.emplace_back(fp(l.b, begin_h)); - - bottom.indices.emplace_back(s + 3, s + 1, s); - bottom.indices.emplace_back(s + 2, s + 3, s); - } - - return bottom; -} - inline Point centroid(Points& pp) { Point c; switch(pp.size()) { @@ -467,41 +583,37 @@ inline Point centroid(Points& pp) { return c; } -inline Point centroid(const ExPolygon& poly) { - return poly.contour.centroid(); +inline Point centroid(const Polygon& poly) { + return poly.centroid(); } /// A fake concave hull that is constructed by connecting separate shapes /// with explicit bridges. Bridges are generated from each shape's centroid /// to the center of the "scene" which is the centroid calculated from the shape /// centroids (a star is created...) -ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, - ThrowOnCancel throw_on_cancel = [](){}) +Polygons concave_hull(const Polygons& polys, double maxd_mm, ThrowOnCancel thr) { namespace bgi = boost::geometry::index; - using SpatElement = std::pair; + using SpatElement = std::pair; using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; - if(polys.empty()) return ExPolygons(); + if(polys.empty()) return Polygons(); + + const double max_dist = scaled(maxd_mm); - ExPolygons punion = unify(polys); // could be redundant + Polygons punion = unify(polys); // could be redundant if(punion.size() == 1) return punion; // We get the centroids of all the islands in the 2D slice Points centroids; centroids.reserve(punion.size()); std::transform(punion.begin(), punion.end(), std::back_inserter(centroids), - [](const ExPolygon& poly) { return centroid(poly); }); - - - SpatIndex boxindex; unsigned idx = 0; - std::for_each(punion.begin(), punion.end(), - [&boxindex, &idx](const ExPolygon& expo) { - BoundingBox bb(expo); - boxindex.insert(std::make_pair(bb, idx++)); - }); - + [](const Polygon& poly) { return centroid(poly); }); + SpatIndex ctrindex; + unsigned idx = 0; + for(const Point &ct : centroids) ctrindex.insert(std::make_pair(ct, idx++)); + // Centroid of the centroids of islands. This is where the additional // connector sticks are routed. Point cc = centroid(centroids); @@ -511,33 +623,40 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, idx = 0; std::transform(centroids.begin(), centroids.end(), std::back_inserter(punion), - [&punion, &boxindex, cc, max_dist_mm, &idx, throw_on_cancel] + [¢roids, &ctrindex, cc, max_dist, &idx, thr] (const Point& c) { - throw_on_cancel(); + thr(); double dx = x(c) - x(cc), dy = y(c) - y(cc); double l = std::sqrt(dx * dx + dy * dy); double nx = dx / l, ny = dy / l; - double max_dist = mm(max_dist_mm); - - ExPolygon& expo = punion[idx++]; - BoundingBox querybb(expo); - - querybb.offset(max_dist); + + Point& ct = centroids[idx]; + std::vector result; - boxindex.query(bgi::intersects(querybb), std::back_inserter(result)); - if(result.size() <= 1) return ExPolygon(); + ctrindex.query(bgi::nearest(ct, 2), std::back_inserter(result)); - ExPolygon r; - auto& ctour = r.contour.points; + double dist = max_dist; + for (const SpatElement &el : result) + if (el.second != idx) { + dist = Line(el.first, ct).length(); + break; + } + + idx++; + + if (dist >= max_dist) return Polygon(); + + Polygon r; + auto& ctour = r.points; ctour.reserve(3); ctour.emplace_back(cc); - Point d(coord_t(mm(1)*nx), coord_t(mm(1)*ny)); + Point d(scaled(nx), scaled(ny)); ctour.emplace_back(c + Point( -y(d), x(d) )); ctour.emplace_back(c + Point( y(d), -x(d) )); - offset(r, mm(1)); + offset(r, scaled(1.)); return r; }); @@ -548,41 +667,55 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, return punion; } -void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h, - float layerh, ThrowOnCancel thrfn) +void base_plate(const TriangleMesh & mesh, + ExPolygons & output, + const std::vector &heights, + ThrowOnCancel thrfn) { - TriangleMesh m = mesh; - m.require_shared_vertices(); // TriangleMeshSlicer needs this - TriangleMeshSlicer slicer(&m); - - auto bb = mesh.bounding_box(); - float gnd = float(bb.min(Z)); - std::vector heights = {float(bb.min(Z))}; - for(float hi = gnd + layerh; hi <= gnd + h; hi += layerh) - heights.emplace_back(hi); - - std::vector out; out.reserve(size_t(std::ceil(h/layerh))); + if (mesh.empty()) return; + // m.require_shared_vertices(); // TriangleMeshSlicer needs this + TriangleMeshSlicer slicer(&mesh); + + std::vector out; out.reserve(heights.size()); slicer.slice(heights, 0.f, &out, thrfn); - + size_t count = 0; for(auto& o : out) count += o.size(); - + // Now we have to unify all slice layers which can be an expensive operation // so we will try to simplify the polygons ExPolygons tmp; tmp.reserve(count); - for(ExPolygons& o : out) for(ExPolygon& e : o) { - auto&& exss = e.simplify(0.1/SCALING_FACTOR); - for(ExPolygon& ep : exss) tmp.emplace_back(std::move(ep)); - } - + for(ExPolygons& o : out) + for(ExPolygon& e : o) { + auto&& exss = e.simplify(scaled(0.1)); + for(ExPolygon& ep : exss) tmp.emplace_back(std::move(ep)); + } + ExPolygons utmp = unify(tmp); - + for(auto& o : utmp) { - auto&& smp = o.simplify(0.1/SCALING_FACTOR); + auto&& smp = o.simplify(scaled(0.1)); output.insert(output.end(), smp.begin(), smp.end()); } } -Contour3D create_base_pool(const ExPolygons &ground_layer, +void base_plate(const TriangleMesh &mesh, + ExPolygons & output, + float h, + float layerh, + ThrowOnCancel thrfn) +{ + auto bb = mesh.bounding_box(); + float gnd = float(bb.min(Z)); + std::vector heights = {float(bb.min(Z))}; + + for(float hi = gnd + layerh; hi <= gnd + h; hi += layerh) + heights.emplace_back(hi); + + base_plate(mesh, output, heights, thrfn); +} + +Contour3D create_base_pool(const Polygons &ground_layer, + const ExPolygons &obj_self_pad = {}, const PoolConfig& cfg = PoolConfig()) { // for debugging: @@ -597,7 +730,7 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, // serve as the bottom plate of the pad. We will offset this concave hull // and then offset back the result with clipper with rounding edges ON. This // trick will create a nice rounded pad shape. - ExPolygons concavehs = concave_hull(ground_layer, mergedist, cfg.throw_on_cancel); + Polygons concavehs = concave_hull(ground_layer, mergedist, cfg.throw_on_cancel); const double thickness = cfg.min_wall_thickness_mm; const double wingheight = cfg.min_wall_height_mm; @@ -607,52 +740,47 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, const double bottom_offs = (thickness + wingheight) / std::tan(slope); // scaled values - const coord_t s_thickness = mm(thickness); - const coord_t s_eradius = mm(cfg.edge_radius_mm); + const coord_t s_thickness = scaled(thickness); + const coord_t s_eradius = scaled(cfg.edge_radius_mm); const coord_t s_safety_dist = 2*s_eradius + coord_t(0.8*s_thickness); - const coord_t s_wingdist = mm(wingdist); - const coord_t s_bottom_offs = mm(bottom_offs); + const coord_t s_wingdist = scaled(wingdist); + const coord_t s_bottom_offs = scaled(bottom_offs); auto& thrcl = cfg.throw_on_cancel; Contour3D pool; - for(ExPolygon& concaveh : concavehs) { - if(concaveh.contour.points.empty()) return pool; - - // Get rid of any holes in the concave hull output. - concaveh.holes.clear(); + for(Polygon& concaveh : concavehs) { + if(concaveh.points.empty()) return pool; // Here lies the trick that does the smoothing only with clipper offset // calls. The offset is configured to round edges. Inner edges will // be rounded because we offset twice: ones to get the outer (top) plate // and again to get the inner (bottom) plate auto outer_base = concaveh; - outer_base.holes.clear(); offset(outer_base, s_safety_dist + s_wingdist + s_thickness); - ExPolygon bottom_poly = outer_base; - bottom_poly.holes.clear(); + ExPolygon bottom_poly; bottom_poly.contour = outer_base; offset(bottom_poly, -s_bottom_offs); // Punching a hole in the top plate for the cavity ExPolygon top_poly; ExPolygon middle_base; ExPolygon inner_base; - top_poly.contour = outer_base.contour; + top_poly.contour = outer_base; if(wingheight > 0) { - inner_base = outer_base; + inner_base.contour = outer_base; offset(inner_base, -(s_thickness + s_wingdist + s_eradius)); - middle_base = outer_base; + middle_base.contour = outer_base; offset(middle_base, -s_thickness); top_poly.holes.emplace_back(middle_base.contour); auto& tph = top_poly.holes.back().points; std::reverse(tph.begin(), tph.end()); } - ExPolygon ob = outer_base; double wh = 0; + ExPolygon ob; ob.contour = outer_base; double wh = 0; // now we will calculate the angle or portion of the circle from // pi/2 that will connect perfectly with the bottom plate. @@ -699,6 +827,7 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, if(wingheight > 0) { // Generate the smoothed edge geometry wh = 0; + ob = middle_base; if(s_eradius) pool.merge(round_edges(middle_base, r, phi - 90, // from tangent lines @@ -713,11 +842,59 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, wh, -wingdist, thrcl)); } - // Now we need to triangulate the top and bottom plates as well as the - // cavity bottom plate which is the same as the bottom plate but it is - // elevated by the thickness. + if (cfg.embed_object) { + ExPolygons bttms = diff_ex(to_polygons(bottom_poly), + to_polygons(obj_self_pad)); + + assert(!bttms.empty()); + + std::sort(bttms.begin(), bttms.end(), + [](const ExPolygon& e1, const ExPolygon& e2) { + return e1.contour.area() > e2.contour.area(); + }); + + if(wingheight > 0) inner_base.holes = bttms.front().holes; + else top_poly.holes = bttms.front().holes; + + auto straight_walls = + [&pool](const Polygon &cntr, coord_t z_low, coord_t z_high) { + + auto lines = cntr.lines(); + + for (auto &l : lines) { + auto s = coord_t(pool.points.size()); + auto& pts = pool.points; + pts.emplace_back(unscale(l.a.x(), l.a.y(), z_low)); + pts.emplace_back(unscale(l.b.x(), l.b.y(), z_low)); + pts.emplace_back(unscale(l.a.x(), l.a.y(), z_high)); + pts.emplace_back(unscale(l.b.x(), l.b.y(), z_high)); + + pool.indices.emplace_back(s, s + 1, s + 3); + pool.indices.emplace_back(s, s + 3, s + 2); + } + }; + + coord_t z_lo = -scaled(fullheight), z_hi = -scaled(wingheight); + for (ExPolygon &ep : bttms) { + pool.merge(triangulate_expolygon_3d(ep, -fullheight, true)); + for (auto &h : ep.holes) straight_walls(h, z_lo, z_hi); + } + + // Skip the outer contour, triangulate the holes + for (auto it = std::next(bttms.begin()); it != bttms.end(); ++it) { + pool.merge(triangulate_expolygon_3d(*it, -wingheight)); + straight_walls(it->contour, z_lo, z_hi); + } + + } else { + // Now we need to triangulate the top and bottom plates as well as + // the cavity bottom plate which is the same as the bottom plate + // but it is elevated by the thickness. + + pool.merge(triangulate_expolygon_3d(bottom_poly, -fullheight, true)); + } + pool.merge(triangulate_expolygon_3d(top_poly)); - pool.merge(triangulate_expolygon_3d(bottom_poly, -fullheight, true)); if(wingheight > 0) pool.merge(triangulate_expolygon_3d(inner_base, -wingheight)); @@ -727,8 +904,8 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, return pool; } -void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, - const PoolConfig& cfg) +void create_base_pool(const Polygons &ground_layer, TriangleMesh& out, + const ExPolygons &holes, const PoolConfig& cfg) { @@ -738,7 +915,7 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, // std::fstream fout("pad_debug.obj", std::fstream::out); // if(fout.good()) pool.to_obj(fout); - out.merge(mesh(create_base_pool(ground_layer, cfg))); + out.merge(mesh(create_base_pool(ground_layer, holes, cfg))); } } diff --git a/src/libslic3r/SLA/SLABasePool.hpp b/src/libslic3r/SLA/SLABasePool.hpp index 3c88e58c85..eec426bbf5 100644 --- a/src/libslic3r/SLA/SLABasePool.hpp +++ b/src/libslic3r/SLA/SLABasePool.hpp @@ -8,7 +8,9 @@ namespace Slic3r { class ExPolygon; +class Polygon; using ExPolygons = std::vector; +using Polygons = std::vector; class TriangleMesh; @@ -19,16 +21,43 @@ using ThrowOnCancel = std::function; /// Calculate the polygon representing the silhouette from the specified height void base_plate(const TriangleMesh& mesh, // input mesh ExPolygons& output, // Output will be merged with - float zlevel = 0.1f, // Plate creation level + float samplingheight = 0.1f, // The height range to sample float layerheight = 0.05f, // The sampling height ThrowOnCancel thrfn = [](){}); // Will be called frequently +void base_plate(const TriangleMesh& mesh, // input mesh + ExPolygons& output, // Output will be merged with + const std::vector&, // Exact Z levels to sample + ThrowOnCancel thrfn = [](){}); // Will be called frequently + +// Function to cut tiny connector cavities for a given polygon. The input poly +// will be offsetted by "padding" and small rectangle shaped cavities will be +// inserted along the perimeter in every "stride" distance. The stick rectangles +// will have a with about "stick_width". The input dimensions are in world +// measure, not the scaled clipper units. +void breakstick_holes(ExPolygon &poly, + double padding, + double stride, + double stick_width, + double penetration = 0.0); + +Polygons concave_hull(const Polygons& polys, double max_dist_mm = 50, + ThrowOnCancel throw_on_cancel = [](){}); + struct PoolConfig { double min_wall_thickness_mm = 2; double min_wall_height_mm = 5; double max_merge_distance_mm = 50; double edge_radius_mm = 1; double wall_slope = std::atan(1.0); // Universal constant for Pi/4 + struct EmbedObject { + double object_gap_mm = 0.5; + double stick_stride_mm = 10; + double stick_width_mm = 0.3; + double stick_penetration_mm = 0.1; + bool enabled = false; + operator bool() const { return enabled; } + } embed_object; ThrowOnCancel throw_on_cancel = [](){}; @@ -42,15 +71,12 @@ struct PoolConfig { }; /// Calculate the pool for the mesh for SLA printing -void create_base_pool(const ExPolygons& base_plate, +void create_base_pool(const Polygons& base_plate, TriangleMesh& output_mesh, + const ExPolygons& holes, const PoolConfig& = PoolConfig()); -/// TODO: Currently the base plate of the pool will have half the height of the -/// whole pool. So the carved out space has also half the height. This is not -/// a particularly elegant solution, the thickness should be exactly -/// min_wall_thickness and it should be corrected in the future. This method -/// will return the correct value for further processing. +/// Returns the elevation needed for compensating the pad. inline double get_pad_elevation(const PoolConfig& cfg) { return cfg.min_wall_thickness_mm; } diff --git a/src/libslic3r/SLA/SLABoilerPlate.hpp b/src/libslic3r/SLA/SLABoilerPlate.hpp index 602121af9a..86e90f3b7f 100644 --- a/src/libslic3r/SLA/SLABoilerPlate.hpp +++ b/src/libslic3r/SLA/SLABoilerPlate.hpp @@ -11,11 +11,6 @@ namespace Slic3r { namespace sla { -using coord_t = Point::coord_type; - -/// get the scaled clipper units for a millimeter value -inline coord_t mm(double v) { return coord_t(v/SCALING_FACTOR); } - /// Get x and y coordinates (because we are eigenizing...) inline coord_t x(const Point& p) { return p(0); } inline coord_t y(const Point& p) { return p(1); } @@ -36,12 +31,10 @@ inline coord_t x(const Vec3crd& p) { return p(0); } inline coord_t y(const Vec3crd& p) { return p(1); } inline coord_t z(const Vec3crd& p) { return p(2); } -using Indices = std::vector; - /// Intermediate struct for a 3D mesh struct Contour3D { Pointf3s points; - Indices indices; + std::vector indices; void merge(const Contour3D& ctr) { auto s3 = coord_t(points.size()); diff --git a/src/libslic3r/SLA/SLACommon.hpp b/src/libslic3r/SLA/SLACommon.hpp index 855802759e..d8e258035e 100644 --- a/src/libslic3r/SLA/SLACommon.hpp +++ b/src/libslic3r/SLA/SLACommon.hpp @@ -18,41 +18,57 @@ namespace sla { // An enum to keep track of where the current points on the ModelObject came from. enum class PointsStatus { - None, // No points were generated so far. + NoPoints, // No points were generated so far. Generating, // The autogeneration algorithm triggered, but not yet finished. AutoGenerated, // Points were autogenerated (i.e. copied from the backend). UserModified // User has done some edits. }; -struct SupportPoint { +struct SupportPoint +{ Vec3f pos; float head_front_radius; - bool is_new_island; + bool is_new_island; - SupportPoint() : - pos(Vec3f::Zero()), head_front_radius(0.f), is_new_island(false) {} + SupportPoint() + : pos(Vec3f::Zero()), head_front_radius(0.f), is_new_island(false) + {} - SupportPoint(float pos_x, float pos_y, float pos_z, float head_radius, bool new_island) : - pos(pos_x, pos_y, pos_z), head_front_radius(head_radius), is_new_island(new_island) {} + SupportPoint(float pos_x, + float pos_y, + float pos_z, + float head_radius, + bool new_island) + : pos(pos_x, pos_y, pos_z) + , head_front_radius(head_radius) + , is_new_island(new_island) + {} - SupportPoint(Vec3f position, float head_radius, bool new_island) : - pos(position), head_front_radius(head_radius), is_new_island(new_island) {} + SupportPoint(Vec3f position, float head_radius, bool new_island) + : pos(position) + , head_front_radius(head_radius) + , is_new_island(new_island) + {} - SupportPoint(Eigen::Matrix data) : - pos(data(0), data(1), data(2)), head_front_radius(data(3)), is_new_island(data(4) != 0.f) {} + SupportPoint(Eigen::Matrix data) + : pos(data(0), data(1), data(2)) + , head_front_radius(data(3)) + , is_new_island(data(4) != 0.f) + {} - bool operator==(const SupportPoint& sp) const { return (pos==sp.pos) && head_front_radius==sp.head_front_radius && is_new_island==sp.is_new_island; } - bool operator!=(const SupportPoint& sp) const { return !(sp == (*this)); } + bool operator==(const SupportPoint &sp) const + { + return (pos == sp.pos) && head_front_radius == sp.head_front_radius && + is_new_island == sp.is_new_island; + } + bool operator!=(const SupportPoint &sp) const { return !(sp == (*this)); } + + template void serialize(Archive &ar) + { + ar(pos, head_front_radius, is_new_island); + } }; -/// An index-triangle structure for libIGL functions. Also serves as an -/// alternative (raw) input format for the SLASupportTree -/*struct EigenMesh3D { - Eigen::MatrixXd V; - Eigen::MatrixXi F; - double ground_level = 0; -};*/ - /// An index-triangle structure for libIGL functions. Also serves as an /// alternative (raw) input format for the SLASupportTree class EigenMesh3D { @@ -60,7 +76,7 @@ class EigenMesh3D { Eigen::MatrixXd m_V; Eigen::MatrixXi m_F; - double m_ground_level = 0; + double m_ground_level = 0, m_gnd_offset = 0; std::unique_ptr m_aabb; public: @@ -71,7 +87,9 @@ public: ~EigenMesh3D(); - inline double ground_level() const { return m_ground_level; } + inline double ground_level() const { return m_ground_level + m_gnd_offset; } + inline void ground_level_offset(double o) { m_gnd_offset = o; } + inline double ground_level_offset() const { return m_gnd_offset; } inline const Eigen::MatrixXd& V() const { return m_V; } inline const Eigen::MatrixXi& F() const { return m_F; } @@ -149,11 +167,14 @@ public: #endif /* SLIC3R_SLA_NEEDS_WINDTREE */ double squared_distance(const Vec3d& p, int& i, Vec3d& c) const; + inline double squared_distance(const Vec3d &p) const + { + int i; + Vec3d c; + return squared_distance(p, i, c); + } }; - - - } // namespace sla } // namespace Slic3r diff --git a/src/libslic3r/Rasterizer/Rasterizer.cpp b/src/libslic3r/SLA/SLARaster.cpp similarity index 72% rename from src/libslic3r/Rasterizer/Rasterizer.cpp rename to src/libslic3r/SLA/SLARaster.cpp index e1213555c4..32a88b1b50 100644 --- a/src/libslic3r/Rasterizer/Rasterizer.cpp +++ b/src/libslic3r/SLA/SLARaster.cpp @@ -1,5 +1,10 @@ -#include "Rasterizer.hpp" -#include +#ifndef SLARASTER_CPP +#define SLARASTER_CPP + +#include + +#include "SLARaster.hpp" +#include "libslic3r/ExPolygon.hpp" #include // For rasterizing @@ -19,11 +24,13 @@ namespace Slic3r { -const Polygon& contour(const ExPolygon& p) { return p.contour; } -const ClipperLib::Path& contour(const ClipperLib::Polygon& p) { return p.Contour; } +inline const Polygon& contour(const ExPolygon& p) { return p.contour; } +inline const ClipperLib::Path& contour(const ClipperLib::Polygon& p) { return p.Contour; } -const Polygons& holes(const ExPolygon& p) { return p.holes; } -const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.Holes; } +inline const Polygons& holes(const ExPolygon& p) { return p.holes; } +inline const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.Holes; } + +namespace sla { class Raster::Impl { public: @@ -39,7 +46,7 @@ public: static const TPixel ColorWhite; static const TPixel ColorBlack; - using Origin = Raster::Origin; + using Format = Raster::Format; private: Raster::Resolution m_resolution; @@ -52,16 +59,21 @@ private: TRendererAA m_renderer; std::function m_gammafn; - Origin m_o; + std::array m_mirror; + Format m_fmt = Format::PNG; inline void flipy(agg::path_storage& path) const { path.flip_y(0, m_resolution.height_px); } + + inline void flipx(agg::path_storage& path) const { + path.flip_x(0, m_resolution.width_px); + } public: inline Impl(const Raster::Resolution& res, const Raster::PixelDim &pd, - Origin o, double gamma = 1.0): + const std::array& mirror, double gamma = 1.0): m_resolution(res), // m_pxdim(pd), m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm), @@ -72,7 +84,7 @@ public: m_pixfmt(m_rbuf), m_raw_renderer(m_pixfmt), m_renderer(m_raw_renderer), - m_o(o) + m_mirror(mirror) { m_renderer.color(ColorWhite); @@ -81,6 +93,19 @@ public: clear(); } + + inline Impl(const Raster::Resolution& res, + const Raster::PixelDim &pd, + Format fmt, + double gamma = 1.0): + Impl(res, pd, {false, false}, gamma) + { + switch (fmt) { + case Format::PNG: m_mirror = {false, true}; break; + case Format::RAW: m_mirror = {false, false}; break; + } + m_fmt = fmt; + } template void draw(const P &poly) { agg::rasterizer_scanline_aa<> ras; @@ -89,14 +114,16 @@ public: ras.gamma(m_gammafn); auto&& path = to_path(contour(poly)); - - if(m_o == Origin::TOP_LEFT) flipy(path); + + if(m_mirror[X]) flipx(path); + if(m_mirror[Y]) flipy(path); ras.add_path(path); for(auto& h : holes(poly)) { auto&& holepath = to_path(h); - if(m_o == Origin::TOP_LEFT) flipy(holepath); + if(m_mirror[X]) flipx(holepath); + if(m_mirror[Y]) flipy(holepath); ras.add_path(holepath); } @@ -108,11 +135,11 @@ public: } inline TBuffer& buffer() { return m_buf; } + + inline Format format() const { return m_fmt; } inline const Raster::Resolution resolution() { return m_resolution; } - - inline Origin origin() const /*noexcept*/ { return m_o; } - + private: inline double getPx(const Point& p) { return p(0) * m_pxdim_scaled.w_mm; @@ -154,30 +181,30 @@ private: const Raster::Impl::TPixel Raster::Impl::ColorWhite = Raster::Impl::TPixel(255); const Raster::Impl::TPixel Raster::Impl::ColorBlack = Raster::Impl::TPixel(0); -Raster::Raster(const Resolution &r, const PixelDim &pd, Origin o, double g): - m_impl(new Impl(r, pd, o, g)) {} +template<> Raster::Raster() { reset(); }; +Raster::~Raster() = default; -Raster::Raster() {} +// Raster::Raster(Raster &&m) = default; +// Raster& Raster::operator=(Raster&&) = default; -Raster::~Raster() {} - -Raster::Raster(Raster &&m): - m_impl(std::move(m.m_impl)) {} - -void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd, - double g) -{ - // Free up the unnecessary memory and make sure it stays clear after - // an exception - auto o = m_impl? m_impl->origin() : Origin::TOP_LEFT; - reset(r, pd, o, g); +// FIXME: remove after migrating to higher version of windows compiler +Raster::Raster(Raster &&m): m_impl(std::move(m.m_impl)) {} +Raster& Raster::operator=(Raster &&m) { + m_impl = std::move(m.m_impl); return *this; } void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd, - Raster::Origin o, double gamma) + Format fmt, double gamma) { m_impl.reset(); - m_impl.reset(new Impl(r, pd, o, gamma)); + m_impl.reset(new Impl(r, pd, fmt, gamma)); +} + +void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd, + const std::array& mirror, double gamma) +{ + m_impl.reset(); + m_impl.reset(new Impl(r, pd, mirror, gamma)); } void Raster::reset() @@ -208,13 +235,13 @@ void Raster::draw(const ClipperLib::Polygon &poly) m_impl->draw(poly); } -void Raster::save(std::ostream& stream, Compression comp) +void Raster::save(std::ostream& stream, Format fmt) { assert(m_impl); if(!stream.good()) return; - switch(comp) { - case Compression::PNG: { + switch(fmt) { + case Format::PNG: { auto& b = m_impl->buffer(); size_t out_len = 0; void * rawdata = tdefl_write_image_to_png_file_in_memory( @@ -231,7 +258,7 @@ void Raster::save(std::ostream& stream, Compression comp) break; } - case Compression::RAW: { + case Format::RAW: { stream << "P5 " << m_impl->resolution().width_px << " " << m_impl->resolution().height_px << " " @@ -244,14 +271,19 @@ void Raster::save(std::ostream& stream, Compression comp) } } -RawBytes Raster::save(Raster::Compression comp) +void Raster::save(std::ostream &stream) +{ + save(stream, m_impl->format()); +} + +RawBytes Raster::save(Format fmt) { assert(m_impl); std::vector data; size_t s = 0; - switch(comp) { - case Compression::PNG: { + switch(fmt) { + case Format::PNG: { void *rawdata = tdefl_write_image_to_png_file_in_memory( m_impl->buffer().data(), int(resolution().width_px), @@ -265,7 +297,7 @@ RawBytes Raster::save(Raster::Compression comp) MZ_FREE(rawdata); break; } - case Compression::RAW: { + case Format::RAW: { auto header = std::string("P5 ") + std::to_string(m_impl->resolution().width_px) + " " + std::to_string(m_impl->resolution().height_px) + " " + "255 "; @@ -286,4 +318,12 @@ RawBytes Raster::save(Raster::Compression comp) return {std::move(data)}; } +RawBytes Raster::save() +{ + return save(m_impl->format()); } + +} +} + +#endif // SLARASTER_CPP diff --git a/src/libslic3r/Rasterizer/Rasterizer.hpp b/src/libslic3r/SLA/SLARaster.hpp similarity index 64% rename from src/libslic3r/Rasterizer/Rasterizer.hpp rename to src/libslic3r/SLA/SLARaster.hpp index 3fffe1a365..d3bd52d92c 100644 --- a/src/libslic3r/Rasterizer/Rasterizer.hpp +++ b/src/libslic3r/SLA/SLARaster.hpp @@ -1,17 +1,21 @@ -#ifndef RASTERIZER_HPP -#define RASTERIZER_HPP +#ifndef SLARASTER_HPP +#define SLARASTER_HPP #include #include #include +#include +#include #include namespace ClipperLib { struct Polygon; } -namespace Slic3r { +namespace Slic3r { class ExPolygon; +namespace sla { + // Raw byte buffer paired with its size. Suitable for compressed PNG data. class RawBytes { @@ -23,15 +27,18 @@ public: size_t size() const { return m_buffer.size(); } const uint8_t * data() { return m_buffer.data(); } + + RawBytes(const RawBytes&) = delete; + RawBytes& operator=(const RawBytes&) = delete; // ///////////////////////////////////////////////////////////////////////// // FIXME: the following is needed for MSVC2013 compatibility // ///////////////////////////////////////////////////////////////////////// - RawBytes(const RawBytes&) = delete; - RawBytes(RawBytes&& mv) : m_buffer(std::move(mv.m_buffer)) {} + // RawBytes(RawBytes&&) = default; + // RawBytes& operator=(RawBytes&&) = default; - RawBytes& operator=(const RawBytes&) = delete; + RawBytes(RawBytes&& mv) : m_buffer(std::move(mv.m_buffer)) {} RawBytes& operator=(RawBytes&& mv) { m_buffer = std::move(mv.m_buffer); return *this; @@ -54,28 +61,19 @@ class Raster { public: /// Supported compression types - enum class Compression { + enum class Format { RAW, //!> Uncompressed pixel data PNG //!> PNG compression }; - /// The Rasterizer expects the input polygons to have their coordinate - /// system origin in the bottom left corner. If the raster is then - /// configured with the TOP_LEFT origin parameter (in the constructor) than - /// it will flip the Y axis in output to maintain the correct orientation. - /// This is the default case with PNG images. They have the origin in the - /// top left corner. Without the flipping, the image would be upside down - /// with the scaled (clipper) coordinate system of the input polygons. - enum class Origin { - TOP_LEFT, - BOTTOM_LEFT - }; - /// Type that represents a resolution in pixels. struct Resolution { unsigned width_px; unsigned height_px; - inline Resolution(unsigned w, unsigned h): width_px(w), height_px(h) {} + + inline Resolution(unsigned w = 0, unsigned h = 0): + width_px(w), height_px(h) {} + inline unsigned pixels() const /*noexcept*/ { return width_px * height_px; } @@ -85,24 +83,34 @@ public: struct PixelDim { double w_mm; double h_mm; - inline PixelDim(double px_width_mm, double px_height_mm ): + inline PixelDim(double px_width_mm = 0.0, double px_height_mm = 0.0): w_mm(px_width_mm), h_mm(px_height_mm) {} }; /// Constructor taking the resolution and the pixel dimension. - Raster(const Resolution& r, const PixelDim& pd, - Origin o = Origin::BOTTOM_LEFT, double gamma = 1.0); + template Raster(Args...args) { + reset(std::forward(args)...); + } - Raster(); Raster(const Raster& cpy) = delete; Raster& operator=(const Raster& cpy) = delete; Raster(Raster&& m); + Raster& operator=(Raster&&); ~Raster(); /// Reallocated everything for the given resolution and pixel dimension. - void reset(const Resolution& r, const PixelDim& pd, double gamma = 1.0); - void reset(const Resolution& r, const PixelDim& pd, Origin o, double gamma); - + /// The third parameter is either the X, Y mirroring or a supported format + /// for which the correct mirroring will be configured. + void reset(const Resolution&, + const PixelDim&, + const std::array& mirror, + double gamma = 1.0); + + void reset(const Resolution& r, + const PixelDim& pd, + Format o, + double gamma = 1.0); + /** * Release the allocated resources. Drawing in this state ends in * unspecified behavior. @@ -119,11 +127,24 @@ public: void draw(const ExPolygon& poly); void draw(const ClipperLib::Polygon& poly); + // Saving the raster: + // It is possible to override the format given in the constructor but + // be aware that the mirroring will not be modified. + /// Save the raster on the specified stream. - void save(std::ostream& stream, Compression comp = Compression::RAW); + void save(std::ostream& stream, Format); + void save(std::ostream& stream); - RawBytes save(Compression comp = Compression::RAW); + /// Save into a continuous byte stream which is returned. + RawBytes save(Format fmt); + RawBytes save(); }; -} -#endif // RASTERIZER_HPP +// This prevents the duplicate default constructor warning on MSVC2013 +template<> Raster::Raster(); + + +} // sla +} // Slic3r + +#endif // SLARASTER_HPP diff --git a/src/libslic3r/SLA/SLARasterWriter.cpp b/src/libslic3r/SLA/SLARasterWriter.cpp new file mode 100644 index 0000000000..f7c3925ac7 --- /dev/null +++ b/src/libslic3r/SLA/SLARasterWriter.cpp @@ -0,0 +1,136 @@ +#include "SLARasterWriter.hpp" +#include "libslic3r/Zipper.hpp" +#include "ExPolygon.hpp" +#include + +#include +#include + +namespace Slic3r { namespace sla { + +std::string SLARasterWriter::createIniContent(const std::string& projectname) const +{ + auto expt_str = std::to_string(m_exp_time_s); + auto expt_first_str = std::to_string(m_exp_time_first_s); + auto layerh_str = std::to_string(m_layer_height); + + const std::string cnt_fade_layers = std::to_string(m_cnt_fade_layers); + const std::string cnt_slow_layers = std::to_string(m_cnt_slow_layers); + const std::string cnt_fast_layers = std::to_string(m_cnt_fast_layers); + const std::string used_material = std::to_string(m_used_material); + + return std::string( + "action = print\n" + "jobDir = ") + projectname + "\n" + + "expTime = " + expt_str + "\n" + "expTimeFirst = " + expt_first_str + "\n" + "numFade = " + cnt_fade_layers + "\n" + "layerHeight = " + layerh_str + "\n" + "usedMaterial = " + used_material + "\n" + "numSlow = " + cnt_slow_layers + "\n" + "numFast = " + cnt_fast_layers + "\n"; +} + +void SLARasterWriter::flpXY(ClipperLib::Polygon &poly) +{ + for(auto& p : poly.Contour) std::swap(p.X, p.Y); + std::reverse(poly.Contour.begin(), poly.Contour.end()); + + for(auto& h : poly.Holes) { + for(auto& p : h) std::swap(p.X, p.Y); + std::reverse(h.begin(), h.end()); + } +} + +void SLARasterWriter::flpXY(ExPolygon &poly) +{ + for(auto& p : poly.contour.points) p = Point(p.y(), p.x()); + std::reverse(poly.contour.points.begin(), poly.contour.points.end()); + + for(auto& h : poly.holes) { + for(auto& p : h.points) p = Point(p.y(), p.x()); + std::reverse(h.points.begin(), h.points.end()); + } +} + +SLARasterWriter::SLARasterWriter(const SLAPrinterConfig &cfg, + const SLAMaterialConfig &mcfg, + double layer_height) +{ + double w = cfg.display_width.getFloat(); + double h = cfg.display_height.getFloat(); + auto pw = unsigned(cfg.display_pixels_x.getInt()); + auto ph = unsigned(cfg.display_pixels_y.getInt()); + + m_mirror[X] = cfg.display_mirror_x.getBool(); + + // PNG raster will implicitly do an Y mirror + m_mirror[Y] = ! cfg.display_mirror_y.getBool(); + + auto ro = cfg.display_orientation.getInt(); + + if(ro == roPortrait) { + std::swap(w, h); + std::swap(pw, ph); + m_o = roPortrait; + + // XY flipping implicitly does an X mirror + m_mirror[X] = ! m_mirror[X]; + } else m_o = roLandscape; + + m_res = Raster::Resolution(pw, ph); + m_pxdim = Raster::PixelDim(w/pw, h/ph); + m_exp_time_s = mcfg.exposure_time.getFloat(); + m_exp_time_first_s = mcfg.initial_exposure_time.getFloat(); + m_layer_height = layer_height; + + m_gamma = cfg.gamma_correction.getFloat(); +} + +void SLARasterWriter::save(const std::string &fpath, const std::string &prjname) +{ + try { + Zipper zipper(fpath); // zipper with no compression + + std::string project = prjname.empty()? + boost::filesystem::path(fpath).stem().string() : prjname; + + zipper.add_entry("config.ini"); + + zipper << createIniContent(project); + + for(unsigned i = 0; i < m_layers_rst.size(); i++) + { + if(m_layers_rst[i].rawbytes.size() > 0) { + char lyrnum[6]; + std::sprintf(lyrnum, "%.5d", i); + auto zfilename = project + lyrnum + ".png"; + + // Add binary entry to the zipper + zipper.add_entry(zfilename, + m_layers_rst[i].rawbytes.data(), + m_layers_rst[i].rawbytes.size()); + } + } + + zipper.finalize(); + } catch(std::exception& e) { + BOOST_LOG_TRIVIAL(error) << e.what(); + // Rethrow the exception + throw; + } +} + +void SLARasterWriter::set_statistics(const std::vector statistics) +{ + if (statistics.size() != psCnt) + return; + + m_used_material = statistics[psUsedMaterial]; + m_cnt_fade_layers = int(statistics[psNumFade]); + m_cnt_slow_layers = int(statistics[psNumSlow]); + m_cnt_fast_layers = int(statistics[psNumFast]); +} + +} // namespace sla +} // namespace Slic3r diff --git a/src/libslic3r/SLA/SLARasterWriter.hpp b/src/libslic3r/SLA/SLARasterWriter.hpp new file mode 100644 index 0000000000..7133d2ddec --- /dev/null +++ b/src/libslic3r/SLA/SLARasterWriter.hpp @@ -0,0 +1,167 @@ +#ifndef SLARASTERWRITER_HPP +#define SLARASTERWRITER_HPP + +// For png export of the sliced model +#include +#include +#include +#include + +#include "libslic3r/PrintConfig.hpp" + +#include "SLARaster.hpp" + +namespace Slic3r { namespace sla { + +// Implementation for PNG raster output +// Be aware that if a large number of layers are allocated, it can very well +// exhaust the available memory especially on 32 bit platform. +// This class is designed to be used in parallel mode. Layers have an ID and +// each layer can be written and compressed independently (in parallel). +// At the end when all layers where written, the save method can be used to +// write out the result into a zipped archive. +class SLARasterWriter +{ +public: + enum RasterOrientation { + roLandscape, + roPortrait + }; + + // Used for addressing parameters of set_statistics() + enum ePrintStatistics + { + psUsedMaterial = 0, + psNumFade, + psNumSlow, + psNumFast, + + psCnt + }; + +private: + + // A struct to bind the raster image data and its compressed bytes together. + struct Layer { + Raster raster; + RawBytes rawbytes; + + Layer() = default; + Layer(const Layer&) = delete; // The image is big, do not copy by accident + Layer& operator=(const Layer&) = delete; + + // ///////////////////////////////////////////////////////////////////// + // FIXME: the following is needed for MSVC2013 compatibility + // ///////////////////////////////////////////////////////////////////// + + // Layer(Layer&& m) = default; + // Layer& operator=(Layer&&) = default; + Layer(Layer &&m): + raster(std::move(m.raster)), rawbytes(std::move(m.rawbytes)) {} + Layer& operator=(Layer &&m) { + raster = std::move(m.raster); rawbytes = std::move(m.rawbytes); + return *this; + } + }; + + // We will save the compressed PNG data into RawBytes type buffers in + // parallel. Later we can write every layer to the disk sequentially. + std::vector m_layers_rst; + Raster::Resolution m_res; + Raster::PixelDim m_pxdim; + double m_exp_time_s = .0, m_exp_time_first_s = .0; + double m_layer_height = .0; + RasterOrientation m_o = roPortrait; + std::array m_mirror; + + double m_gamma; + + double m_used_material = 0.0; + int m_cnt_fade_layers = 0; + int m_cnt_slow_layers = 0; + int m_cnt_fast_layers = 0; + + std::string createIniContent(const std::string& projectname) const; + + static void flpXY(ClipperLib::Polygon& poly); + static void flpXY(ExPolygon& poly); + +public: + + SLARasterWriter(const SLAPrinterConfig& cfg, + const SLAMaterialConfig& mcfg, + double layer_height); + + SLARasterWriter(const SLARasterWriter& ) = delete; + SLARasterWriter& operator=(const SLARasterWriter&) = delete; + + // ///////////////////////////////////////////////////////////////////////// + // FIXME: the following is needed for MSVC2013 compatibility + // ///////////////////////////////////////////////////////////////////////// + + // SLARasterWriter(SLARasterWriter&& m) = default; + // SLARasterWriter& operator=(SLARasterWriter&&) = default; + SLARasterWriter(SLARasterWriter&& m): + m_layers_rst(std::move(m.m_layers_rst)), + m_res(m.m_res), + m_pxdim(m.m_pxdim), + m_exp_time_s(m.m_exp_time_s), + m_exp_time_first_s(m.m_exp_time_first_s), + m_layer_height(m.m_layer_height), + m_o(m.m_o), + m_mirror(std::move(m.m_mirror)), + m_gamma(m.m_gamma), + m_used_material(m.m_used_material), + m_cnt_fade_layers(m.m_cnt_fade_layers), + m_cnt_slow_layers(m.m_cnt_slow_layers), + m_cnt_fast_layers(m.m_cnt_fast_layers) + {} + + // ///////////////////////////////////////////////////////////////////////// + + inline void layers(unsigned cnt) { if(cnt > 0) m_layers_rst.resize(cnt); } + inline unsigned layers() const { return unsigned(m_layers_rst.size()); } + + template void draw_polygon(const Poly& p, unsigned lyr) { + assert(lyr < m_layers_rst.size()); + if(m_o == roPortrait) { + Poly poly(p); flpXY(poly); + m_layers_rst[lyr].raster.draw(poly); + } + else m_layers_rst[lyr].raster.draw(p); + } + + inline void begin_layer(unsigned lyr) { + if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1); + m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_mirror, m_gamma); + } + + inline void begin_layer() { + m_layers_rst.emplace_back(); + m_layers_rst.front().raster.reset(m_res, m_pxdim, m_mirror, m_gamma); + } + + inline void finish_layer(unsigned lyr_id) { + assert(lyr_id < m_layers_rst.size()); + m_layers_rst[lyr_id].rawbytes = + m_layers_rst[lyr_id].raster.save(Raster::Format::PNG); + m_layers_rst[lyr_id].raster.reset(); + } + + inline void finish_layer() { + if(!m_layers_rst.empty()) { + m_layers_rst.back().rawbytes = + m_layers_rst.back().raster.save(Raster::Format::PNG); + m_layers_rst.back().raster.reset(); + } + } + + void save(const std::string& fpath, const std::string& prjname = ""); + + void set_statistics(const std::vector statistics); +}; + +} // namespace sla +} // namespace Slic3r + +#endif // SLARASTERWRITER_HPP diff --git a/src/libslic3r/SLA/SLARotfinder.cpp b/src/libslic3r/SLA/SLARotfinder.cpp index 1a91041b78..2e64059d68 100644 --- a/src/libslic3r/SLA/SLARotfinder.cpp +++ b/src/libslic3r/SLA/SLARotfinder.cpp @@ -44,7 +44,7 @@ std::array find_best_rotation(const ModelObject& modelobj, // call the status callback in each iteration but the actual value may be // the same for subsequent iterations (status goes from 0 to 100 but // iterations can be many more) - auto objfunc = [&emesh, &status, &statuscb, max_tries] + auto objfunc = [&emesh, &status, &statuscb, &stopcond, max_tries] (double rx, double ry, double rz) { EigenMesh3D& m = emesh; @@ -91,7 +91,7 @@ std::array find_best_rotation(const ModelObject& modelobj, } // report status - statuscb( unsigned(++status * 100.0/max_tries) ); + if(!stopcond()) statuscb( unsigned(++status * 100.0/max_tries) ); return score; }; diff --git a/src/libslic3r/SLA/SLASpatIndex.hpp b/src/libslic3r/SLA/SLASpatIndex.hpp index e5fbfa7d4b..90dcdc3627 100644 --- a/src/libslic3r/SLA/SLASpatIndex.hpp +++ b/src/libslic3r/SLA/SLASpatIndex.hpp @@ -7,13 +7,15 @@ #include +#include + namespace Slic3r { namespace sla { typedef Eigen::Matrix Vec3d; -using SpatElement = std::pair; +using PointIndexEl = std::pair; -class SpatIndex { +class PointIndex { class Impl; // We use Pimpl because it takes a long time to compile boost headers which @@ -21,30 +23,67 @@ class SpatIndex { std::unique_ptr m_impl; public: - SpatIndex(); - ~SpatIndex(); + PointIndex(); + ~PointIndex(); - SpatIndex(const SpatIndex&); - SpatIndex(SpatIndex&&); - SpatIndex& operator=(const SpatIndex&); - SpatIndex& operator=(SpatIndex&&); + PointIndex(const PointIndex&); + PointIndex(PointIndex&&); + PointIndex& operator=(const PointIndex&); + PointIndex& operator=(PointIndex&&); - void insert(const SpatElement&); - bool remove(const SpatElement&); + void insert(const PointIndexEl&); + bool remove(const PointIndexEl&); inline void insert(const Vec3d& v, unsigned idx) { insert(std::make_pair(v, unsigned(idx))); } - std::vector query(std::function); - std::vector nearest(const Vec3d&, unsigned k); + std::vector query(std::function); + std::vector nearest(const Vec3d&, unsigned k); // For testing size_t size() const; bool empty() const { return size() == 0; } - void foreach(std::function fn); + void foreach(std::function fn); +}; + +using BoxIndexEl = std::pair; + +class BoxIndex { + class Impl; + + // We use Pimpl because it takes a long time to compile boost headers which + // is the engine of this class. We include it only in the cpp file. + std::unique_ptr m_impl; +public: + + BoxIndex(); + ~BoxIndex(); + + BoxIndex(const BoxIndex&); + BoxIndex(BoxIndex&&); + BoxIndex& operator=(const BoxIndex&); + BoxIndex& operator=(BoxIndex&&); + + void insert(const BoxIndexEl&); + inline void insert(const BoundingBox& bb, unsigned idx) + { + insert(std::make_pair(bb, unsigned(idx))); + } + + bool remove(const BoxIndexEl&); + + enum QueryType { qtIntersects, qtWithin }; + + std::vector query(const BoundingBox&, QueryType qt); + + // For testing + size_t size() const; + bool empty() const { return size() == 0; } + + void foreach(std::function fn); }; } diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index cb2001024d..5e0ac1c956 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -9,10 +9,12 @@ #include "SLASpatIndex.hpp" #include "SLABasePool.hpp" +#include #include #include #include +#include #include #include #include @@ -81,6 +83,42 @@ const unsigned SupportConfig::max_bridges_on_pillar = 3; using Coordf = double; using Portion = std::tuple; +// Set this to true to enable full parallelism in this module. +// Only the well tested parts will be concurrent if this is set to false. +const constexpr bool USE_FULL_CONCURRENCY = true; + +template struct _ccr {}; + +template<> struct _ccr +{ + using Mutex = SpinMutex; + + template + static inline void enumerate(It from, It to, Fn fn) + { + using TN = size_t; + auto iN = to - from; + TN N = iN < 0 ? 0 : TN(iN); + + tbb::parallel_for(TN(0), N, [from, fn](TN n) { fn(*(from + n), n); }); + } +}; + +template<> struct _ccr +{ + struct Mutex { inline void lock() {} inline void unlock() {} }; + + template + static inline void enumerate(It from, It to, Fn fn) + { + for (auto it = from; it != to; ++it) fn(*it, it - from); + } +}; + +using ccr = _ccr; +using ccr_seq = _ccr; +using ccr_par = _ccr; + inline Portion make_portion(double a, double b) { return std::make_tuple(a, b); } @@ -236,13 +274,13 @@ Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d sp = {0,0,0}) // According to the slicing algorithms, we need to aid them with generating // a watertight body. So we create a triangle fan for the upper and lower // ending of the cylinder to close the geometry. - points.emplace_back(jp); size_t ci = points.size() - 1; + points.emplace_back(jp); int ci = int(points.size() - 1); for(int i = 0; i < steps - 1; ++i) indices.emplace_back(i + offs + 1, i + offs, ci); indices.emplace_back(offs, steps + offs - 1, ci); - points.emplace_back(endp); ci = points.size() - 1; + points.emplace_back(endp); ci = int(points.size() - 1); for(int i = 0; i < steps - 1; ++i) indices.emplace_back(ci, i, i + 1); @@ -413,7 +451,7 @@ struct Pillar { assert(steps > 0); height = jp(Z) - endp(Z); - if(height > 0) { // Endpoint is below the starting point + if(height > EPSILON) { // Endpoint is below the starting point // We just create a bridge geometry with the pillar parameters and // move the data. @@ -528,6 +566,7 @@ struct CompactBridge { const Vec3d& ep, const Vec3d& n, double r, + bool endball = true, size_t steps = 45) { Vec3d startp = sp + r * n; @@ -542,11 +581,13 @@ struct CompactBridge { auto upperball = sphere(r, Portion{PI / 2 - fa, PI}, fa); for(auto& p : upperball.points) p += startp; - auto lowerball = sphere(r, Portion{0, PI/2 + 2*fa}, fa); - for(auto& p : lowerball.points) p += endp; + if(endball) { + auto lowerball = sphere(r, Portion{0, PI/2 + 2*fa}, fa); + for(auto& p : lowerball.points) p += endp; + mesh.merge(lowerball); + } mesh.merge(upperball); - mesh.merge(lowerball); } }; @@ -556,29 +597,123 @@ struct Pad { PoolConfig cfg; double zlevel = 0; - Pad() {} + Pad() = default; - Pad(const TriangleMesh& object_support_mesh, - const ExPolygons& baseplate, + Pad(const TriangleMesh& support_mesh, + const ExPolygons& modelbase, double ground_level, const PoolConfig& pcfg) : cfg(pcfg), zlevel(ground_level + - (sla::get_pad_fullheight(pcfg) - sla::get_pad_elevation(pcfg)) ) + sla::get_pad_fullheight(pcfg) - + sla::get_pad_elevation(pcfg)) { - ExPolygons basep; - cfg.throw_on_cancel(); + Polygons basep; + auto &thr = cfg.throw_on_cancel; - // The 0.1f is the layer height with which the mesh is sampled and then - // the layers are unified into one vector of polygons. - base_plate(object_support_mesh, basep, - float(cfg.min_wall_height_mm + cfg.min_wall_thickness_mm), - 0.1f, pcfg.throw_on_cancel); + thr(); - for(auto& bp : baseplate) basep.emplace_back(bp); + // Get a sample for the pad from the support mesh + { + ExPolygons platetmp; + + float zstart = float(zlevel); + float zend = zstart + float(get_pad_fullheight(pcfg) + EPSILON); + + base_plate(support_mesh, platetmp, grid(zstart, zend, 0.1f), thr); + + // We don't need no... holes control... + for (const ExPolygon &bp : platetmp) + basep.emplace_back(std::move(bp.contour)); + } + + if(pcfg.embed_object) { + + // If the zero elevation mode is ON, we need to process the model + // base silhouette. Create the offsetted version and punch the + // breaksticks across its perimeter. + + ExPolygons modelbase_offs = modelbase; + + if (pcfg.embed_object.object_gap_mm > 0.0) + modelbase_offs + = offset_ex(modelbase_offs, + float(scaled(pcfg.embed_object.object_gap_mm))); + + // Create a spatial index of the support silhouette polygons. + // This will be used to check for intersections with the model + // silhouette polygons. If there is no intersection, then a certain + // part of the pad is redundant as it does not host any supports. + BoxIndex bindex; + { + unsigned idx = 0; + for(auto &bp : basep) { + auto bb = bp.bounding_box(); + bb.offset(float(scaled(pcfg.min_wall_thickness_mm))); + bindex.insert(bb, idx++); + } + } + + ExPolygons concaveh = offset_ex( + concave_hull(basep, pcfg.max_merge_distance_mm, thr), + scaled(pcfg.min_wall_thickness_mm)); + + // Punching the breaksticks across the offsetted polygon perimeters + auto pad_stickholes = reserve_vector(modelbase.size()); + for(auto& poly : modelbase_offs) { + + bool overlap = false; + for (const ExPolygon &p : concaveh) + overlap = overlap || poly.overlaps(p); + + auto bb = poly.contour.bounding_box(); + bb.offset(scaled(pcfg.min_wall_thickness_mm)); + + std::vector qres = + bindex.query(bb, BoxIndex::qtIntersects); + + if (!qres.empty() || overlap) { + + // The model silhouette polygon 'poly' HAS an intersection + // with the support silhouettes. Include this polygon + // in the pad holes with the breaksticks and merge the + // original (offsetted) version with the rest of the pad + // base plate. + + basep.emplace_back(poly.contour); + + // The holes of 'poly' will become positive parts of the + // pad, so they has to be checked for intersections as well + // and erased if there is no intersection with the supports + auto it = poly.holes.begin(); + while(it != poly.holes.end()) { + if (bindex.query(it->bounding_box(), + BoxIndex::qtIntersects).empty()) + it = poly.holes.erase(it); + else + ++it; + } + + // Punch the breaksticks + sla::breakstick_holes( + poly, + pcfg.embed_object.object_gap_mm, // padding + pcfg.embed_object.stick_stride_mm, + pcfg.embed_object.stick_width_mm, + pcfg.embed_object.stick_penetration_mm); + + pad_stickholes.emplace_back(poly); + } + } + + create_base_pool(basep, tmesh, pad_stickholes, cfg); + } else { + for (const ExPolygon &bp : modelbase) basep.emplace_back(bp.contour); + create_base_pool(basep, tmesh, {}, cfg); + } - create_base_pool(basep, tmesh, cfg); tmesh.translate(0, 0, float(zlevel)); + if (!tmesh.empty()) tmesh.require_shared_vertices(); } bool empty() const { return tmesh.facets_count() == 0; } @@ -603,7 +738,7 @@ inline Vec2d to_vec2(const Vec3d& v3) { return {v3(X), v3(Y)}; } -bool operator==(const SpatElement& e1, const SpatElement& e2) { +bool operator==(const PointIndexEl& e1, const PointIndexEl& e2) { return e1.second == e2.second; } @@ -620,7 +755,7 @@ ClusteredPoints cluster(const PointSet& points, ClusteredPoints cluster( const std::vector& indices, std::function pointfn, - std::function predicate, + std::function predicate, unsigned max_points); // This class will hold the support tree meshes with some additional bookkeeping @@ -637,7 +772,10 @@ ClusteredPoints cluster( // The support pad is considered an auxiliary geometry and is not part of the // merged mesh. It can be retrieved using a dedicated method (pad()) class SLASupportTree::Impl { - std::map m_heads; + // For heads it is beneficial to use the same IDs as for the support points. + std::vector m_heads; + std::vector m_head_indices; + std::vector m_pillars; std::vector m_junctions; std::vector m_bridges; @@ -645,8 +783,13 @@ class SLASupportTree::Impl { Controller m_ctl; Pad m_pad; + + using Mutex = ccr::Mutex; + + mutable Mutex m_mutex; mutable TriangleMesh meshcache; mutable bool meshcache_valid = false; mutable double model_height = 0; // the full height of the model + public: double ground_level = 0; @@ -655,37 +798,49 @@ public: const Controller& ctl() const { return m_ctl; } - template Head& add_head(unsigned id, Args&&... args) { - auto el = m_heads.emplace(std::piecewise_construct, - std::forward_as_tuple(id), - std::forward_as_tuple(std::forward(args)...)); - el.first->second.id = id; + template Head& add_head(unsigned id, Args&&... args) + { + std::lock_guard lk(m_mutex); + m_heads.emplace_back(std::forward(args)...); + m_heads.back().id = id; + + if (id >= m_head_indices.size()) m_head_indices.resize(id + 1); + m_head_indices[id] = m_heads.size() - 1; + meshcache_valid = false; - return el.first->second; + return m_heads.back(); } - template Pillar& add_pillar(unsigned headid, Args&&... args) { - auto it = m_heads.find(headid); - assert(it != m_heads.end()); - Head& head = it->second; + template Pillar& add_pillar(unsigned headid, Args&&... args) + { + std::lock_guard lk(m_mutex); + + assert(headid < m_head_indices.size()); + Head &head = m_heads[m_head_indices[headid]]; + m_pillars.emplace_back(head, std::forward(args)...); Pillar& pillar = m_pillars.back(); pillar.id = long(m_pillars.size() - 1); head.pillar_id = pillar.id; pillar.start_junction_id = head.id; pillar.starts_from_head = true; + meshcache_valid = false; return m_pillars.back(); } - void increment_bridges(const Pillar& pillar) { + void increment_bridges(const Pillar& pillar) + { + std::lock_guard lk(m_mutex); assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()); if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()) m_pillars[size_t(pillar.id)].bridges++; } - void increment_links(const Pillar& pillar) { + void increment_links(const Pillar& pillar) + { + std::lock_guard lk(m_mutex); assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()); if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()) @@ -694,6 +849,7 @@ public: template Pillar& add_pillar(Args&&...args) { + std::lock_guard lk(m_mutex); m_pillars.emplace_back(std::forward(args)...); Pillar& pillar = m_pillars.back(); pillar.id = long(m_pillars.size() - 1); @@ -702,114 +858,125 @@ public: return m_pillars.back(); } - const Head& pillar_head(long pillar_id) const { + const Head& pillar_head(long pillar_id) const + { + std::lock_guard lk(m_mutex); assert(pillar_id >= 0 && pillar_id < long(m_pillars.size())); + const Pillar& p = m_pillars[size_t(pillar_id)]; assert(p.starts_from_head && p.start_junction_id >= 0); - auto it = m_heads.find(unsigned(p.start_junction_id)); - assert(it != m_heads.end()); - return it->second; + assert(size_t(p.start_junction_id) < m_head_indices.size()); + + return m_heads[m_head_indices[p.start_junction_id]]; } - const Pillar& head_pillar(unsigned headid) const { - auto it = m_heads.find(headid); - assert(it != m_heads.end()); - const Head& h = it->second; + const Pillar& head_pillar(unsigned headid) const + { + std::lock_guard lk(m_mutex); + assert(headid < m_head_indices.size()); + + const Head& h = m_heads[m_head_indices[headid]]; assert(h.pillar_id >= 0 && h.pillar_id < long(m_pillars.size())); - return pillar(h.pillar_id); + + return m_pillars[size_t(h.pillar_id)]; } - template const Junction& add_junction(Args&&... args) { + template const Junction& add_junction(Args&&... args) + { + std::lock_guard lk(m_mutex); m_junctions.emplace_back(std::forward(args)...); m_junctions.back().id = long(m_junctions.size() - 1); meshcache_valid = false; return m_junctions.back(); } - template const Bridge& add_bridge(Args&&... args) { + template const Bridge& add_bridge(Args&&... args) + { + std::lock_guard lk(m_mutex); m_bridges.emplace_back(std::forward(args)...); m_bridges.back().id = long(m_bridges.size() - 1); meshcache_valid = false; return m_bridges.back(); } - template - const CompactBridge& add_compact_bridge(Args&&...args) { + template const CompactBridge& add_compact_bridge(Args&&...args) + { + std::lock_guard lk(m_mutex); m_compact_bridges.emplace_back(std::forward(args)...); m_compact_bridges.back().id = long(m_compact_bridges.size() - 1); meshcache_valid = false; return m_compact_bridges.back(); } - const std::map& heads() const { return m_heads; } - Head& head(unsigned idx) { + Head &head(unsigned id) + { + std::lock_guard lk(m_mutex); + assert(id < m_head_indices.size()); + meshcache_valid = false; - auto it = m_heads.find(idx); - assert(it != m_heads.end()); - return it->second; - } - const std::vector& pillars() const { return m_pillars; } - const std::vector& bridges() const { return m_bridges; } - const std::vector& junctions() const { return m_junctions; } - const std::vector& compact_bridges() const { - return m_compact_bridges; + return m_heads[m_head_indices[id]]; } - template inline const Pillar& pillar(T id) const { - static_assert(std::is_integral::value, "Invalid index type"); + inline size_t pillarcount() const { + std::lock_guard lk(m_mutex); + return m_pillars.size(); + } + + template inline IntegerOnly pillar(T id) const + { + std::lock_guard lk(m_mutex); assert(id >= 0 && size_t(id) < m_pillars.size() && size_t(id) < std::numeric_limits::max()); + return m_pillars[size_t(id)]; } - const Pad& create_pad(const TriangleMesh& object_supports, - const ExPolygons& baseplate, - const PoolConfig& cfg) { - m_pad = Pad(object_supports, baseplate, ground_level, cfg); + const Pad &create_pad(const TriangleMesh &object_supports, + const ExPolygons & modelbase, + const PoolConfig & cfg) + { + m_pad = Pad(object_supports, modelbase, ground_level, cfg); return m_pad; } - void remove_pad() { - m_pad = Pad(); - } + void remove_pad() { m_pad = Pad(); } const Pad& pad() const { return m_pad; } // WITHOUT THE PAD!!! - const TriangleMesh& merged_mesh() const { - if(meshcache_valid) return meshcache; + const TriangleMesh &merged_mesh() const + { + if (meshcache_valid) return meshcache; Contour3D merged; - for(auto& headel : heads()) { - if(m_ctl.stopcondition()) break; - if(headel.second.is_valid()) - merged.merge(headel.second.mesh); + for (auto &head : m_heads) { + if (m_ctl.stopcondition()) break; + if (head.is_valid()) merged.merge(head.mesh); } - for(auto& stick : pillars()) { - if(m_ctl.stopcondition()) break; + for (auto &stick : m_pillars) { + if (m_ctl.stopcondition()) break; merged.merge(stick.mesh); merged.merge(stick.base); } - for(auto& j : junctions()) { - if(m_ctl.stopcondition()) break; + for (auto &j : m_junctions) { + if (m_ctl.stopcondition()) break; merged.merge(j.mesh); } - for(auto& cb : compact_bridges()) { - if(m_ctl.stopcondition()) break; + for (auto &cb : m_compact_bridges) { + if (m_ctl.stopcondition()) break; merged.merge(cb.mesh); } - for(auto& bs : bridges()) { - if(m_ctl.stopcondition()) break; + for (auto &bs : m_bridges) { + if (m_ctl.stopcondition()) break; merged.merge(bs.mesh); } - - if(m_ctl.stopcondition()) { + if (m_ctl.stopcondition()) { // In case of failure we have to return an empty mesh meshcache = TriangleMesh(); return meshcache; @@ -819,44 +986,46 @@ public: // The mesh will be passed by const-pointer to TriangleMeshSlicer, // which will need this. - meshcache.require_shared_vertices(); + if (!meshcache.empty()) meshcache.require_shared_vertices(); - // TODO: Is this necessary? - //meshcache.repair(); - - BoundingBoxf3&& bb = meshcache.bounding_box(); - model_height = bb.max(Z) - bb.min(Z); + BoundingBoxf3 &&bb = meshcache.bounding_box(); + model_height = bb.max(Z) - bb.min(Z); meshcache_valid = true; return meshcache; } // WITH THE PAD - double full_height() const { - if(merged_mesh().empty() && !pad().empty()) + double full_height() const + { + if (merged_mesh().empty() && !pad().empty()) return get_pad_fullheight(pad().cfg); double h = mesh_height(); - if(!pad().empty()) h += sla::get_pad_elevation(pad().cfg); + if (!pad().empty()) h += sla::get_pad_elevation(pad().cfg); return h; } // WITHOUT THE PAD!!! - double mesh_height() const { - if(!meshcache_valid) merged_mesh(); + double mesh_height() const + { + if (!meshcache_valid) merged_mesh(); return model_height; } - - // Intended to be called after the generation is fully complete - void clear_support_data() { - merged_mesh(); // in case the mesh is not generated, it should be... - m_heads.clear(); - m_pillars.clear(); - m_junctions.clear(); - m_bridges.clear(); - m_compact_bridges.clear(); - } + // Intended to be called after the generation is fully complete + void merge_and_cleanup() + { + merged_mesh(); // in case the mesh is not generated, it should be... + + // Doing clear() does not garantee to release the memory. + m_heads = {}; + m_head_indices = {}; + m_pillars = {}; + m_junctions = {}; + m_bridges = {}; + m_compact_bridges = {}; + } }; // This function returns the position of the centroid in the input 'clust' @@ -947,7 +1116,7 @@ class SLASupportTree::Algorithm { ThrowOnCancel m_thr; // A spatial index to easily find strong pillars to connect to. - SpatIndex m_pillar_index; + PointIndex m_pillar_index; inline double ray_mesh_intersect(const Vec3d& s, const Vec3d& dir) @@ -1025,11 +1194,10 @@ class SLASupportTree::Algorithm { // Now a and b vectors are perpendicular to v and to each other. // Together they define the plane where we have to iterate with the // given angles in the 'phis' vector - tbb::parallel_for(size_t(0), phis.size(), - [&phis, &hits, &m, sd, r_pin, r_back, s, a, b, c] - (size_t i) + ccr_seq::enumerate(phis.begin(), phis.end(), + [&hits, &m, sd, r_pin, r_back, s, a, b, c] + (double phi, size_t i) { - double& phi = phis[i]; double sinphi = std::sin(phi); double cosphi = std::cos(phi); @@ -1129,11 +1297,10 @@ class SLASupportTree::Algorithm { // Hit results std::array hits; - tbb::parallel_for(size_t(0), phis.size(), - [&m, &phis, a, b, sd, dir, r, s, ins_check, &hits] - (size_t i) + ccr_seq::enumerate(phis.begin(), phis.end(), + [&m, a, b, sd, dir, r, s, ins_check, &hits] + (double phi, size_t i) { - double& phi = phis[i]; double sinphi = std::sin(phi); double cosphi = std::cos(phi); @@ -1149,7 +1316,7 @@ class SLASupportTree::Algorithm { auto hr = m.query_ray_hit(p + sd*dir, dir); if(ins_check && hr.is_inside()) { - if(hr.distance() > r + sd) hits[i] = HitResult(0.0); + if(hr.distance() > 2 * r + sd) hits[i] = HitResult(0.0); else { // re-cast the ray from the outside of the object auto hr2 = @@ -1265,8 +1432,11 @@ class SLASupportTree::Algorithm { // For connecting a head to a nearby pillar. bool connect_to_nearpillar(const Head& head, long nearpillar_id) { - auto nearpillar = [this, nearpillar_id]() { return m_result.pillar(nearpillar_id); }; - if(nearpillar().bridges > m_cfg.max_bridges_on_pillar) return false; + auto nearpillar = [this, nearpillar_id]() { + return m_result.pillar(nearpillar_id); + }; + + if (nearpillar().bridges > m_cfg.max_bridges_on_pillar) return false; Vec3d headjp = head.junction_point(); Vec3d nearjp_u = nearpillar().startpoint(); @@ -1337,7 +1507,7 @@ class SLASupportTree::Algorithm { } bool search_pillar_and_connect(const Head& head) { - SpatIndex spindex = m_pillar_index; + PointIndex spindex = m_pillar_index; long nearest_id = -1; @@ -1358,7 +1528,7 @@ class SLASupportTree::Algorithm { if(nearest_id >= 0) { auto nearpillarID = unsigned(nearest_id); - if(nearpillarID < m_result.pillars().size()) { + if(nearpillarID < m_result.pillarcount()) { if(!connect_to_nearpillar(head, nearpillarID)) { nearest_id = -1; // continue searching spindex.remove(ne); // without the current pillar @@ -1370,6 +1540,120 @@ class SLASupportTree::Algorithm { return nearest_id >= 0; } + // This is a proxy function for pillar creation which will mind the gap + // between the pad and the model bottom in zero elevation mode. + void create_ground_pillar(const Vec3d &jp, + const Vec3d &sourcedir, + double radius, + int head_id = -1) + { + // People were killed for this number (seriously) + static const double SQR2 = std::sqrt(2.0); + static const Vec3d DOWN = {0.0, 0.0, -1.0}; + + double gndlvl = m_result.ground_level; + Vec3d endp = {jp(X), jp(Y), gndlvl}; + double sd = m_cfg.pillar_base_safety_distance_mm; + int pillar_id = -1; + double min_dist = sd + m_cfg.base_radius_mm + EPSILON; + double dist = 0; + bool can_add_base = true; + bool normal_mode = true; + + if (m_cfg.object_elevation_mm < EPSILON + && (dist = std::sqrt(m_mesh.squared_distance(endp))) < min_dist) { + // Get the distance from the mesh. This can be later optimized + // to get the distance in 2D plane because we are dealing with + // the ground level only. + + normal_mode = false; + double mv = min_dist - dist; + double azimuth = std::atan2(sourcedir(Y), sourcedir(X)); + double sinpolar = std::sin(PI - m_cfg.bridge_slope); + double cospolar = std::cos(PI - m_cfg.bridge_slope); + double cosazm = std::cos(azimuth); + double sinazm = std::sin(azimuth); + + auto dir = Vec3d(cosazm * sinpolar, sinazm * sinpolar, cospolar) + .normalized(); + + using namespace libnest2d::opt; + StopCriteria scr; + scr.stop_score = min_dist; + SubplexOptimizer solver(scr); + + auto result = solver.optimize_max( + [this, dir, jp, gndlvl](double mv) { + Vec3d endp = jp + SQR2 * mv * dir; + endp(Z) = gndlvl; + return std::sqrt(m_mesh.squared_distance(endp)); + }, + initvals(mv), bound(0.0, 2 * min_dist)); + + mv = std::get<0>(result.optimum); + endp = jp + SQR2 * mv * dir; + Vec3d pgnd = {endp(X), endp(Y), gndlvl}; + can_add_base = result.score > min_dist; + + double gnd_offs = m_mesh.ground_level_offset(); + auto abort_in_shame = + [gnd_offs, &normal_mode, &can_add_base, &endp, jp, gndlvl]() + { + normal_mode = true; + can_add_base = false; // Nothing left to do, hope for the best + endp = {jp(X), jp(Y), gndlvl - gnd_offs }; + }; + + // We have to check if the bridge is feasible. + if (bridge_mesh_intersect(jp, dir, radius) < (endp - jp).norm()) + abort_in_shame(); + else { + // If the new endpoint is below ground, do not make a pillar + if (endp(Z) < gndlvl) + endp = endp - SQR2 * (gndlvl - endp(Z)) * dir; // back off + else { + + auto hit = bridge_mesh_intersect(endp, DOWN, radius); + if (!std::isinf(hit.distance())) abort_in_shame(); + + Pillar &plr = m_result.add_pillar(endp, pgnd, radius); + + if (can_add_base) + plr.add_base(m_cfg.base_height_mm, + m_cfg.base_radius_mm); + + pillar_id = plr.id; + } + + m_result.add_bridge(jp, endp, radius); + m_result.add_junction(endp, radius); + + // Add a degenerated pillar and the bridge. + // The degenerate pillar will have zero length and it will + // prevent from queries of head_pillar() to have non-existing + // pillar when the head should have one. + if (head_id >= 0) + m_result.add_pillar(unsigned(head_id), jp, radius); + } + } + + if (normal_mode) { + Pillar &plr = head_id >= 0 + ? m_result.add_pillar(unsigned(head_id), + endp, + radius) + : m_result.add_pillar(jp, endp, radius); + + if (can_add_base) + plr.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); + + pillar_id = plr.id; + } + + if(pillar_id >= 0) // Save the pillar endpoint in the spatial index + m_pillar_index.insert(endp, pillar_id); + } + public: Algorithm(const SupportConfig& config, @@ -1433,11 +1717,17 @@ public: using libnest2d::opt::GeneticOptimizer; using libnest2d::opt::StopCriteria; - for(unsigned i = 0, fidx = 0; i < filtered_indices.size(); ++i) + ccr::Mutex mutex; + auto addfn = [&mutex](PtIndices &container, unsigned val) { + std::lock_guard lk(mutex); + container.emplace_back(val); + }; + + ccr::enumerate(filtered_indices.begin(), filtered_indices.end(), + [this, &nmls, addfn](unsigned fidx, size_t i) { m_thr(); - fidx = filtered_indices[i]; auto n = nmls.row(i); // for all normals we generate the spherical coordinates and @@ -1447,9 +1737,9 @@ public: // (Quaternion::FromTwoVectors) and apply the rotation to the // arrow head. - double z = n(2); - double r = 1.0; // for normalized vector - double polar = std::acos(z / r); + double z = n(2); + double r = 1.0; // for normalized vector + double polar = std::acos(z / r); double azimuth = std::atan2(n(1), n(0)); // skip if the tilt is not sane @@ -1473,14 +1763,14 @@ public: std::cos(polar)).normalized(); // check available distance - double t = pinhead_mesh_intersect( - hp, // touching point - nn, // normal - pin_r, - m_cfg.head_back_radius_mm, - w); + EigenMesh3D::hit_result t + = pinhead_mesh_intersect(hp, // touching point + nn, // normal + pin_r, + m_cfg.head_back_radius_mm, + w); - if(t <= w) { + if(t.distance() <= w) { // Let's try to optimize this angle, there might be a // viable normal that doesn't collide with the model @@ -1495,20 +1785,20 @@ public: auto oresult = solver.optimize_max( [this, pin_r, w, hp](double plr, double azm) - { - auto n = Vec3d(std::cos(azm) * std::sin(plr), - std::sin(azm) * std::sin(plr), - std::cos(plr)).normalized(); + { + auto n = Vec3d(std::cos(azm) * std::sin(plr), + std::sin(azm) * std::sin(plr), + std::cos(plr)).normalized(); - double score = pinhead_mesh_intersect( hp, n, pin_r, - m_cfg.head_back_radius_mm, w); + double score = pinhead_mesh_intersect( + hp, n, pin_r, m_cfg.head_back_radius_mm, w); - return score; - }, - initvals(polar, azimuth), // start with what we have - bound(3*PI/4, PI), // Must not exceed the tilt limit - bound(-PI, PI) // azimuth can be a full search - ); + return score; + }, + initvals(polar, azimuth), // start with what we have + bound(3*PI/4, PI), // Must not exceed the tilt limit + bound(-PI, PI) // azimuth can be a full search + ); if(oresult.score > w) { polar = std::get<0>(oresult.optimum); @@ -1523,16 +1813,21 @@ public: // save the verified and corrected normal m_support_nmls.row(fidx) = nn; - if(t > w) { - // mark the point for needing a head. - m_iheads.emplace_back(fidx); - } else if( polar >= 3*PI/4 ) { - // Headless supports do not tilt like the headed ones so - // the normal should point almost to the ground. - m_iheadless.emplace_back(fidx); + if (t.distance() > w) { + // Check distance from ground, we might have zero elevation. + if (hp(Z) + w * nn(Z) < m_result.ground_level) { + addfn(m_iheadless, fidx); + } else { + // mark the point for needing a head. + addfn(m_iheads, fidx); + } + } else if (polar >= 3 * PI / 4) { + // Headless supports do not tilt like the headed ones + // so the normal should point almost to the ground. + addfn(m_iheadless, fidx); } } - } + }); m_thr(); } @@ -1594,16 +1889,22 @@ public: // from each other in the XY plane to not cross their pillar bases // These clusters of support points will join in one pillar, // possibly in their centroid support point. + auto pointfn = [this](unsigned i) { return m_result.head(i).junction_point(); }; - auto predicate = [this](const SpatElement& e1, const SpatElement& e2) { + + auto predicate = [this](const PointIndexEl &e1, + const PointIndexEl &e2) { double d2d = distance(to_2d(e1.first), to_2d(e2.first)); double d3d = distance(e1.first, e2.first); - return d2d < 2 * m_cfg.base_radius_mm && - d3d < m_cfg.max_bridge_length_mm; + return d2d < 2 * m_cfg.base_radius_mm + && d3d < m_cfg.max_bridge_length_mm; }; - m_pillar_clusters = cluster(ground_head_indices, pointfn, predicate, + + m_pillar_clusters = cluster(ground_head_indices, + pointfn, + predicate, m_cfg.max_bridges_on_pillar); } @@ -1615,7 +1916,7 @@ public: void routing_to_ground() { const double pradius = m_cfg.head_back_radius_mm; - const double gndlvl = m_result.ground_level; + // const double gndlvl = m_result.ground_level; ClusterEl cl_centroids; cl_centroids.reserve(m_pillar_clusters.size()); @@ -1648,13 +1949,8 @@ public: Head& h = m_result.head(hid); h.transform(); - Vec3d p = h.junction_point(); p(Z) = gndlvl; - auto& plr = m_result.add_pillar(hid, p, h.r_back_mm) - .add_base(m_cfg.base_height_mm, - m_cfg.base_radius_mm); - // Save the pillar endpoint and the pillar id in the spatial index - m_pillar_index.insert(plr.endpoint(), unsigned(plr.id)); + create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id); } // now we will go through the clusters ones again and connect the @@ -1681,15 +1977,12 @@ public: !search_pillar_and_connect(sidehead)) { Vec3d pstart = sidehead.junction_point(); - Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl}; + //Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl}; // Could not find a pillar, create one - auto& pillar = m_result.add_pillar(unsigned(sidehead.id), - pend, pradius) - .add_base(m_cfg.base_height_mm, - m_cfg.base_radius_mm); - - // connects to ground, eligible for bridging - m_pillar_index.insert(pend, unsigned(pillar.id)); + create_ground_pillar(pstart, + sidehead.dir, + pradius, + sidehead.id); } } } @@ -1718,20 +2011,21 @@ public: m_result.add_bridge(hjp, endp, head.r_back_mm); m_result.add_junction(endp, head.r_back_mm); - auto groundp = endp; - groundp(Z) = m_result.ground_level; - auto& newpillar = m_result.add_pillar(endp, groundp, head.r_back_mm) - .add_base(m_cfg.base_height_mm, - m_cfg.base_radius_mm); - m_pillar_index.insert(groundp, unsigned(newpillar.id)); + this->create_ground_pillar(endp, dir, head.r_back_mm); }; std::vector modelpillars; + ccr::Mutex mutex; // TODO: connect these to the ground pillars if possible - for(auto item : m_iheads_onmodel) { m_thr(); - unsigned idx = item.first; - EigenMesh3D::hit_result hit = item.second; + ccr::enumerate(m_iheads_onmodel.begin(), m_iheads_onmodel.end(), + [this, routedown, &modelpillars, &mutex] + (const std::pair &el, + size_t) + { + m_thr(); + unsigned idx = el.first; + EigenMesh3D::hit_result hit = el.second; auto& head = m_result.head(idx); Vec3d hjp = head.junction_point(); @@ -1740,7 +2034,7 @@ public: // Search nearby pillar // ///////////////////////////////////////////////////////////////// - if(search_pillar_and_connect(head)) { head.transform(); continue; } + if(search_pillar_and_connect(head)) { head.transform(); return; } // ///////////////////////////////////////////////////////////////// // Try straight path @@ -1762,7 +2056,7 @@ public: } if(std::isinf(tdown)) { // we heave found a route to the ground - routedown(head, head.dir, d); continue; + routedown(head, head.dir, d); return; } // ///////////////////////////////////////////////////////////////// @@ -1824,7 +2118,7 @@ public: } if(std::isinf(tdown)) { // we heave found a route to the ground - routedown(head, bridgedir, d); continue; + routedown(head, bridgedir, d); return; } // ///////////////////////////////////////////////////////////////// @@ -1867,8 +2161,9 @@ public: pill.base = tailhead.mesh; // Experimental: add the pillar to the index for cascading + std::lock_guard lk(mutex); modelpillars.emplace_back(unsigned(pill.id)); - continue; + return; } // We have failed to route this head. @@ -1876,7 +2171,7 @@ public: << "Failed to route model facing support point." << " ID: " << idx; head.invalidate(); - } + }); for(auto pillid : modelpillars) { auto& pillar = m_result.pillar(pillid); @@ -1884,6 +2179,28 @@ public: } } + // Helper function for interconnect_pillars where pairs of already connected + // pillars should be checked for not to be processed again. This can be done + // in O(log) or even constant time with a set or an unordered set of hash + // values uniquely representing a pair of integers. The order of numbers + // within the pair should not matter, it has the same unique hash. + template static I pairhash(I a, I b) + { + using std::ceil; using std::log2; using std::max; using std::min; + + static_assert(std::is_integral::value, + "This function works only for integral types."); + + I g = min(a, b), l = max(a, b); + + auto bits_g = g ? int(ceil(log2(g))) : 0; + + // Assume the hash will fit into the output variable + assert((l ? (ceil(log2(l))) : 0) + bits_g < int(sizeof(I) * CHAR_BIT)); + + return (l << bits_g) + g; + } + void interconnect_pillars() { // Now comes the algorithm that connects pillars with each other. // Ideally every pillar should be connected with at least one of its @@ -1901,44 +2218,50 @@ public: std::set pairs; + // A function to connect one pillar with its neighbors. THe number of + // neighbors is given in the configuration. This function if called + // for every pillar in the pillar index. A pair of pillar will not + // be connected multiple times this is ensured by the 'pairs' set which + // remembers the processed pillar pairs auto cascadefn = - [this, d, &pairs, min_height_ratio, H1] (const SpatElement& el) + [this, d, &pairs, min_height_ratio, H1] (const PointIndexEl& el) { - Vec3d qp = el.first; + Vec3d qp = el.first; // endpoint of the pillar - const Pillar& pillar = m_result.pillar(el.second); + const Pillar& pillar = m_result.pillar(el.second); // actual pillar + // Get the max number of neighbors a pillar should connect to unsigned neighbors = m_cfg.pillar_cascade_neighbors; - // connections are enough for one pillar + // connections are already enough for the pillar if(pillar.links >= neighbors) return; // Query all remaining points within reach - auto qres = m_pillar_index.query([qp, d](const SpatElement& e){ + auto qres = m_pillar_index.query([qp, d](const PointIndexEl& e){ return distance(e.first, qp) < d; }); // sort the result by distance (have to check if this is needed) std::sort(qres.begin(), qres.end(), - [qp](const SpatElement& e1, const SpatElement& e2){ + [qp](const PointIndexEl& e1, const PointIndexEl& e2){ return distance(e1.first, qp) < distance(e2.first, qp); }); - for(auto& re : qres) { + for(auto& re : qres) { // process the queried neighbors - if(re.second == el.second) continue; + if(re.second == el.second) continue; // Skip self auto a = el.second, b = re.second; - // I hope that the area of a square is never equal to its - // circumference - auto hashval = 2 * (a + b) + a * b; + // Get unique hash for the given pair (order doesn't matter) + auto hashval = pairhash(a, b); + // Search for the pair amongst the remembered pairs if(pairs.find(hashval) != pairs.end()) continue; - const Pillar& neighborpillar = m_result.pillars()[re.second]; + const Pillar& neighborpillar = m_result.pillar(re.second); - // this neighbor is occupied + // this neighbor is occupied, skip if(neighborpillar.links >= neighbors) continue; if(interconnect(pillar, neighborpillar)) { @@ -1961,46 +2284,78 @@ public: } }; + // Run the cascade for the pillars in the index m_pillar_index.foreach(cascadefn); - size_t pillarcount = m_result.pillars().size(); + // We would be done here if we could allow some pillars to not be + // connected with any neighbors. But this might leave the support tree + // unprintable. + // + // The current solution is to insert additional pillars next to these + // lonely pillars. One or even two additional pillar might get inserted + // depending on the length of the lonely pillar. + size_t pillarcount = m_result.pillarcount(); + + // Again, go through all pillars, this time in the whole support tree + // not just the index. for(size_t pid = 0; pid < pillarcount; pid++) { auto pillar = [this, pid]() { return m_result.pillar(pid); }; + // Decide how many additional pillars will be needed: + unsigned needpillars = 0; - if(pillar().bridges > m_cfg.max_bridges_on_pillar) needpillars = 3; - else if(pillar().links < 2 && pillar().height > H2) { + if (pillar().bridges > m_cfg.max_bridges_on_pillar) + needpillars = 3; + else if (pillar().links < 2 && pillar().height > H2) { // Not enough neighbors to support this pillar needpillars = 2 - pillar().links; - } - else if(pillar().links < 1 && pillar().height > H1) { + } else if (pillar().links < 1 && pillar().height > H1) { // No neighbors could be found and the pillar is too long. needpillars = 1; } - // Search for new pillar locations - bool found = false; - double alpha = 0; // goes to 2Pi - double r = 2 * m_cfg.base_radius_mm; - Vec3d pillarsp = pillar().startpoint(); + // Search for new pillar locations: + + bool found = false; + double alpha = 0; // goes to 2Pi + double r = 2 * m_cfg.base_radius_mm; + Vec3d pillarsp = pillar().startpoint(); + + // temp value for starting point detection Vec3d sp(pillarsp(X), pillarsp(Y), pillarsp(Z) - r); - std::vector tv(needpillars, false); - std::vector spts(needpillars); + + // A vector of bool for placement feasbility + std::vector canplace(needpillars, false); + std::vector spts(needpillars); // vector of starting points + + double gnd = m_result.ground_level; + double min_dist = m_cfg.pillar_base_safety_distance_mm + + m_cfg.base_radius_mm + EPSILON; while(!found && alpha < 2*PI) { - - for(unsigned n = 0; n < needpillars; n++) { - double a = alpha + n * PI/3; - Vec3d s = sp; + for (unsigned n = 0; + n < needpillars && (!n || canplace[n - 1]); + n++) + { + double a = alpha + n * PI / 3; + Vec3d s = sp; s(X) += std::cos(a) * r; s(Y) += std::sin(a) * r; spts[n] = s; + + // Check the path vertically down auto hr = bridge_mesh_intersect(s, {0, 0, -1}, pillar().r); - tv[n] = std::isinf(hr.distance()); + Vec3d gndsp{s(X), s(Y), gnd}; + + // If the path is clear, check for pillar base collisions + canplace[n] = std::isinf(hr.distance()) && + std::sqrt(m_mesh.squared_distance(gndsp)) > + min_dist; } - found = std::all_of(tv.begin(), tv.end(), [](bool v){return v;}); + found = std::all_of(canplace.begin(), canplace.end(), + [](bool v) { return v; }); // 20 angles will be tried... alpha += 0.1 * PI; @@ -2010,7 +2365,7 @@ public: newpills.reserve(needpillars); if(found) for(unsigned n = 0; n < needpillars; n++) { - Vec3d s = spts[n]; double gnd = m_result.ground_level; + Vec3d s = spts[n]; Pillar p(s, Vec3d(s(X), s(Y), gnd), pillar().r); p.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); @@ -2075,9 +2430,13 @@ public: // This is only for checking double idist = bridge_mesh_intersect(sph, dir, R, true); double dist = ray_mesh_intersect(sj, dir); + if (std::isinf(dist)) + dist = sph(Z) - m_mesh.ground_level() + + m_mesh.ground_level_offset(); - if(std::isinf(idist) || std::isnan(idist) || idist < 2*R || - std::isinf(dist) || std::isnan(dist) || dist < 2*R) { + if(std::isnan(idist) || idist < 2*R || + std::isnan(dist) || dist < 2*R) + { BOOST_LOG_TRIVIAL(warning) << "Can not find route for headless" << " support stick at: " << sj.transpose(); @@ -2085,9 +2444,11 @@ public: } Vec3d ej = sj + (dist + HWIDTH_MM)* dir; - m_result.add_compact_bridge(sp, ej, n, R); + m_result.add_compact_bridge(sp, ej, n, R, !std::isinf(dist)); } } + + void merge_result() { m_result.merge_and_cleanup(); } }; bool SLASupportTree::generate(const std::vector &support_points, @@ -2110,6 +2471,7 @@ bool SLASupportTree::generate(const std::vector &support_points, ROUTING_NONGROUND, CASCADE_PILLARS, HEADLESS, + MERGE_RESULT, DONE, ABORT, NUM_STEPS @@ -2137,6 +2499,8 @@ bool SLASupportTree::generate(const std::vector &support_points, std::bind(&Algorithm::routing_headless, &alg), + std::bind(&Algorithm::merge_result, &alg), + [] () { // Done }, @@ -2151,7 +2515,7 @@ bool SLASupportTree::generate(const std::vector &support_points, if(cfg.ground_facing_only) { program[ROUTING_NONGROUND] = []() { BOOST_LOG_TRIVIAL(info) - << "Skipping model-facing supports as requested."; + << "Skipping model-facing supports as requested."; }; program[HEADLESS] = []() { BOOST_LOG_TRIVIAL(info) << "Skipping headless stick generation as" @@ -2170,6 +2534,7 @@ bool SLASupportTree::generate(const std::vector &support_points, "Routing supports to model surface", "Interconnecting pillars", "Processing small holes", + "Merging support mesh", "Done", "Abort" }; @@ -2182,7 +2547,8 @@ bool SLASupportTree::generate(const std::vector &support_points, 60, 70, 80, - 90, + 85, + 99, 100, 0 }; @@ -2197,11 +2563,13 @@ bool SLASupportTree::generate(const std::vector &support_points, case ROUTING_GROUND: pc = ROUTING_NONGROUND; break; case ROUTING_NONGROUND: pc = CASCADE_PILLARS; break; case CASCADE_PILLARS: pc = HEADLESS; break; - case HEADLESS: pc = DONE; break; + case HEADLESS: pc = MERGE_RESULT; break; + case MERGE_RESULT: pc = DONE; break; case DONE: case ABORT: break; default: ; } + ctl.statuscb(stepstate[pc], stepstr[pc]); }; @@ -2214,11 +2582,13 @@ bool SLASupportTree::generate(const std::vector &support_points, return pc == ABORT; } -SLASupportTree::SLASupportTree(): m_impl(new Impl()) {} +SLASupportTree::SLASupportTree(double gnd_lvl): m_impl(new Impl()) { + m_impl->ground_level = gnd_lvl; +} const TriangleMesh &SLASupportTree::merged_mesh() const { - return get().merged_mesh(); + return m_impl->merged_mesh(); } void SLASupportTree::merged_mesh_with_pad(TriangleMesh &outmesh) const { @@ -2226,55 +2596,58 @@ void SLASupportTree::merged_mesh_with_pad(TriangleMesh &outmesh) const { outmesh.merge(get_pad()); } -SlicedSupports SLASupportTree::slice(float layerh, float init_layerh) const +std::vector SLASupportTree::slice( + const std::vector &grid, float cr) const { - if(init_layerh < 0) init_layerh = layerh; - auto& stree = get(); + const TriangleMesh &sup_mesh = m_impl->merged_mesh(); + const TriangleMesh &pad_mesh = get_pad(); - const auto modelh = float(stree.full_height()); - auto gndlvl = float(this->m_impl->ground_level); - const Pad& pad = m_impl->pad(); - if(!pad.empty()) gndlvl -= float(get_pad_elevation(pad.cfg)); + using Slices = std::vector; + auto slices = reserve_vector(2); - std::vector heights; - heights.reserve(size_t(modelh/layerh) + 1); + if (!sup_mesh.empty()) { + slices.emplace_back(); - for(float h = gndlvl + init_layerh; h < gndlvl + modelh; h += layerh) { - heights.emplace_back(h); + TriangleMeshSlicer sup_slicer(&sup_mesh); + sup_slicer.slice(grid, cr, &slices.back(), m_impl->ctl().cancelfn); } - TriangleMesh fullmesh = m_impl->merged_mesh(); - fullmesh.merge(get_pad()); - fullmesh.require_shared_vertices(); // TriangleMeshSlicer needs this - TriangleMeshSlicer slicer(&fullmesh); - SlicedSupports ret; - slicer.slice(heights, 0.f, &ret, get().ctl().cancelfn); + if (!pad_mesh.empty()) { + slices.emplace_back(); - return ret; + auto bb = pad_mesh.bounding_box(); + auto maxzit = std::upper_bound(grid.begin(), grid.end(), bb.max.z()); + + auto padgrid = reserve_vector(grid.end() - maxzit); + std::copy(grid.begin(), maxzit, std::back_inserter(padgrid)); + + TriangleMeshSlicer pad_slicer(&pad_mesh); + pad_slicer.slice(padgrid, cr, &slices.back(), m_impl->ctl().cancelfn); + } + + size_t len = grid.size(); + for (const Slices &slv : slices) { len = std::min(len, slv.size()); } + + // Either the support or the pad or both has to be non empty + if (slices.empty()) return {}; + + Slices &mrg = slices.front(); + + for (auto it = std::next(slices.begin()); it != slices.end(); ++it) { + for (size_t i = 0; i < len; ++i) { + Slices &slv = *it; + std::copy(slv[i].begin(), slv[i].end(), std::back_inserter(mrg[i])); + slv[i] = {}; // clear and delete + } + } + + return mrg; } -SlicedSupports SLASupportTree::slice(const std::vector &heights, - float cr) const -{ - TriangleMesh fullmesh = m_impl->merged_mesh(); - fullmesh.merge(get_pad()); - fullmesh.require_shared_vertices(); // TriangleMeshSlicer needs this - TriangleMeshSlicer slicer(&fullmesh); - SlicedSupports ret; - slicer.slice(heights, cr, &ret, get().ctl().cancelfn); - - return ret; -} - -const TriangleMesh &SLASupportTree::add_pad(const SliceLayer& baseplate, +const TriangleMesh &SLASupportTree::add_pad(const ExPolygons& modelbase, const PoolConfig& pcfg) const { -// PoolConfig pcfg; -// pcfg.min_wall_thickness_mm = min_wall_thickness_mm; -// pcfg.min_wall_height_mm = min_wall_height_mm; -// pcfg.max_merge_distance_mm = max_merge_distance_mm; -// pcfg.edge_radius_mm = edge_radius_mm; - return m_impl->create_pad(merged_mesh(), baseplate, pcfg).tmesh; + return m_impl->create_pad(merged_mesh(), modelbase, pcfg).tmesh; } const TriangleMesh &SLASupportTree::get_pad() const @@ -2295,16 +2668,6 @@ SLASupportTree::SLASupportTree(const std::vector &points, { m_impl->ground_level = emesh.ground_level() - cfg.object_elevation_mm; generate(points, emesh, cfg, ctl); - m_impl->clear_support_data(); -} - -SLASupportTree::SLASupportTree(const SLASupportTree &c): - m_impl(new Impl(*c.m_impl)) {} - -SLASupportTree &SLASupportTree::operator=(const SLASupportTree &c) -{ - m_impl = make_unique(*c.m_impl); - return *this; } SLASupportTree::~SLASupportTree() {} diff --git a/src/libslic3r/SLA/SLASupportTree.hpp b/src/libslic3r/SLA/SLASupportTree.hpp index 66677e4d7a..d7f15c17bd 100644 --- a/src/libslic3r/SLA/SLASupportTree.hpp +++ b/src/libslic3r/SLA/SLASupportTree.hpp @@ -24,10 +24,11 @@ class TriangleMesh; class Model; class ModelInstance; class ModelObject; +class Polygon; class ExPolygon; -using SliceLayer = std::vector; -using SlicedSupports = std::vector; +using Polygons = std::vector; +using ExPolygons = std::vector; namespace sla { @@ -80,6 +81,10 @@ struct SupportConfig { // The elevation in Z direction upwards. This is the space between the pad // and the model object's bounding box bottom. double object_elevation_mm = 10; + + // The shortest distance between a pillar base perimeter from the model + // body. This is only useful when elevation is set to zero. + double pillar_base_safety_distance_mm = 0.5; // ///////////////////////////////////////////////////////////////////////// // Compile time configuration values (candidates for runtime) @@ -160,15 +165,15 @@ class SLASupportTree { public: - SLASupportTree(); + SLASupportTree(double ground_level = 0.0); SLASupportTree(const std::vector& pts, const EigenMesh3D& em, const SupportConfig& cfg = {}, const Controller& ctl = {}); - - SLASupportTree(const SLASupportTree&); - SLASupportTree& operator=(const SLASupportTree&); + + SLASupportTree(const SLASupportTree&) = delete; + SLASupportTree& operator=(const SLASupportTree&) = delete; ~SLASupportTree(); @@ -178,13 +183,15 @@ public: void merged_mesh_with_pad(TriangleMesh&) const; - /// Get the sliced 2d layers of the support geometry. - SlicedSupports slice(float layerh, float init_layerh = -1.0) const; - - SlicedSupports slice(const std::vector&, float closing_radius) const; + std::vector slice(const std::vector &, + float closing_radius) const; /// Adding the "pad" (base pool) under the supports - const TriangleMesh& add_pad(const SliceLayer& baseplate, + /// modelbase will be used according to the embed_object flag in PoolConfig. + /// If set, the plate will interpreted as the model's intrinsic pad. + /// Otherwise, the modelbase will be unified with the base plate calculated + /// from the supports. + const TriangleMesh& add_pad(const ExPolygons& modelbase, const PoolConfig& pcfg) const; /// Get the pad geometry diff --git a/src/libslic3r/SLA/SLASupportTreeIGL.cpp b/src/libslic3r/SLA/SLASupportTreeIGL.cpp index c368b8604d..95d4512710 100644 --- a/src/libslic3r/SLA/SLASupportTreeIGL.cpp +++ b/src/libslic3r/SLA/SLASupportTreeIGL.cpp @@ -12,10 +12,18 @@ #include "SLABoostAdapter.hpp" #include "boost/geometry/index/rtree.hpp" +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4244) +#pragma warning(disable: 4267) +#endif #include #include #include #include +#ifdef _MSC_VER +#pragma warning(pop) +#endif #include @@ -29,69 +37,137 @@ namespace sla { using igl::PI; /* ************************************************************************** - * SpatIndex implementation + * PointIndex implementation * ************************************************************************** */ -class SpatIndex::Impl { +class PointIndex::Impl { public: - using BoostIndex = boost::geometry::index::rtree< SpatElement, + using BoostIndex = boost::geometry::index::rtree< PointIndexEl, boost::geometry::index::rstar<16, 4> /* ? */ >; BoostIndex m_store; }; -SpatIndex::SpatIndex(): m_impl(new Impl()) {} -SpatIndex::~SpatIndex() {} +PointIndex::PointIndex(): m_impl(new Impl()) {} +PointIndex::~PointIndex() {} -SpatIndex::SpatIndex(const SpatIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} -SpatIndex::SpatIndex(SpatIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} +PointIndex::PointIndex(const PointIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} +PointIndex::PointIndex(PointIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} -SpatIndex& SpatIndex::operator=(const SpatIndex &cpy) +PointIndex& PointIndex::operator=(const PointIndex &cpy) { m_impl.reset(new Impl(*cpy.m_impl)); return *this; } -SpatIndex& SpatIndex::operator=(SpatIndex &&cpy) +PointIndex& PointIndex::operator=(PointIndex &&cpy) { m_impl.swap(cpy.m_impl); return *this; } -void SpatIndex::insert(const SpatElement &el) +void PointIndex::insert(const PointIndexEl &el) { m_impl->m_store.insert(el); } -bool SpatIndex::remove(const SpatElement& el) +bool PointIndex::remove(const PointIndexEl& el) { return m_impl->m_store.remove(el) == 1; } -std::vector -SpatIndex::query(std::function fn) +std::vector +PointIndex::query(std::function fn) { namespace bgi = boost::geometry::index; - std::vector ret; + std::vector ret; m_impl->m_store.query(bgi::satisfies(fn), std::back_inserter(ret)); return ret; } -std::vector SpatIndex::nearest(const Vec3d &el, unsigned k = 1) +std::vector PointIndex::nearest(const Vec3d &el, unsigned k = 1) { namespace bgi = boost::geometry::index; - std::vector ret; ret.reserve(k); + std::vector ret; ret.reserve(k); m_impl->m_store.query(bgi::nearest(el, k), std::back_inserter(ret)); return ret; } -size_t SpatIndex::size() const +size_t PointIndex::size() const { return m_impl->m_store.size(); } -void SpatIndex::foreach(std::function fn) +void PointIndex::foreach(std::function fn) +{ + for(auto& el : m_impl->m_store) fn(el); +} + +/* ************************************************************************** + * BoxIndex implementation + * ************************************************************************** */ + +class BoxIndex::Impl { +public: + using BoostIndex = boost::geometry::index:: + rtree /* ? */>; + + BoostIndex m_store; +}; + +BoxIndex::BoxIndex(): m_impl(new Impl()) {} +BoxIndex::~BoxIndex() {} + +BoxIndex::BoxIndex(const BoxIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} +BoxIndex::BoxIndex(BoxIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} + +BoxIndex& BoxIndex::operator=(const BoxIndex &cpy) +{ + m_impl.reset(new Impl(*cpy.m_impl)); + return *this; +} + +BoxIndex& BoxIndex::operator=(BoxIndex &&cpy) +{ + m_impl.swap(cpy.m_impl); + return *this; +} + +void BoxIndex::insert(const BoxIndexEl &el) +{ + m_impl->m_store.insert(el); +} + +bool BoxIndex::remove(const BoxIndexEl& el) +{ + return m_impl->m_store.remove(el) == 1; +} + +std::vector BoxIndex::query(const BoundingBox &qrbb, + BoxIndex::QueryType qt) +{ + namespace bgi = boost::geometry::index; + + std::vector ret; ret.reserve(m_impl->m_store.size()); + + switch (qt) { + case qtIntersects: + m_impl->m_store.query(bgi::intersects(qrbb), std::back_inserter(ret)); + break; + case qtWithin: + m_impl->m_store.query(bgi::within(qrbb), std::back_inserter(ret)); + } + + return ret; +} + +size_t BoxIndex::size() const +{ + return m_impl->m_store.size(); +} + +void BoxIndex::foreach(std::function fn) { for(auto& el : m_impl->m_store) fn(el); } @@ -121,19 +197,10 @@ EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) { V.resize(3*stl.stats.number_of_facets, 3); F.resize(stl.stats.number_of_facets, 3); for (unsigned int i = 0; i < stl.stats.number_of_facets; ++i) { - const stl_facet* facet = stl.facet_start+i; - V(3*i+0, 0) = double(facet->vertex[0](0)); - V(3*i+0, 1) = double(facet->vertex[0](1)); - V(3*i+0, 2) = double(facet->vertex[0](2)); - - V(3*i+1, 0) = double(facet->vertex[1](0)); - V(3*i+1, 1) = double(facet->vertex[1](1)); - V(3*i+1, 2) = double(facet->vertex[1](2)); - - V(3*i+2, 0) = double(facet->vertex[2](0)); - V(3*i+2, 1) = double(facet->vertex[2](1)); - V(3*i+2, 2) = double(facet->vertex[2](2)); - + const stl_facet &facet = stl.facet_start[i]; + V.block<1, 3>(3 * i + 0, 0) = facet.vertex[0].cast(); + V.block<1, 3>(3 * i + 1, 0) = facet.vertex[1].cast(); + V.block<1, 3>(3 * i + 2, 0) = facet.vertex[2].cast(); F(i, 0) = int(3*i+0); F(i, 1) = int(3*i+1); F(i, 2) = int(3*i+2); @@ -239,6 +306,7 @@ PointSet normals(const PointSet& points, PointSet ret(range.size(), 3); +// for (size_t ridx = 0; ridx < range.size(); ++ridx) tbb::parallel_for(size_t(0), range.size(), [&ret, &range, &mesh, &points, thr, eps](size_t ridx) { @@ -352,12 +420,14 @@ PointSet normals(const PointSet& points, return ret; } namespace bgi = boost::geometry::index; -using Index3D = bgi::rtree< SpatElement, bgi::rstar<16, 4> /* ? */ >; +using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >; -ClusteredPoints cluster(Index3D& sindex, unsigned max_points, - std::function(const Index3D&, const SpatElement&)> qfn) +ClusteredPoints cluster(Index3D &sindex, + unsigned max_points, + std::function( + const Index3D &, const PointIndexEl &)> qfn) { - using Elems = std::vector; + using Elems = std::vector; // Recursive function for visiting all the points in a given distance to // each other @@ -365,8 +435,8 @@ ClusteredPoints cluster(Index3D& sindex, unsigned max_points, [&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster) { for(auto& p : pts) { - std::vector tmp = qfn(sindex, p); - auto cmp = [](const SpatElement& e1, const SpatElement& e2){ + std::vector tmp = qfn(sindex, p); + auto cmp = [](const PointIndexEl& e1, const PointIndexEl& e2){ return e1.second < e2.second; }; @@ -410,12 +480,12 @@ ClusteredPoints cluster(Index3D& sindex, unsigned max_points, } namespace { -std::vector distance_queryfn(const Index3D& sindex, - const SpatElement& p, +std::vector distance_queryfn(const Index3D& sindex, + const PointIndexEl& p, double dist, unsigned max_points) { - std::vector tmp; tmp.reserve(max_points); + std::vector tmp; tmp.reserve(max_points); sindex.query( bgi::nearest(p.first, max_points), std::back_inserter(tmp) @@ -442,7 +512,7 @@ ClusteredPoints cluster( for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx)); return cluster(sindex, max_points, - [dist, max_points](const Index3D& sidx, const SpatElement& p) + [dist, max_points](const Index3D& sidx, const PointIndexEl& p) { return distance_queryfn(sidx, p, dist, max_points); }); @@ -452,7 +522,7 @@ ClusteredPoints cluster( ClusteredPoints cluster( const std::vector& indices, std::function pointfn, - std::function predicate, + std::function predicate, unsigned max_points) { // A spatial index for querying the nearest points @@ -462,10 +532,10 @@ ClusteredPoints cluster( for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx)); return cluster(sindex, max_points, - [max_points, predicate](const Index3D& sidx, const SpatElement& p) + [max_points, predicate](const Index3D& sidx, const PointIndexEl& p) { - std::vector tmp; tmp.reserve(max_points); - sidx.query(bgi::satisfies([p, predicate](const SpatElement& e){ + std::vector tmp; tmp.reserve(max_points); + sidx.query(bgi::satisfies([p, predicate](const PointIndexEl& e){ return predicate(p, e); }), std::back_inserter(tmp)); return tmp; @@ -482,7 +552,7 @@ ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points) sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i))); return cluster(sindex, max_points, - [dist, max_points](const Index3D& sidx, const SpatElement& p) + [dist, max_points](const Index3D& sidx, const PointIndexEl& p) { return distance_queryfn(sidx, p, dist, max_points); }); diff --git a/src/libslic3r/Rasterizer/bicubic.h b/src/libslic3r/SLA/bicubic.h similarity index 100% rename from src/libslic3r/Rasterizer/bicubic.h rename to src/libslic3r/SLA/bicubic.h diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index f6a1c429e5..21aec83842 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -16,6 +16,12 @@ // For geometry algorithms with native Clipper types (no copies and conversions) #include +// #define SLAPRINT_DO_BENCHMARK + +#ifdef SLAPRINT_DO_BENCHMARK +#include +#endif + //#include //#include "tbb/mutex.h" #include "I18N.hpp" @@ -28,14 +34,15 @@ namespace Slic3r { using SupportTreePtr = std::unique_ptr; -class SLAPrintObject::SupportData { +class SLAPrintObject::SupportData +{ public: sla::EigenMesh3D emesh; // index-triangle representation std::vector support_points; // all the support points (manual/auto) - SupportTreePtr support_tree_ptr; // the supports - SlicedSupports support_slices; // sliced supports + SupportTreePtr support_tree_ptr; // the supports + std::vector support_slices; // sliced supports - inline SupportData(const TriangleMesh& trmesh): emesh(trmesh) {} + inline SupportData(const TriangleMesh &trmesh) : emesh(trmesh) {} }; namespace { @@ -51,7 +58,7 @@ const std::array OBJ_STEP_LEVELS = }; // Object step to status label. The labels are localized at the time of calling, thus supporting language switching. -std::string OBJ_STEP_LABELS(size_t idx) +std::string OBJ_STEP_LABELS(size_t idx) { switch (idx) { case slaposObjectSlice: return L("Slicing model"); @@ -144,14 +151,13 @@ static std::vector sla_instances(const ModelObject &mo return instances; } -SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConfig &config_in) +SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig config) { #ifdef _DEBUG check_model_ids_validity(model); #endif /* _DEBUG */ - // Make a copy of the config, normalize it. - DynamicPrintConfig config(config_in); + // Normalize the config. config.option("sla_print_settings_id", true); config.option("sla_material_settings_id", true); config.option("printer_settings_id", true); @@ -161,7 +167,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf t_config_option_keys printer_diff = m_printer_config.diff(config); t_config_option_keys material_diff = m_material_config.diff(config); t_config_option_keys object_diff = m_default_object_config.diff(config); - t_config_option_keys placeholder_parser_diff = this->placeholder_parser().config_diff(config); + t_config_option_keys placeholder_parser_diff = m_placeholder_parser.config_diff(config); // Do not use the ApplyStatus as we will use the max function when updating apply_status. unsigned int apply_status = APPLY_STATUS_UNCHANGED; @@ -186,12 +192,11 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf // only to generate the output file name. if (! placeholder_parser_diff.empty()) { // update_apply_status(this->invalidate_step(slapsRasterize)); - PlaceholderParser &pp = this->placeholder_parser(); - pp.apply_config(config); + m_placeholder_parser.apply_config(config); // Set the profile aliases for the PrintBase::output_filename() - pp.set("print_preset", config.option("sla_print_settings_id")->clone()); - pp.set("material_preset", config.option("sla_material_settings_id")->clone()); - pp.set("printer_preset", config.option("printer_settings_id")->clone()); + m_placeholder_parser.set("print_preset", config.option("sla_print_settings_id")->clone()); + m_placeholder_parser.set("material_preset", config.option("sla_material_settings_id")->clone()); + m_placeholder_parser.set("printer_preset", config.option("printer_settings_id")->clone()); } // It is also safe to change m_config now after this->invalidate_state_by_config_options() call. @@ -210,8 +215,8 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf Moved, Deleted, }; - ModelObjectStatus(ModelID id, Status status = Unknown) : id(id), status(status) {} - ModelID id; + ModelObjectStatus(ObjectID id, Status status = Unknown) : id(id), status(status) {} + ObjectID id; Status status; // Search by id. bool operator<(const ModelObjectStatus &rhs) const { return id < rhs.id; } @@ -314,9 +319,9 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf print_object(print_object), trafo(print_object->trafo()), status(status) {} - PrintObjectStatus(ModelID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {} + PrintObjectStatus(ObjectID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {} // ID of the ModelObject & PrintObject - ModelID id; + ObjectID id; // Pointer to the old PrintObject SLAPrintObject *print_object; // Trafo generated with model_object->world_matrix(true) @@ -366,7 +371,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf // Synchronize Object's config. bool object_config_changed = model_object.config != model_object_new.config; if (object_config_changed) - model_object.config = model_object_new.config; + static_cast(model_object.config) = static_cast(model_object_new.config); if (! object_diff.empty() || object_config_changed) { SLAPrintObjectConfig new_config = m_default_object_config; normalize_and_apply_config(new_config, model_object.config); @@ -387,9 +392,9 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf if (it_print_object_status != print_object_status.end()) update_apply_status(it_print_object_status->print_object->invalidate_step(slaposSupportPoints)); - model_object.sla_points_status = model_object_new.sla_points_status; model_object.sla_support_points = model_object_new.sla_support_points; } + model_object.sla_points_status = model_object_new.sla_points_status; // Copy the ModelObject name, input_file and instances. The instances will compared against PrintObject instances in the next step. model_object.name = model_object_new.name; @@ -425,10 +430,10 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf print_object->set_trafo(sla_trafo(*this, model_object), model_object.instances.front()->is_left_handed()); print_object->set_instances(std::move(new_instances)); - - SLAPrintObjectConfig new_config = m_default_object_config; - normalize_and_apply_config(new_config, model_object.config); - print_object->config_apply(new_config, true); + + SLAPrintObjectConfig new_config = m_default_object_config; + normalize_and_apply_config(new_config, model_object.config); + print_object->config_apply(new_config, true); print_objects_new.emplace_back(print_object); new_objects = true; } @@ -439,17 +444,15 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf update_apply_status(this->invalidate_all_steps()); m_objects = print_objects_new; // Delete the PrintObjects marked as Unknown or Deleted. - bool deleted_objects = false; for (auto &pos : print_object_status) if (pos.status == PrintObjectStatus::Unknown || pos.status == PrintObjectStatus::Deleted) { update_apply_status(pos.print_object->invalidate_all_steps()); delete pos.print_object; - deleted_objects = true; } if (new_objects) update_apply_status(false); } - + if(m_objects.empty()) { m_printer.release(); m_printer_input.clear(); @@ -460,6 +463,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf check_model_ids_equal(m_model, model); #endif /* _DEBUG */ + m_full_print_config = std::move(config); return static_cast(apply_status); } @@ -471,7 +475,7 @@ void SLAPrint::set_task(const TaskParams ¶ms) int n_object_steps = int(params.to_object_step) + 1; if (n_object_steps == 0) - n_object_steps = (int)slaposCount; + n_object_steps = int(slaposCount); if (params.single_model_object.valid()) { // Find the print object to be processed with priority. @@ -486,7 +490,7 @@ void SLAPrint::set_task(const TaskParams ¶ms) // Find out whether the priority print object is being currently processed. bool running = false; for (int istep = 0; istep < n_object_steps; ++ istep) { - if (! print_object->m_stepmask[istep]) + if (! print_object->m_stepmask[size_t(istep)]) // Step was skipped, cancel. break; if (print_object->is_step_started_unguarded(SLAPrintObjectStep(istep))) { @@ -502,7 +506,7 @@ void SLAPrint::set_task(const TaskParams ¶ms) if (params.single_model_instance_only) { // Suppress all the steps of other instances. for (SLAPrintObject *po : m_objects) - for (int istep = 0; istep < (int)slaposCount; ++ istep) + for (size_t istep = 0; istep < slaposCount; ++ istep) po->m_stepmask[istep] = false; } else if (! running) { // Swap the print objects, so that the selected print_object is first in the row. @@ -512,15 +516,15 @@ void SLAPrint::set_task(const TaskParams ¶ms) } // and set the steps for the current object. for (int istep = 0; istep < n_object_steps; ++ istep) - print_object->m_stepmask[istep] = true; - for (int istep = n_object_steps; istep < (int)slaposCount; ++ istep) - print_object->m_stepmask[istep] = false; + print_object->m_stepmask[size_t(istep)] = true; + for (int istep = n_object_steps; istep < int(slaposCount); ++ istep) + print_object->m_stepmask[size_t(istep)] = false; } else { // Slicing all objects. bool running = false; for (SLAPrintObject *print_object : m_objects) for (int istep = 0; istep < n_object_steps; ++ istep) { - if (! print_object->m_stepmask[istep]) { + if (! print_object->m_stepmask[size_t(istep)]) { // Step may have been skipped. Restart. goto loop_end; } @@ -536,8 +540,8 @@ void SLAPrint::set_task(const TaskParams ¶ms) this->call_cancel_callback(); for (SLAPrintObject *po : m_objects) { for (int istep = 0; istep < n_object_steps; ++ istep) - po->m_stepmask[istep] = true; - for (int istep = n_object_steps; istep < (int)slaposCount; ++ istep) + po->m_stepmask[size_t(istep)] = true; + for (auto istep = size_t(n_object_steps); istep < slaposCount; ++ istep) po->m_stepmask[istep] = false; } } @@ -555,9 +559,9 @@ void SLAPrint::set_task(const TaskParams ¶ms) void SLAPrint::finalize() { for (SLAPrintObject *po : m_objects) - for (int istep = 0; istep < (int)slaposCount; ++ istep) + for (size_t istep = 0; istep < slaposCount; ++ istep) po->m_stepmask[istep] = true; - for (int istep = 0; istep < (int)slapsCount; ++ istep) + for (size_t istep = 0; istep < slapsCount; ++ istep) m_stepmask[istep] = true; } @@ -571,6 +575,16 @@ std::string SLAPrint::output_filename(const std::string &filename_base) const } namespace { + +bool is_zero_elevation(const SLAPrintObjectConfig &c) { + bool en_implicit = c.support_object_elevation.getFloat() <= EPSILON && + c.pad_enable.getBool() && c.supports_enable.getBool(); + bool en_explicit = c.pad_zero_elevation.getBool() && + c.supports_enable.getBool(); + + return en_implicit || en_explicit; +} + // Compile the argument for support creation from the static print config. sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { sla::SupportConfig scfg; @@ -579,7 +593,8 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { scfg.head_back_radius_mm = 0.5*c.support_pillar_diameter.getFloat(); scfg.head_penetration_mm = c.support_head_penetration.getFloat(); scfg.head_width_mm = c.support_head_width.getFloat(); - scfg.object_elevation_mm = c.support_object_elevation.getFloat(); + scfg.object_elevation_mm = is_zero_elevation(c) ? + 0. : c.support_object_elevation.getFloat(); scfg.bridge_slope = c.support_critical_angle.getFloat() * PI / 180.0 ; scfg.max_bridge_length_mm = c.support_max_bridge_length.getFloat(); scfg.max_pillar_link_distance_mm = c.support_max_pillar_link_distance.getFloat(); @@ -595,21 +610,47 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { scfg.pillar_widening_factor = c.support_pillar_widening_factor.getFloat(); scfg.base_radius_mm = 0.5*c.support_base_diameter.getFloat(); scfg.base_height_mm = c.support_base_height.getFloat(); + scfg.pillar_base_safety_distance_mm = + c.support_base_safety_distance.getFloat() < EPSILON ? + scfg.safety_distance_mm : c.support_base_safety_distance.getFloat(); return scfg; } +sla::PoolConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) { + sla::PoolConfig::EmbedObject ret; + + ret.enabled = is_zero_elevation(c); + + if(ret.enabled) { + ret.object_gap_mm = c.pad_object_gap.getFloat(); + ret.stick_width_mm = c.pad_object_connector_width.getFloat(); + ret.stick_stride_mm = c.pad_object_connector_stride.getFloat(); + ret.stick_penetration_mm = c.pad_object_connector_penetration + .getFloat(); + } + + return ret; +} + sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) { sla::PoolConfig pcfg; pcfg.min_wall_thickness_mm = c.pad_wall_thickness.getFloat(); - pcfg.wall_slope = c.pad_wall_slope.getFloat(); - pcfg.edge_radius_mm = c.pad_edge_radius.getFloat(); + pcfg.wall_slope = c.pad_wall_slope.getFloat() * PI / 180.0; + + // We do not support radius for now + pcfg.edge_radius_mm = 0.0; //c.pad_edge_radius.getFloat(); + pcfg.max_merge_distance_mm = c.pad_max_merge_distance.getFloat(); pcfg.min_wall_height_mm = c.pad_wall_height.getFloat(); + // set builtin pad implicitly ON + pcfg.embed_object = builtin_pad_cfg(c); + return pcfg; } + } std::string SLAPrint::validate() const @@ -633,10 +674,38 @@ std::string SLAPrint::validate() const 2 * cfg.head_back_radius_mm - cfg.head_penetration_mm; - if(supports_en && pinhead_width > cfg.object_elevation_mm) - return L("Elevation is too low for object."); + double elv = cfg.object_elevation_mm; + + if(supports_en && elv > EPSILON && elv < pinhead_width ) + return L( + "Elevation is too low for object. Use the \"Pad around " + "obect\" feature to print the object without elevation."); + + sla::PoolConfig::EmbedObject builtinpad = builtin_pad_cfg(po->config()); + if(supports_en && builtinpad.enabled && + cfg.pillar_base_safety_distance_mm < builtinpad.object_gap_mm) { + return L( + "The endings of the support pillars will be deployed on the " + "gap between the object and the pad. 'Support base safety " + "distance' has to be greater than the 'Pad object gap' " + "parameter to avoid this."); + } } + double expt_max = m_printer_config.max_exposure_time.getFloat(); + double expt_min = m_printer_config.min_exposure_time.getFloat(); + double expt_cur = m_material_config.exposure_time.getFloat(); + + if (expt_cur < expt_min || expt_cur > expt_max) + return L("Exposition time is out of printer profile bounds."); + + double iexpt_max = m_printer_config.max_initial_exposure_time.getFloat(); + double iexpt_min = m_printer_config.min_initial_exposure_time.getFloat(); + double iexpt_cur = m_material_config.initial_exposure_time.getFloat(); + + if (iexpt_cur < iexpt_min || iexpt_cur > iexpt_max) + return L("Initial exposition time is out of printer profile bounds."); + return ""; } @@ -666,11 +735,11 @@ void SLAPrint::process() double ilhd = m_material_config.initial_layer_height.getFloat(); auto ilh = float(ilhd); - auto ilhs = coord_t(ilhd / SCALING_FACTOR); + coord_t ilhs = scaled(ilhd); const size_t objcount = m_objects.size(); - const unsigned min_objstatus = 0; // where the per object operations start - const unsigned max_objstatus = 50; // where the per object operations end + static const unsigned min_objstatus = 0; // where the per object operations start + static const unsigned max_objstatus = 50; // where the per object operations end // the coefficient that multiplies the per object status values which // are set up for <0, 100>. They need to be scaled into the whole process @@ -687,40 +756,40 @@ void SLAPrint::process() // Slicing the model object. This method is oversimplified and needs to // be compared with the fff slicing algorithm for verification - auto slice_model = [this, ilhs, ilh, ilhd](SLAPrintObject& po) { + auto slice_model = [this, ilhs, ilh](SLAPrintObject& po) { const TriangleMesh& mesh = po.transformed_mesh(); // We need to prepare the slice index... - double lhd = m_objects.front()->m_config.layer_height.getFloat(); - float lh = float(lhd); - auto lhs = coord_t(lhd / SCALING_FACTOR); - - auto&& bb3d = mesh.bounding_box(); - double minZ = bb3d.min(Z) - po.get_elevation(); - double maxZ = bb3d.max(Z); - - auto minZs = coord_t(minZ / SCALING_FACTOR); - auto maxZs = coord_t(maxZ / SCALING_FACTOR); + double lhd = m_objects.front()->m_config.layer_height.getFloat(); + float lh = float(lhd); + coord_t lhs = scaled(lhd); + auto && bb3d = mesh.bounding_box(); + double minZ = bb3d.min(Z) - po.get_elevation(); + double maxZ = bb3d.max(Z); + auto minZf = float(minZ); + coord_t minZs = scaled(minZ); + coord_t maxZs = scaled(maxZ); po.m_slice_index.clear(); - + size_t cap = size_t(1 + (maxZs - minZs - ilhs) / lhs); po.m_slice_index.reserve(cap); - - po.m_slice_index.emplace_back(minZs + ilhs, minZ + ilhd / 2.0, ilh); - for(coord_t h = minZs + ilhs + lhs; h <= maxZs; h += lhs) - po.m_slice_index.emplace_back(h, h*SCALING_FACTOR - lhd / 2.0, lh); - - // Just get the first record that is form the model: + po.m_slice_index.emplace_back(minZs + ilhs, minZf + ilh / 2.f, ilh); + + for(coord_t h = minZs + ilhs + lhs; h <= maxZs; h += lhs) + po.m_slice_index.emplace_back(h, unscaled(h) - lh / 2.f, lh); + + // Just get the first record that is from the model: auto slindex_it = po.closest_slice_record(po.m_slice_index, float(bb3d.min(Z))); if(slindex_it == po.m_slice_index.end()) //TRN To be shown at the status bar on SLA slicing error. - throw std::runtime_error(L("Slicing had to be stopped " - "due to an internal error.")); + throw std::runtime_error( + L("Slicing had to be stopped due to an internal error: " + "Inconsistent slice index.")); po.m_model_height_levels.clear(); po.m_model_height_levels.reserve(po.m_slice_index.size()); @@ -737,30 +806,39 @@ void SLAPrint::process() auto mit = slindex_it; double doffs = m_printer_config.absolute_correction.getFloat(); - coord_t clpr_offs = coord_t(doffs / SCALING_FACTOR); + coord_t clpr_offs = scaled(doffs); for(size_t id = 0; id < po.m_model_slices.size() && mit != po.m_slice_index.end(); id++) { // We apply the printer correction offset here. if(clpr_offs != 0) - po.m_model_slices[id] = - offset_ex(po.m_model_slices[id], clpr_offs); - + po.m_model_slices[id] = + offset_ex(po.m_model_slices[id], float(clpr_offs)); + mit->set_model_slice_idx(po, id); ++mit; } + + if(po.m_config.supports_enable.getBool() || + po.m_config.pad_enable.getBool()) + { + po.m_supportdata.reset( + new SLAPrintObject::SupportData(po.transformed_mesh()) ); + } }; // In this step we check the slices, identify island and cover them with // support points. Then we sprinkle the rest of the mesh. auto support_points = [this, ostepd](SLAPrintObject& po) { - const ModelObject& mo = *po.m_model_object; - po.m_supportdata.reset( - new SLAPrintObject::SupportData(po.transformed_mesh()) ); - // If supports are disabled, we can skip the model scan. if(!po.m_config.supports_enable.getBool()) return; + if (!po.m_supportdata) + po.m_supportdata.reset( + new SLAPrintObject::SupportData(po.transformed_mesh())); + + const ModelObject& mo = *po.m_model_object; + BOOST_LOG_TRIVIAL(debug) << "Support point count " << mo.sla_support_points.size(); @@ -769,7 +847,7 @@ void SLAPrint::process() // into the backend cache. if (mo.sla_points_status != sla::PointsStatus::UserModified) { - // Hypotetical use of the slice index: + // Hypothetical use of the slice index: // auto bb = po.transformed_mesh().bounding_box(); // auto range = po.get_slice_records(bb.min(Z)); // std::vector heights; heights.reserve(range.size()); @@ -818,13 +896,40 @@ void SLAPrint::process() BOOST_LOG_TRIVIAL(debug) << "Automatic support points: " << po.m_supportdata->support_points.size(); - // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass the update status to GLGizmoSlaSupports - m_report_status(*this, -1, L("Generating support points"), SlicingStatus::RELOAD_SLA_SUPPORT_POINTS); + // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass + // the update status to GLGizmoSlaSupports + m_report_status(*this, + -1, + L("Generating support points"), + SlicingStatus::RELOAD_SLA_SUPPORT_POINTS); } else { - // There are either some points on the front-end, or the user removed them on purpose. No calculation will be done. + // There are either some points on the front-end, or the user + // removed them on purpose. No calculation will be done. po.m_supportdata->support_points = po.transformed_support_points(); } + + // If the zero elevation mode is engaged, we have to filter out all the + // points that are on the bottom of the object + if (is_zero_elevation(po.config())) { + double gnd = po.m_supportdata->emesh.ground_level(); + auto & pts = po.m_supportdata->support_points; + double tolerance = po.config().pad_enable.getBool() + ? po.m_config.pad_wall_thickness.getFloat() + : po.m_config.support_base_height.getFloat(); + + // get iterator to the reorganized vector end + auto endit = std::remove_if( + pts.begin(), + pts.end(), + [tolerance, gnd](const sla::SupportPoint &sp) { + double diff = std::abs(gnd - double(sp.pos(Z))); + return diff <= tolerance; + }); + + // erase all elements after the new end + pts.erase(endit, pts.end()); + } }; // In this step we create the supports @@ -832,9 +937,18 @@ void SLAPrint::process() { if(!po.m_supportdata) return; + sla::PoolConfig pcfg = make_pool_config(po.m_config); + + if (pcfg.embed_object) + po.m_supportdata->emesh.ground_level_offset( + pcfg.min_wall_thickness_mm); + if(!po.m_config.supports_enable.getBool()) { + // Generate empty support tree. It can still host a pad - po.m_supportdata->support_tree_ptr.reset(new SLASupportTree()); + po.m_supportdata->support_tree_ptr.reset( + new SLASupportTree(po.m_supportdata->emesh.ground_level())); + return; } @@ -845,12 +959,14 @@ void SLAPrint::process() double d = ostepd * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0; double init = m_report_status.status(); - ctl.statuscb = [this, d, init](unsigned st, const std::string&) + ctl.statuscb = [this, d, init](unsigned st, const std::string &logmsg) { double current = init + st * d; if(std::round(m_report_status.status()) < std::round(current)) m_report_status(*this, current, - OBJ_STEP_LABELS(slaposSupportTree)); + OBJ_STEP_LABELS(slaposSupportTree), + SlicingStatus::DEFAULT, + logmsg); }; @@ -886,40 +1002,33 @@ void SLAPrint::process() // and before the supports had been sliced. (or the slicing has to be // repeated) - if(!po.m_supportdata || !po.m_supportdata->support_tree_ptr) { - BOOST_LOG_TRIVIAL(error) << "Uninitialized support data at " - << "pad creation."; - return; - } - if(po.m_config.pad_enable.getBool()) { - double wt = po.m_config.pad_wall_thickness.getFloat(); - double h = po.m_config.pad_wall_height.getFloat(); - double md = po.m_config.pad_max_merge_distance.getFloat(); - // Radius is disabled for now... - double er = 0; // po.m_config.pad_edge_radius.getFloat(); - double tilt = po.m_config.pad_wall_slope.getFloat() * PI / 180.0; - double lh = po.m_config.layer_height.getFloat(); - double elevation = po.m_config.support_object_elevation.getFloat(); - if(!po.m_config.supports_enable.getBool()) elevation = 0; - sla::PoolConfig pcfg(wt, h, md, er, tilt); + // Get the distilled pad configuration from the config + sla::PoolConfig pcfg = make_pool_config(po.m_config); - ExPolygons bp; - double pad_h = sla::get_pad_fullheight(pcfg); - auto&& trmesh = po.transformed_mesh(); + ExPolygons bp; // This will store the base plate of the pad. + double pad_h = sla::get_pad_fullheight(pcfg); + const TriangleMesh &trmesh = po.transformed_mesh(); // This call can get pretty time consuming auto thrfn = [this](){ throw_if_canceled(); }; - if(elevation < pad_h) { - // we have to count with the model geometry for the base plate - sla::base_plate(trmesh, bp, float(pad_h), float(lh), thrfn); + if (!po.m_config.supports_enable.getBool() || pcfg.embed_object) { + // No support (thus no elevation) or zero elevation mode + // we sometimes call it "builtin pad" is enabled so we will + // get a sample from the bottom of the mesh and use it for pad + // creation. + sla::base_plate(trmesh, + bp, + float(pad_h), + float(po.m_config.layer_height.getFloat()), + thrfn); } pcfg.throw_on_cancel = thrfn; po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg); - } else { + } else if(po.m_supportdata && po.m_supportdata->support_tree_ptr) { po.m_supportdata->support_tree_ptr->remove_pad(); } @@ -936,6 +1045,11 @@ void SLAPrint::process() if(sd) sd->support_slices.clear(); + // Don't bother if no supports and no pad is present. + if (!po.m_config.supports_enable.getBool() && + !po.m_config.pad_enable.getBool()) + return; + if(sd && sd->support_tree_ptr) { std::vector heights; heights.reserve(po.m_slice_index.size()); @@ -949,20 +1063,21 @@ void SLAPrint::process() } double doffs = m_printer_config.absolute_correction.getFloat(); - coord_t clpr_offs = coord_t(doffs / SCALING_FACTOR); + coord_t clpr_offs = scaled(doffs); for(size_t i = 0; i < sd->support_slices.size() && i < po.m_slice_index.size(); ++i) { // We apply the printer correction offset here. if(clpr_offs != 0) - sd->support_slices[i] = - offset_ex(sd->support_slices[i], clpr_offs); - + sd->support_slices[i] = + offset_ex(sd->support_slices[i], float(clpr_offs)); + po.m_slice_index[i].set_support_slice_idx(po, i); } - // Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update status to the 3D preview to load the SLA slices. + // Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update + // status to the 3D preview to load the SLA slices. m_report_status(*this, -2, "", SlicingStatus::RELOAD_SLA_PREVIEW); }; @@ -1010,9 +1125,6 @@ void SLAPrint::process() using ClipperPolygons = std::vector; namespace sl = libnest2d::shapelike; // For algorithms - // If the raster has vertical orientation, we will flip the coordinates - bool flpXY = m_printer_config.display_orientation.getInt() == SLADisplayOrientation::sladoPortrait; - // Set up custom union and diff functions for clipper polygons auto polyunion = [] (const ClipperPolygons& subjects) { @@ -1063,15 +1175,15 @@ void SLAPrint::process() const int fade_layers_cnt = m_default_object_config.faded_layers.getInt();// 10 // [3;20] - const double width = m_printer_config.display_width.getFloat() / SCALING_FACTOR; - const double height = m_printer_config.display_height.getFloat() / SCALING_FACTOR; + const auto width = scaled(m_printer_config.display_width.getFloat()); + const auto height = scaled(m_printer_config.display_height.getFloat()); const double display_area = width*height; // get polygons for all instances in the object auto get_all_polygons = - [flpXY](const ExPolygons& input_polygons, - const std::vector& instances, - bool is_lefthanded) + [](const ExPolygons& input_polygons, + const std::vector& instances, + bool is_lefthanded) { ClipperPolygons polygons; polygons.reserve(input_polygons.size() * instances.size()); @@ -1085,7 +1197,7 @@ void SLAPrint::process() // We need to reverse if flpXY OR is_lefthanded is true but // not if both are true which is a logical inequality (XOR) - bool needreverse = flpXY != is_lefthanded; + bool needreverse = /*flpXY !=*/ is_lefthanded; // should be a move poly.Contour.reserve(polygon.contour.size() + 1); @@ -1120,11 +1232,6 @@ void SLAPrint::process() sl::translate(poly, ClipperPoint{instances[i].shift(X), instances[i].shift(Y)}); - if (flpXY) { - for(auto& p : poly.Contour) std::swap(p.X, p.Y); - for(auto& h : poly.Holes) for(auto& p : h) std::swap(p.X, p.Y); - } - polygons.emplace_back(std::move(poly)); } } @@ -1170,13 +1277,20 @@ void SLAPrint::process() ClipperPolygons model_polygons; ClipperPolygons supports_polygons; - size_t c = std::accumulate(layer.slices().begin(), layer.slices().end(), 0u, [](size_t a, const SliceRecord& sr) { - return a + sr.get_slice(soModel).size(); + size_t c = std::accumulate(layer.slices().begin(), + layer.slices().end(), + size_t(0), + [](size_t a, const SliceRecord &sr) { + return a + sr.get_slice(soModel) + .size(); }); model_polygons.reserve(c); - c = std::accumulate(layer.slices().begin(), layer.slices().end(), 0u, [](size_t a, const SliceRecord& sr) { + c = std::accumulate(layer.slices().begin(), + layer.slices().end(), + size_t(0), + [](size_t a, const SliceRecord &sr) { return a + sr.get_slice(soModel).size(); }); @@ -1186,7 +1300,7 @@ void SLAPrint::process() const SLAPrintObject *po = record.print_obj(); const ExPolygons &modelslices = record.get_slice(soModel); - + bool is_lefth = record.print_obj()->is_left_handed(); if (!modelslices.empty()) { ClipperPolygons v = get_all_polygons(modelslices, po->instances(), is_lefth); @@ -1194,7 +1308,7 @@ void SLAPrint::process() } const ExPolygons &supportslices = record.get_slice(soSupport); - + if (!supportslices.empty()) { ClipperPolygons v = get_all_polygons(supportslices, po->instances(), is_lefth); for(ClipperPolygon& p_tmp : v) supports_polygons.emplace_back(std::move(p_tmp)); @@ -1264,8 +1378,9 @@ void SLAPrint::process() // for(size_t i = 0; i < m_printer_input.size(); ++i) printlayerfn(i); tbb::parallel_for(0, m_printer_input.size(), printlayerfn); - m_print_statistics.support_used_material = supports_volume * SCALING_FACTOR * SCALING_FACTOR; - m_print_statistics.objects_used_material = models_volume * SCALING_FACTOR * SCALING_FACTOR; + auto SCALING2 = SCALING_FACTOR * SCALING_FACTOR; + m_print_statistics.support_used_material = supports_volume * SCALING2; + m_print_statistics.objects_used_material = models_volume * SCALING2; // Estimated printing time // A layers count o the highest object @@ -1281,38 +1396,14 @@ void SLAPrint::process() }; // Rasterizing the model objects, and their supports - auto rasterize = [this, max_objstatus]() { + auto rasterize = [this]() { if(canceled()) return; - // collect all the keys - - // If the raster has vertical orientation, we will flip the coordinates - bool flpXY = m_printer_config.display_orientation.getInt() == - SLADisplayOrientation::sladoPortrait; - { // create a raster printer for the current print parameters - // I don't know any better - auto& ocfg = m_objects.front()->m_config; - auto& matcfg = m_material_config; - auto& printcfg = m_printer_config; - - double w = printcfg.display_width.getFloat(); - double h = printcfg.display_height.getFloat(); - auto pw = unsigned(printcfg.display_pixels_x.getInt()); - auto ph = unsigned(printcfg.display_pixels_y.getInt()); - double lh = ocfg.layer_height.getFloat(); - double exp_t = matcfg.exposure_time.getFloat(); - double iexp_t = matcfg.initial_exposure_time.getFloat(); - - double gamma = m_printer_config.gamma_correction.getFloat(); - - if(flpXY) { std::swap(w, h); std::swap(pw, ph); } - - m_printer.reset( - new SLAPrinter(w, h, pw, ph, lh, exp_t, iexp_t, - flpXY? SLAPrinter::RO_PORTRAIT : - SLAPrinter::RO_LANDSCAPE, - gamma)); + double layerh = m_default_object_config.layer_height.getFloat(); + m_printer.reset(new SLAPrinter(m_printer_config, + m_material_config, + layerh)); } // Allocate space for all the layers @@ -1370,91 +1461,103 @@ void SLAPrint::process() if(canceled()) return; // Sequential version (for testing) - // for(unsigned l = 0; l < lvlcnt; ++l) process_level(l); + // for(unsigned l = 0; l < lvlcnt; ++l) lvlfn(l); // Print all the layers in parallel tbb::parallel_for(0, lvlcnt, lvlfn); // Set statistics values to the printer - m_printer->set_statistics({(m_print_statistics.objects_used_material + m_print_statistics.support_used_material)/1000, - double(m_default_object_config.faded_layers.getInt()), - double(m_print_statistics.slow_layers_count), - double(m_print_statistics.fast_layers_count) - }); + m_printer->set_statistics( + {(m_print_statistics.objects_used_material + + m_print_statistics.support_used_material) / 1000, + double(m_default_object_config.faded_layers.getInt()), + double(m_print_statistics.slow_layers_count), + double(m_print_statistics.fast_layers_count)}); }; using slaposFn = std::function; using slapsFn = std::function; - std::array pobj_program = + slaposFn pobj_program[] = { - slice_model, - support_points, - support_tree, - base_pool, - slice_supports + slice_model, support_points, support_tree, base_pool, slice_supports }; - std::array print_program = - { - merge_slices_and_eval_stats, - rasterize + // We want to first process all objects... + std::vector level1_obj_steps = { + slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposBasePool }; + // and then slice all supports to allow preview to be displayed ASAP + std::vector level2_obj_steps = { + slaposSliceSupports + }; + + slapsFn print_program[] = { merge_slices_and_eval_stats, rasterize }; + SLAPrintStep print_steps[] = { slapsMergeSlicesAndEval, slapsRasterize }; + double st = min_objstatus; - unsigned incr = 0; BOOST_LOG_TRIVIAL(info) << "Start slicing process."; - // TODO: this loop could run in parallel but should not exhaust all the CPU - // power available - // Calculate the support structures first before slicing the supports, so that the preview will get displayed ASAP for all objects. - std::vector step_ranges = { slaposObjectSlice, slaposSliceSupports, slaposCount }; - for (size_t idx_range = 0; idx_range + 1 < step_ranges.size(); ++ idx_range) { - for(SLAPrintObject * po : m_objects) { +#ifdef SLAPRINT_DO_BENCHMARK + Benchmark bench; +#else + struct { + void start() {} void stop() {} double getElapsedSec() { return .0; } + } bench; +#endif - BOOST_LOG_TRIVIAL(info) << "Slicing object " << po->model_object()->name; + std::array step_times {}; - for (int s = int(step_ranges[idx_range]); s < int(step_ranges[idx_range + 1]); ++s) { - auto currentstep = static_cast(s); + auto apply_steps_on_objects = + [this, &st, ostepd, &pobj_program, &step_times, &bench] + (const std::vector &steps) + { + unsigned incr = 0; + for (SLAPrintObject *po : m_objects) { + for (SLAPrintObjectStep step : steps) { - // Cancellation checking. Each step will check for cancellation - // on its own and return earlier gracefully. Just after it returns - // execution gets to this point and throws the canceled signal. + // Cancellation checking. Each step will check for + // cancellation on its own and return earlier gracefully. + // Just after it returns execution gets to this point and + // throws the canceled signal. throw_if_canceled(); st += incr * ostepd; - if(po->m_stepmask[currentstep] && po->set_started(currentstep)) { - m_report_status(*this, st, OBJ_STEP_LABELS(currentstep)); - pobj_program[currentstep](*po); + if (po->m_stepmask[step] && po->set_started(step)) { + m_report_status(*this, st, OBJ_STEP_LABELS(step)); + bench.start(); + pobj_program[step](*po); + bench.stop(); + step_times[step] += bench.getElapsedSec(); throw_if_canceled(); - po->set_done(currentstep); + po->set_done(step); } - incr = OBJ_STEP_LEVELS[currentstep]; + incr = OBJ_STEP_LEVELS[step]; } } - } - - std::array printsteps = { - slapsMergeSlicesAndEval, slapsRasterize }; + apply_steps_on_objects(level1_obj_steps); + apply_steps_on_objects(level2_obj_steps); + // this would disable the rasterization step - // m_stepmask[slapsRasterize] = false; + // std::fill(m_stepmask.begin(), m_stepmask.end(), false); double pstd = (100 - max_objstatus) / 100.0; st = max_objstatus; - for(size_t s = 0; s < print_program.size(); ++s) { - auto currentstep = printsteps[s]; - + for(SLAPrintStep currentstep : print_steps) { throw_if_canceled(); - if(m_stepmask[currentstep] && set_started(currentstep)) - { + if (m_stepmask[currentstep] && set_started(currentstep)) { m_report_status(*this, st, PRINT_STEP_LABELS(currentstep)); + bench.start(); print_program[currentstep](); + bench.stop(); + step_times[slaposCount + currentstep] += bench.getElapsedSec(); throw_if_canceled(); set_done(currentstep); } @@ -1464,6 +1567,21 @@ void SLAPrint::process() // If everything vent well m_report_status(*this, 100, L("Slicing done")); + +#ifdef SLAPRINT_DO_BENCHMARK + std::string csvbenchstr; + for (size_t i = 0; i < size_t(slaposCount); ++i) + csvbenchstr += OBJ_STEP_LABELS(i) + ";"; + + for (size_t i = 0; i < size_t(slapsCount); ++i) + csvbenchstr += PRINT_STEP_LABELS(i) + ";"; + + csvbenchstr += "\n"; + for (double t : step_times) csvbenchstr += std::to_string(t) + ";"; + + std::cout << "Performance stats: \n" << csvbenchstr << std::endl; +#endif + } bool SLAPrint::invalidate_state_by_config_options(const std::vector &opt_keys, bool &invalidate_all_model_objects) @@ -1482,12 +1600,18 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector steps_rasterize = { + "min_exposure_time", + "max_exposure_time", "exposure_time", + "min_initial_exposure_time", + "max_initial_exposure_time", "initial_exposure_time", "display_width", "display_height", "display_pixels_x", "display_pixels_y", + "display_mirror_x", + "display_mirror_y", "display_orientation" }; @@ -1544,14 +1668,17 @@ bool SLAPrint::is_step_done(SLAPrintObjectStep step) const return true; } -SLAPrintObject::SLAPrintObject(SLAPrint *print, ModelObject *model_object): - Inherited(print, model_object), - m_stepmask(slaposCount, true), - m_transformed_rmesh( [this](TriangleMesh& obj){ - obj = m_model_object->raw_mesh(); obj.transform(m_trafo); obj.require_shared_vertices(); - }) -{ -} +SLAPrintObject::SLAPrintObject(SLAPrint *print, ModelObject *model_object) + : Inherited(print, model_object) + , m_stepmask(slaposCount, true) + , m_transformed_rmesh([this](TriangleMesh &obj) { + obj = m_model_object->raw_mesh(); + if (!obj.empty()) { + obj.transform(m_trafo); + obj.require_shared_vertices(); + } + }) +{} SLAPrintObject::~SLAPrintObject() {} @@ -1571,6 +1698,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector= 2) { corr(X) *= material_config().material_correction.values[0]; @@ -1690,13 +1828,14 @@ namespace { // dummy empty static containers for return values in some methods const std::vector EMPTY_SLICES; const TriangleMesh EMPTY_MESH; const ExPolygons EMPTY_SLICE; +const std::vector EMPTY_SUPPORT_POINTS; } const SliceRecord SliceRecord::EMPTY(0, std::nanf(""), 0.f); const std::vector& SLAPrintObject::get_support_points() const { - return m_supportdata->support_points; + return m_supportdata? m_supportdata->support_points : EMPTY_SUPPORT_POINTS; } const std::vector &SLAPrintObject::get_support_slices() const @@ -1786,8 +1925,8 @@ std::vector SLAPrintObject::transformed_support_points() cons ret.reserve(spts.size()); for(sla::SupportPoint& sp : spts) { - Vec3d transformed_pos = trafo() * Vec3d(sp.pos(0), sp.pos(1), sp.pos(2)); - ret.emplace_back(transformed_pos(0), transformed_pos(1), transformed_pos(2), sp.head_front_radius, sp.is_new_island); + Vec3f transformed_pos = trafo().cast() * sp.pos; + ret.emplace_back(transformed_pos, sp.head_front_radius, sp.is_new_island); } return ret; @@ -1833,11 +1972,17 @@ std::string SLAPrintStatistics::finalize_output_path(const std::string &path_in) return final_path; } -void SLAPrint::StatusReporter::operator()( - SLAPrint &p, double st, const std::string &msg, unsigned flags) +void SLAPrint::StatusReporter::operator()(SLAPrint & p, + double st, + const std::string &msg, + unsigned flags, + const std::string &logmsg) { m_st = st; - BOOST_LOG_TRIVIAL(info) << st << "% " << msg << log_memory_info(); + BOOST_LOG_TRIVIAL(info) + << st << "% " << msg << (logmsg.empty() ? "" : ": ") << logmsg + << log_memory_info(); + p.set_status(int(std::round(st)), msg, flags); } diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index dea468e7a0..e8cdac1b86 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -3,11 +3,11 @@ #include #include "PrintBase.hpp" -#include "PrintExport.hpp" +//#include "PrintExport.hpp" +#include "SLA/SLARasterWriter.hpp" #include "Point.hpp" #include "MTUtils.hpp" #include -#include "Zipper.hpp" namespace Slic3r { @@ -54,15 +54,15 @@ public: bool is_left_handed() const { return m_left_handed; } struct Instance { - Instance(ModelID instance_id, const Point &shift, float rotation) : instance_id(instance_id), shift(shift), rotation(rotation) {} - bool operator==(const Instance &rhs) const { return this->instance_id == rhs.instance_id && this->shift == rhs.shift && this->rotation == rhs.rotation; } - // ID of the corresponding ModelInstance. - ModelID instance_id; - // Slic3r::Point objects in scaled G-code coordinates - Point shift; - // Rotation along the Z axis, in radians. - float rotation; - }; + Instance(ObjectID instance_id, const Point &shift, float rotation) : instance_id(instance_id), shift(shift), rotation(rotation) {} + bool operator==(const Instance &rhs) const { return this->instance_id == rhs.instance_id && this->shift == rhs.shift && this->rotation == rhs.rotation; } + // ID of the corresponding ModelInstance. + ObjectID instance_id; + // Slic3r::Point objects in scaled G-code coordinates + Point shift; + // Rotation along the Z axis, in radians. + float rotation; + }; const std::vector& instances() const { return m_instances; } bool has_mesh(SLAPrintObjectStep step) const; @@ -142,15 +142,19 @@ public: }; private: - - template inline static T level(const SliceRecord& sr) { + template inline static T level(const SliceRecord &sr) + { static_assert(std::is_arithmetic::value, "Arithmetic only!"); - return std::is_integral::value ? T(sr.print_level()) : T(sr.slice_level()); + return std::is_integral::value ? T(sr.print_level()) + : T(sr.slice_level()); } - template inline static SliceRecord create_slice_record(T val) { + template inline static SliceRecord create_slice_record(T val) + { static_assert(std::is_arithmetic::value, "Arithmetic only!"); - return std::is_integral::value ? SliceRecord{ coord_t(val), 0.f, 0.f } : SliceRecord{ 0, float(val), 0.f }; + return std::is_integral::value + ? SliceRecord{coord_t(val), 0.f, 0.f} + : SliceRecord{0, float(val), 0.f}; } // This is a template method for searching the slice index either by @@ -241,11 +245,11 @@ protected: ~SLAPrintObject(); void config_apply(const ConfigBase &other, bool ignore_nonexistent = false) { this->m_config.apply(other, ignore_nonexistent); } - void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) - { this->m_config.apply_only(other, keys, ignore_nonexistent); } + void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) + { this->m_config.apply_only(other, keys, ignore_nonexistent); } void set_trafo(const Transform3d& trafo, bool left_handed) { - m_transformed_rmesh.invalidate([this, &trafo, left_handed](){ m_trafo = trafo; m_left_handed = left_handed; }); + m_transformed_rmesh.invalidate([this, &trafo, left_handed](){ m_trafo = trafo; m_left_handed = left_handed; }); } template inline void set_instances(InstVec&& instances) { m_instances = std::forward(instances); } @@ -322,37 +326,6 @@ struct SLAPrintStatistics } }; -// The implementation of creating zipped archives with wxWidgets -template<> class LayerWriter { - Zipper m_zip; -public: - - LayerWriter(const std::string& zipfile_path): m_zip(zipfile_path) {} - - void next_entry(const std::string& fname) { m_zip.add_entry(fname); } - - void binary_entry(const std::string& fname, - const std::uint8_t* buf, - size_t l) - { - m_zip.add_entry(fname, buf, l); - } - - template inline LayerWriter& operator<<(T&& arg) { - m_zip << std::forward(arg); return *this; - } - - bool is_ok() const { - return true; // m_zip blows up if something goes wrong... - } - - // After finalize, no writing to the archive will have an effect. The only - // valid operation is to dispose the object calling the destructor which - // should close the file. This method can throw and signal potential errors - // when flushing the archive. This is why its present. - void finalize() { m_zip.finalize(); } -}; - /** * @brief This class is the high level FSM for the SLA printing process. * @@ -376,20 +349,19 @@ public: void clear() override; bool empty() const override { return m_objects.empty(); } - ApplyStatus apply(const Model &model, const DynamicPrintConfig &config) override; + ApplyStatus apply(const Model &model, DynamicPrintConfig config) override; void set_task(const TaskParams ¶ms) override; void process() override; void finalize() override; - // Returns true if an object step is done on all objects and there's at least one object. + // Returns true if an object step is done on all objects and there's at least one object. bool is_step_done(SLAPrintObjectStep step) const; // Returns true if the last step was finished with success. bool finished() const override { return this->is_step_done(slaposSliceSupports) && this->Inherited::is_step_done(slapsRasterize); } - template inline void export_raster(const std::string& fpath, - const std::string& projectname = "") + const std::string& projectname = "") { - if(m_printer) m_printer->save(fpath, projectname); + if(m_printer) m_printer->save(fpath, projectname); } const PrintObjects& objects() const { return m_objects; } @@ -450,7 +422,7 @@ public: const std::vector& print_layers() const { return m_printer_input; } private: - using SLAPrinter = FilePrinter; + using SLAPrinter = sla::SLARasterWriter; using SLAPrinterPtr = std::unique_ptr; // Implement same logic as in SLAPrintObject @@ -475,16 +447,21 @@ private: // Estimated print time, material consumed. SLAPrintStatistics m_print_statistics; - - class StatusReporter { + + class StatusReporter + { double m_st = 0; + public: - void operator() (SLAPrint& p, double st, const std::string& msg, - unsigned flags = SlicingStatus::DEFAULT); + void operator()(SLAPrint & p, + double st, + const std::string &msg, + unsigned flags = SlicingStatus::DEFAULT, + const std::string &logmsg = ""); + double status() const { return m_st; } } m_report_status; - friend SLAPrintObject; }; diff --git a/src/libslic3r/Semver.cpp b/src/libslic3r/Semver.cpp new file mode 100644 index 0000000000..5d36b39f72 --- /dev/null +++ b/src/libslic3r/Semver.cpp @@ -0,0 +1,7 @@ +#include "libslic3r.h" + +namespace Slic3r { + +Semver SEMVER { SLIC3R_VERSION }; + +} diff --git a/src/slic3r/Utils/Semver.hpp b/src/libslic3r/Semver.hpp similarity index 97% rename from src/slic3r/Utils/Semver.hpp rename to src/libslic3r/Semver.hpp index 2fb4e3f4bf..a755becaa5 100644 --- a/src/slic3r/Utils/Semver.hpp +++ b/src/libslic3r/Semver.hpp @@ -137,6 +137,11 @@ public: Semver operator-(const Minor &b) const { Semver res(*this); return res -= b; } Semver operator-(const Patch &b) const { Semver res(*this); return res -= b; } + // Stream output + friend std::ostream& operator<<(std::ostream& os, const Semver &self) { + os << self.to_string(); + return os; + } private: semver_t ver; diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index 3a05e9d8af..c62736ffeb 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -153,24 +153,33 @@ SlicingParameters SlicingParameters::create_from_config( return params; } -// Convert layer_height_ranges to layer_height_profile. Both are referenced to z=0, meaning the raft layers are not accounted for +std::vector> layer_height_ranges(const t_layer_config_ranges &config_ranges) +{ + std::vector> out; + out.reserve(config_ranges.size()); + for (const auto &kvp : config_ranges) + out.emplace_back(kvp.first, kvp.second.option("layer_height")->getFloat()); + return out; +} + +// Convert layer_config_ranges to layer_height_profile. Both are referenced to z=0, meaning the raft layers are not accounted for // in the height profile and the printed object may be lifted by the raft thickness at the time of the G-code generation. std::vector layer_height_profile_from_ranges( const SlicingParameters &slicing_params, - const t_layer_height_ranges &layer_height_ranges) + const t_layer_config_ranges &layer_config_ranges) // #ys_FIXME_experiment { // 1) If there are any height ranges, trim one by the other to make them non-overlapping. Insert the 1st layer if fixed. std::vector> ranges_non_overlapping; - ranges_non_overlapping.reserve(layer_height_ranges.size() * 4); + ranges_non_overlapping.reserve(layer_config_ranges.size() * 4); // #ys_FIXME_experiment if (slicing_params.first_object_layer_height_fixed()) ranges_non_overlapping.push_back(std::pair( t_layer_height_range(0., slicing_params.first_object_layer_height), slicing_params.first_object_layer_height)); // The height ranges are sorted lexicographically by low / high layer boundaries. - for (t_layer_height_ranges::const_iterator it_range = layer_height_ranges.begin(); it_range != layer_height_ranges.end(); ++ it_range) { + for (t_layer_config_ranges::const_iterator it_range = layer_config_ranges.begin(); it_range != layer_config_ranges.end(); ++ it_range) { coordf_t lo = it_range->first.first; coordf_t hi = std::min(it_range->first.second, slicing_params.object_print_z_height()); - coordf_t height = it_range->second; + coordf_t height = it_range->second.option("layer_height")->getFloat(); if (! ranges_non_overlapping.empty()) // Trim current low with the last high. lo = std::max(lo, ranges_non_overlapping.back().first.second); @@ -187,7 +196,6 @@ std::vector layer_height_profile_from_ranges( coordf_t hi = it_range->first.second; coordf_t height = it_range->second; coordf_t last_z = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 2]; - coordf_t last_height = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 1]; if (lo > last_z + EPSILON) { // Insert a step of normal layer height. layer_height_profile.push_back(last_z); @@ -203,7 +211,6 @@ std::vector layer_height_profile_from_ranges( } coordf_t last_z = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 2]; - coordf_t last_height = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 1]; if (last_z < slicing_params.object_print_z_height()) { // Insert a step of normal layer height up to the object top. layer_height_profile.push_back(last_z); @@ -219,7 +226,7 @@ std::vector layer_height_profile_from_ranges( // Fill layer_height_profile by heights ensuring a prescribed maximum cusp height. std::vector layer_height_profile_adaptive( const SlicingParameters &slicing_params, - const t_layer_height_ranges &layer_height_ranges, + const t_layer_config_ranges & /* layer_config_ranges */, const ModelVolumePtrs &volumes) { // 1) Initialize the SlicingAdaptive class with the object meshes. @@ -227,7 +234,7 @@ std::vector layer_height_profile_adaptive( as.set_slicing_parameters(slicing_params); for (const ModelVolume *volume : volumes) if (volume->is_model_part()) - as.add_mesh(&volume->mesh); + as.add_mesh(&volume->mesh()); as.prepare(); // 2) Generate layers using the algorithm of @platsch @@ -245,7 +252,6 @@ std::vector layer_height_profile_adaptive( } coordf_t slice_z = slicing_params.first_object_layer_height; coordf_t height = slicing_params.first_object_layer_height; - coordf_t cusp_height = 0.; int current_facet = 0; while ((slice_z - height) <= slicing_params.object_print_z_height()) { height = 999; @@ -401,7 +407,6 @@ void adjust_layer_height_profile( } // Adjust height by layer_thickness_delta. coordf_t weight = std::abs(zz - z) < 0.5 * band_width ? (0.5 + 0.5 * cos(2. * M_PI * (zz - z) / band_width)) : 0.; - coordf_t height_new = height; switch (action) { case LAYER_HEIGHT_EDIT_ACTION_INCREASE: case LAYER_HEIGHT_EDIT_ACTION_DECREASE: diff --git a/src/libslic3r/Slicing.hpp b/src/libslic3r/Slicing.hpp index fa5a12f9ce..064363ec26 100644 --- a/src/libslic3r/Slicing.hpp +++ b/src/libslic3r/Slicing.hpp @@ -11,6 +11,8 @@ #include "libslic3r.h" #include "Utils.hpp" +#include "PrintConfig.hpp" + namespace Slic3r { @@ -128,15 +130,17 @@ inline bool equal_layering(const SlicingParameters &sp1, const SlicingParameters } typedef std::pair t_layer_height_range; -typedef std::map t_layer_height_ranges; +typedef std::map t_layer_config_ranges; + +extern std::vector> layer_height_ranges(const t_layer_config_ranges &config_ranges); extern std::vector layer_height_profile_from_ranges( const SlicingParameters &slicing_params, - const t_layer_height_ranges &layer_height_ranges); + const t_layer_config_ranges &layer_config_ranges); extern std::vector layer_height_profile_adaptive( const SlicingParameters &slicing_params, - const t_layer_height_ranges &layer_height_ranges, + const t_layer_config_ranges &layer_config_ranges, const ModelVolumePtrs &volumes); @@ -171,4 +175,9 @@ extern int generate_layer_height_texture( }; // namespace Slic3r +namespace cereal +{ + template void serialize(Archive& archive, Slic3r::t_layer_height_range &lhr) { archive(lhr.first, lhr.second); } +} + #endif /* slic3r_Slicing_hpp_ */ diff --git a/src/libslic3r/SlicingAdaptive.cpp b/src/libslic3r/SlicingAdaptive.cpp index 2ef4aec8c6..ad03b550b9 100644 --- a/src/libslic3r/SlicingAdaptive.cpp +++ b/src/libslic3r/SlicingAdaptive.cpp @@ -27,8 +27,8 @@ void SlicingAdaptive::prepare() nfaces_total += (*it_mesh)->stl.stats.number_of_facets; m_faces.reserve(nfaces_total); for (std::vector::const_iterator it_mesh = m_meshes.begin(); it_mesh != m_meshes.end(); ++ it_mesh) - for (int i = 0; i < (*it_mesh)->stl.stats.number_of_facets; ++ i) - m_faces.push_back((*it_mesh)->stl.facet_start + i); + for (const stl_facet &face : (*it_mesh)->stl.facet_start) + m_faces.emplace_back(&face); // 2) Sort faces lexicographically by their Z span. std::sort(m_faces.begin(), m_faces.end(), [](const stl_facet *f1, const stl_facet *f2) { diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index fde35ca1e4..505fdcaf58 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -361,17 +361,17 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) std::sort(layers_sorted.begin(), layers_sorted.end(), MyLayersPtrCompare()); int layer_id = 0; assert(object.support_layers().empty()); - for (int i = 0; i < int(layers_sorted.size());) { + for (size_t i = 0; i < layers_sorted.size();) { // Find the last layer with roughly the same print_z, find the minimum layer height of all. // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. - int j = i + 1; + size_t j = i + 1; coordf_t zmax = layers_sorted[i]->print_z + EPSILON; for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) ; // Assign an average print_z to the set of layers with nearly equal print_z. coordf_t zavg = 0.5 * (layers_sorted[i]->print_z + layers_sorted[j - 1]->print_z); coordf_t height_min = layers_sorted[i]->height; bool empty = true; - for (int u = i; u < j; ++u) { + for (size_t u = i; u < j; ++u) { MyLayer &layer = *layers_sorted[u]; if (! layer.polygons.empty()) empty = false; @@ -829,7 +829,7 @@ namespace SupportMaterialInternal { assert(expansion_scaled >= 0.f); for (const ExtrusionPath &ep : loop.paths) if (ep.role() == erOverhangPerimeter && ! ep.polyline.empty()) { - float exp = 0.5f * scale_(ep.width) + expansion_scaled; + float exp = 0.5f * (float)scale_(ep.width) + expansion_scaled; if (ep.is_closed()) { if (ep.size() >= 3) { // This is a complete loop. @@ -1042,7 +1042,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ float fw = float(layerm->flow(frExternalPerimeter).scaled_width()); no_interface_offset = (no_interface_offset == 0.f) ? fw : std::min(no_interface_offset, fw); float lower_layer_offset = - (layer_id < m_object_config->support_material_enforce_layers.value) ? + (layer_id < (size_t)m_object_config->support_material_enforce_layers.value) ? // Enforce a full possible support, ignore the overhang angle. 0.f : (threshold_rad > 0. ? @@ -1352,7 +1352,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ { // Find the span of layers, which are to be printed at the first layer height. int j = 0; - for (; j < contact_out.size() && contact_out[j]->print_z < m_slicing_params.first_print_layer_height + this->m_support_layer_height_min - EPSILON; ++ j); + for (; j < (int)contact_out.size() && contact_out[j]->print_z < m_slicing_params.first_print_layer_height + this->m_support_layer_height_min - EPSILON; ++ j); if (j > 0) { // Merge the contact_out layers (0) to (j - 1) into the contact_out[0]. MyLayer &dst = *contact_out.front(); @@ -1377,7 +1377,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // Find the span of layers closer than m_support_layer_height_min. int j = i + 1; coordf_t zmax = contact_out[i]->print_z + m_support_layer_height_min + EPSILON; - for (; j < contact_out.size() && contact_out[j]->print_z < zmax; ++ j) ; + for (; j < (int)contact_out.size() && contact_out[j]->print_z < zmax; ++ j) ; if (i + 1 < j) { // Merge the contact_out layers (i + 1) to (j - 1) into the contact_out[i]. MyLayer &dst = *contact_out[i]; @@ -1395,7 +1395,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ contact_out[k] = contact_out[i]; i = j; } - if (k < contact_out.size()) + if (k < (int)contact_out.size()) contact_out.erase(contact_out.begin() + k, contact_out.end()); } @@ -2214,7 +2214,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf // Expand the bases of the support columns in the 1st layer. columns_base->polygons = diff( offset(columns_base->polygons, inflate_factor_1st_layer), - offset(m_object->layers().front()->slices.expolygons, scale_(m_gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS)); + offset(m_object->layers().front()->slices.expolygons, (float)scale_(m_gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS)); if (contacts != nullptr) columns_base->polygons = diff(columns_base->polygons, interface_polygons); } @@ -2566,11 +2566,11 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const { // make more loops Polygons loop_polygons = loops0; - for (size_t i = 1; i < n_contact_loops; ++ i) + for (int i = 1; i < n_contact_loops; ++ i) polygons_append(loop_polygons, offset2( loops0, - - int(i) * flow.scaled_spacing() - 0.5f * flow.scaled_spacing(), + - i * flow.scaled_spacing() - 0.5f * flow.scaled_spacing(), 0.5f * flow.scaled_spacing())); // Clip such loops to the side oriented towards the object. // Collect split points, so they will be recognized after the clipping. @@ -3226,7 +3226,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( // TODO: use brim ordering algorithm Polygons to_infill_polygons = to_polygons(to_infill); // TODO: use offset2_ex() - to_infill = offset_ex(to_infill, - 0.4 * float(flow.scaled_spacing())); + to_infill = offset_ex(to_infill, - 0.4f * float(flow.scaled_spacing())); extrusion_entities_append_paths( base_layer.extrusions, to_polylines(std::move(to_infill_polygons)), diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index bffc45cde7..51d0920946 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -13,6 +13,10 @@ #define ENABLE_RENDER_SELECTION_CENTER 0 // Shows an imgui dialog with render related data #define ENABLE_RENDER_STATISTICS 0 +// Shows an imgui dialog with camera related data +#define ENABLE_CAMERA_STATISTICS 0 +// Render the picking pass instead of the main scene (use [T] key to toggle between regular rendering and picking pass only rendering) +#define ENABLE_RENDER_PICKING_PASS 0 //==================== @@ -28,22 +32,4 @@ #define ENABLE_NONCUSTOM_DATA_VIEW_RENDERING (0 && ENABLE_1_42_0_ALPHA1) -//==================== -// 1.42.0.alpha7 techs -//==================== -#define ENABLE_1_42_0_ALPHA7 1 - -// Printbed textures generated from svg files -#define ENABLE_TEXTURES_FROM_SVG (1 && ENABLE_1_42_0_ALPHA7) - - -//==================== -// 1.42.0.alpha8 techs -//==================== -#define ENABLE_1_42_0_ALPHA8 1 - -// Toolbars and Gizmos use icons imported from svg files -#define ENABLE_SVG_ICONS (1 && ENABLE_1_42_0_ALPHA8 && ENABLE_TEXTURES_FROM_SVG) - - #endif // _technologies_h_ diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 4d35cabca2..c2fcb11bd2 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -42,20 +42,17 @@ namespace Slic3r { -TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& facets) - : repaired(false) +TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& facets) : repaired(false) { - stl_initialize(&this->stl); stl_file &stl = this->stl; - stl.error = 0; stl.stats.type = inmemory; // count facets and allocate memory - stl.stats.number_of_facets = facets.size(); + stl.stats.number_of_facets = (uint32_t)facets.size(); stl.stats.original_num_facets = stl.stats.number_of_facets; stl_allocate(&stl); - for (uint32_t i = 0; i < stl.stats.number_of_facets; i++) { + for (uint32_t i = 0; i < stl.stats.number_of_facets; ++ i) { stl_facet facet; facet.vertex[0] = points[facets[i](0)].cast(); facet.vertex[1] = points[facets[i](1)].cast(); @@ -73,57 +70,37 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& f stl_get_size(&stl); } -TriangleMesh& TriangleMesh::operator=(const TriangleMesh &other) -{ - stl_close(&this->stl); - this->stl = other.stl; - this->repaired = other.repaired; - this->stl.heads = nullptr; - this->stl.tail = nullptr; - this->stl.error = other.stl.error; - if (other.stl.facet_start != nullptr) { - this->stl.facet_start = (stl_facet*)calloc(other.stl.stats.number_of_facets, sizeof(stl_facet)); - std::copy(other.stl.facet_start, other.stl.facet_start + other.stl.stats.number_of_facets, this->stl.facet_start); - } - if (other.stl.neighbors_start != nullptr) { - this->stl.neighbors_start = (stl_neighbors*)calloc(other.stl.stats.number_of_facets, sizeof(stl_neighbors)); - std::copy(other.stl.neighbors_start, other.stl.neighbors_start + other.stl.stats.number_of_facets, this->stl.neighbors_start); - } - if (other.stl.v_indices != nullptr) { - this->stl.v_indices = (v_indices_struct*)calloc(other.stl.stats.number_of_facets, sizeof(v_indices_struct)); - std::copy(other.stl.v_indices, other.stl.v_indices + other.stl.stats.number_of_facets, this->stl.v_indices); - } - if (other.stl.v_shared != nullptr) { - this->stl.v_shared = (stl_vertex*)calloc(other.stl.stats.shared_vertices, sizeof(stl_vertex)); - std::copy(other.stl.v_shared, other.stl.v_shared + other.stl.stats.shared_vertices, this->stl.v_shared); - } - return *this; -} - // #define SLIC3R_TRACE_REPAIR -void TriangleMesh::repair() +void TriangleMesh::repair(bool update_shared_vertices) { - if (this->repaired) return; - + if (this->repaired) { + if (update_shared_vertices) + this->require_shared_vertices(); + return; + } + // admesh fails when repairing empty meshes - if (this->stl.stats.number_of_facets == 0) return; + if (this->stl.stats.number_of_facets == 0) + return; BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() started"; - + // checking exact #ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_exact"; #endif /* SLIC3R_TRACE_REPAIR */ + assert(stl_validate(&this->stl)); stl_check_facets_exact(&stl); + assert(stl_validate(&this->stl)); stl.stats.facets_w_1_bad_edge = (stl.stats.connected_facets_2_edge - stl.stats.connected_facets_3_edge); stl.stats.facets_w_2_bad_edge = (stl.stats.connected_facets_1_edge - stl.stats.connected_facets_2_edge); stl.stats.facets_w_3_bad_edge = (stl.stats.number_of_facets - stl.stats.connected_facets_1_edge); // checking nearby //int last_edges_fixed = 0; - float tolerance = stl.stats.shortest_edge; - float increment = stl.stats.bounding_diameter / 10000.0; + float tolerance = (float)stl.stats.shortest_edge; + float increment = (float)stl.stats.bounding_diameter / 10000.0f; int iterations = 2; if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) { for (int i = 0; i < iterations; i++) { @@ -141,6 +118,7 @@ void TriangleMesh::repair() } } } + assert(stl_validate(&this->stl)); // remove_unconnected if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) { @@ -148,6 +126,7 @@ void TriangleMesh::repair() BOOST_LOG_TRIVIAL(trace) << "\tstl_remove_unconnected_facets"; #endif /* SLIC3R_TRACE_REPAIR */ stl_remove_unconnected_facets(&stl); + assert(stl_validate(&this->stl)); } // fill_holes @@ -168,28 +147,38 @@ void TriangleMesh::repair() BOOST_LOG_TRIVIAL(trace) << "\tstl_fix_normal_directions"; #endif /* SLIC3R_TRACE_REPAIR */ stl_fix_normal_directions(&stl); + assert(stl_validate(&this->stl)); // normal_values #ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_fix_normal_values"; #endif /* SLIC3R_TRACE_REPAIR */ stl_fix_normal_values(&stl); + assert(stl_validate(&this->stl)); // always calculate the volume and reverse all normals if volume is negative #ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_calculate_volume"; #endif /* SLIC3R_TRACE_REPAIR */ stl_calculate_volume(&stl); + assert(stl_validate(&this->stl)); // neighbors #ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_verify_neighbors"; #endif /* SLIC3R_TRACE_REPAIR */ stl_verify_neighbors(&stl); + assert(stl_validate(&this->stl)); this->repaired = true; BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() finished"; + + // This call should be quite cheap, a lot of code requires the indexed_triangle_set data structure, + // and it is risky to generate such a structure once the meshes are shared. Do it now. + this->its.clear(); + if (update_shared_vertices) + this->require_shared_vertices(); } float TriangleMesh::volume() @@ -249,20 +238,24 @@ bool TriangleMesh::needed_repair() const void TriangleMesh::WriteOBJFile(const char* output_file) { - stl_generate_shared_vertices(&stl); - stl_write_obj(&stl, output_file); + its_write_obj(this->its, output_file); } void TriangleMesh::scale(float factor) { stl_scale(&(this->stl), factor); - stl_invalidate_shared_vertices(&this->stl); + for (stl_vertex& v : this->its.vertices) + v *= factor; } void TriangleMesh::scale(const Vec3d &versor) { stl_scale_versor(&this->stl, versor.cast()); - stl_invalidate_shared_vertices(&this->stl); + for (stl_vertex& v : this->its.vertices) { + v.x() *= versor.x(); + v.y() *= versor.y(); + v.z() *= versor.z(); + } } void TriangleMesh::translate(float x, float y, float z) @@ -270,7 +263,9 @@ void TriangleMesh::translate(float x, float y, float z) if (x == 0.f && y == 0.f && z == 0.f) return; stl_translate_relative(&(this->stl), x, y, z); - stl_invalidate_shared_vertices(&this->stl); + stl_vertex shift(x, y, z); + for (stl_vertex& v : this->its.vertices) + v += shift; } void TriangleMesh::translate(const Vec3f &displacement) @@ -287,13 +282,15 @@ void TriangleMesh::rotate(float angle, const Axis &axis) angle = Slic3r::Geometry::rad2deg(angle); if (axis == X) { - stl_rotate_x(&(this->stl), angle); + stl_rotate_x(&this->stl, angle); + its_rotate_x(this->its, angle); } else if (axis == Y) { - stl_rotate_y(&(this->stl), angle); + stl_rotate_y(&this->stl, angle); + its_rotate_y(this->its, angle); } else if (axis == Z) { - stl_rotate_z(&(this->stl), angle); + stl_rotate_z(&this->stl, angle); + its_rotate_z(this->its, angle); } - stl_invalidate_shared_vertices(&this->stl); } void TriangleMesh::rotate(float angle, const Vec3d& axis) @@ -305,39 +302,49 @@ void TriangleMesh::rotate(float angle, const Vec3d& axis) Transform3d m = Transform3d::Identity(); m.rotate(Eigen::AngleAxisd(angle, axis_norm)); stl_transform(&stl, m); + its_transform(its, m); } void TriangleMesh::mirror(const Axis &axis) { if (axis == X) { stl_mirror_yz(&this->stl); + for (stl_vertex &v : this->its.vertices) + v(0) *= -1.0; } else if (axis == Y) { stl_mirror_xz(&this->stl); + for (stl_vertex &v : this->its.vertices) + v(1) *= -1.0; } else if (axis == Z) { stl_mirror_xy(&this->stl); + for (stl_vertex &v : this->its.vertices) + v(2) *= -1.0; } - stl_invalidate_shared_vertices(&this->stl); } void TriangleMesh::transform(const Transform3d& t, bool fix_left_handed) { stl_transform(&stl, t); - stl_invalidate_shared_vertices(&stl); + its_transform(its, t); if (fix_left_handed && t.matrix().block(0, 0, 3, 3).determinant() < 0.) { // Left handed transformation is being applied. It is a good idea to flip the faces and their normals. - this->repair(); + this->repair(false); stl_reverse_all_facets(&stl); + this->its.clear(); + this->require_shared_vertices(); } } void TriangleMesh::transform(const Matrix3d& m, bool fix_left_handed) { stl_transform(&stl, m); - stl_invalidate_shared_vertices(&stl); + its_transform(its, m); if (fix_left_handed && m.determinant() < 0.) { // Left handed transformation is being applied. It is a good idea to flip the faces and their normals. - this->repair(); + this->repair(false); stl_reverse_all_facets(&stl); + this->its.clear(); + this->require_shared_vertices(); } } @@ -355,7 +362,8 @@ void TriangleMesh::rotate(double angle, Point* center) return; Vec2f c = center->cast(); this->translate(-c(0), -c(1), 0); - stl_rotate_z(&(this->stl), (float)angle); + stl_rotate_z(&this->stl, (float)angle); + its_rotate_z(this->its, (float)angle); this->translate(c(0), c(1), 0); } @@ -435,9 +443,8 @@ TriangleMeshPtrs TriangleMesh::split() const TriangleMesh* mesh = new TriangleMesh; meshes.emplace_back(mesh); mesh->stl.stats.type = inmemory; - mesh->stl.stats.number_of_facets = facets.size(); + mesh->stl.stats.number_of_facets = (uint32_t)facets.size(); mesh->stl.stats.original_num_facets = mesh->stl.stats.number_of_facets; - stl_clear_error(&mesh->stl); stl_allocate(&mesh->stl); // Assign the facets to the new mesh. @@ -455,7 +462,7 @@ void TriangleMesh::merge(const TriangleMesh &mesh) { // reset stats and metadata int number_of_facets = this->stl.stats.number_of_facets; - stl_invalidate_shared_vertices(&this->stl); + this->its.clear(); this->repaired = false; // update facet count and allocate more memory @@ -477,13 +484,12 @@ ExPolygons TriangleMesh::horizontal_projection() const { Polygons pp; pp.reserve(this->stl.stats.number_of_facets); - for (uint32_t i = 0; i < this->stl.stats.number_of_facets; ++ i) { - stl_facet* facet = &this->stl.facet_start[i]; + for (const stl_facet &facet : this->stl.facet_start) { Polygon p; p.points.resize(3); - p.points[0] = Point::new_scale(facet->vertex[0](0), facet->vertex[0](1)); - p.points[1] = Point::new_scale(facet->vertex[1](0), facet->vertex[1](1)); - p.points[2] = Point::new_scale(facet->vertex[2](0), facet->vertex[2](1)); + p.points[0] = Point::new_scale(facet.vertex[0](0), facet.vertex[0](1)); + p.points[1] = Point::new_scale(facet.vertex[1](0), facet.vertex[1](1)); + p.points[2] = Point::new_scale(facet.vertex[2](0), facet.vertex[2](1)); p.make_counter_clockwise(); // do this after scaling, as winding order might change while doing that pp.emplace_back(p); } @@ -495,11 +501,10 @@ ExPolygons TriangleMesh::horizontal_projection() const // 2D convex hull of a 3D mesh projected into the Z=0 plane. Polygon TriangleMesh::convex_hull() { - this->require_shared_vertices(); Points pp; - pp.reserve(this->stl.stats.shared_vertices); - for (int i = 0; i < this->stl.stats.shared_vertices; ++ i) { - const stl_vertex &v = this->stl.v_shared[i]; + pp.reserve(this->its.vertices.size()); + for (size_t i = 0; i < this->its.vertices.size(); ++ i) { + const stl_vertex &v = this->its.vertices[i]; pp.emplace_back(Point::new_scale(v(0), v(1))); } return Slic3r::Geometry::convex_hull(pp); @@ -517,49 +522,47 @@ BoundingBoxf3 TriangleMesh::bounding_box() const BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d &trafo) const { BoundingBoxf3 bbox; - if (stl.v_shared == nullptr) { + if (this->its.vertices.empty()) { // Using the STL faces. - for (size_t i = 0; i < this->facets_count(); ++ i) { - const stl_facet &facet = this->stl.facet_start[i]; + for (const stl_facet &facet : this->stl.facet_start) for (size_t j = 0; j < 3; ++ j) bbox.merge(trafo * facet.vertex[j].cast()); - } } else { // Using the shared vertices should be a bit quicker than using the STL faces. - for (int i = 0; i < stl.stats.shared_vertices; ++ i) - bbox.merge(trafo * this->stl.v_shared[i].cast()); + for (const stl_vertex &v : this->its.vertices) + bbox.merge(trafo * v.cast()); } return bbox; } TriangleMesh TriangleMesh::convex_hull_3d() const { - // Helper struct for qhull: - struct PointForQHull{ - PointForQHull(float x_p, float y_p, float z_p) : x((realT)x_p), y((realT)y_p), z((realT)z_p) {} - realT x, y, z; - }; - std::vector src_vertices; - - // We will now fill the vector with input points for computation: - stl_facet* facet_ptr = stl.facet_start; - while (facet_ptr < stl.facet_start + stl.stats.number_of_facets) - { - for (int i = 0; i < 3; ++i) - { - const stl_vertex& v = facet_ptr->vertex[i]; - src_vertices.emplace_back(v(0), v(1), v(2)); - } - - facet_ptr += 1; - } - // The qhull call: orgQhull::Qhull qhull; qhull.disableOutputStream(); // we want qhull to be quiet - try + std::vector src_vertices; + try { - qhull.runQhull("", 3, (int)src_vertices.size(), (const realT*)(src_vertices.data()), "Qt"); + if (this->has_shared_vertices()) { +#if REALfloat + qhull.runQhull("", 3, (int)this->its.vertices.size(), (const realT*)(this->its.vertices.front().data()), "Qt"); +#else + src_vertices.reserve(this->its.vertices.size() * 3); + // We will now fill the vector with input points for computation: + for (const stl_vertex &v : this->its.vertices) + for (int i = 0; i < 3; ++ i) + src_vertices.emplace_back(v(i)); + qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt"); +#endif + } else { + src_vertices.reserve(this->stl.facet_start.size() * 9); + // We will now fill the vector with input points for computation: + for (const stl_facet &f : this->stl.facet_start) + for (int i = 0; i < 3; ++ i) + for (int j = 0; j < 3; ++ j) + src_vertices.emplace_back(f.vertex[i](j)); + qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt"); + } } catch (...) { @@ -578,7 +581,7 @@ TriangleMesh TriangleMesh::convex_hull_3d() const { // iterate through facet's vertices orgQhull::QhullPoint p = vertices[i].point(); - const float* coords = p.coordinates(); + const auto* coords = p.coordinates(); dst_vertices.emplace_back(coords[0], coords[1], coords[2]); } unsigned int size = (unsigned int)dst_vertices.size(); @@ -587,37 +590,57 @@ TriangleMesh TriangleMesh::convex_hull_3d() const TriangleMesh output_mesh(dst_vertices, facets); output_mesh.repair(); - output_mesh.require_shared_vertices(); return output_mesh; } void TriangleMesh::require_shared_vertices() { BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - start"; - if (!this->repaired) + assert(stl_validate(&this->stl)); + if (! this->repaired) this->repair(); - if (this->stl.v_shared == NULL) { + if (this->its.vertices.empty()) { BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - stl_generate_shared_vertices"; - stl_generate_shared_vertices(&(this->stl)); + stl_generate_shared_vertices(&this->stl, this->its); } -#ifdef _DEBUG - // Verify validity of neighborship data. - for (int facet_idx = 0; facet_idx < stl.stats.number_of_facets; ++facet_idx) { - const stl_neighbors &nbr = stl.neighbors_start[facet_idx]; - const int *vertices = stl.v_indices[facet_idx].vertex; - for (int nbr_idx = 0; nbr_idx < 3; ++nbr_idx) { - int nbr_face = this->stl.neighbors_start[facet_idx].neighbor[nbr_idx]; - if (nbr_face != -1) { - assert( - (stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == vertices[(nbr_idx + 1) % 3] && stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == vertices[nbr_idx]) || - (stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == vertices[(nbr_idx + 1) % 3] && stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == vertices[nbr_idx])); - } - } - } -#endif /* _DEBUG */ + assert(stl_validate(&this->stl, this->its)); BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end"; } +size_t TriangleMesh::memsize() const +{ + size_t memsize = 8 + this->stl.memsize() + this->its.memsize(); + return memsize; +} + +// Release optional data from the mesh if the object is on the Undo / Redo stack only. Returns the amount of memory released. +size_t TriangleMesh::release_optional() +{ + size_t memsize_released = sizeof(stl_neighbors) * this->stl.neighbors_start.size() + this->its.memsize(); + // The indexed triangle set may be recalculated using the stl_generate_shared_vertices() function. + this->its.clear(); + // The neighbors structure may be recalculated using the stl_check_facets_exact() function. + this->stl.neighbors_start.clear(); + return memsize_released; +} + +// Restore optional data possibly released by release_optional(). +void TriangleMesh::restore_optional() +{ + if (! this->stl.facet_start.empty()) { + // Save the old stats before calling stl_check_faces_exact, as it may modify the statistics. + stl_stats stats = this->stl.stats; + if (this->stl.neighbors_start.empty()) { + stl_reallocate(&this->stl); + stl_check_facets_exact(&this->stl); + } + if (this->its.vertices.empty()) + stl_generate_shared_vertices(&this->stl, this->its); + // Restore the old statistics. + this->stl.stats = stats; + } +} + void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callback_type throw_on_cancel) { mesh = _mesh; @@ -626,10 +649,9 @@ void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callbac throw_on_cancel(); facets_edges.assign(_mesh->stl.stats.number_of_facets * 3, -1); - v_scaled_shared.assign(_mesh->stl.v_shared, _mesh->stl.v_shared + _mesh->stl.stats.shared_vertices); - // Scale the copied vertices. - for (int i = 0; i < this->mesh->stl.stats.shared_vertices; ++ i) - this->v_scaled_shared[i] *= float(1. / SCALING_FACTOR); + v_scaled_shared.assign(_mesh->its.vertices.size(), stl_vertex()); + for (size_t i = 0; i < v_scaled_shared.size(); ++ i) + this->v_scaled_shared[i] = _mesh->its.vertices[i] / float(SCALING_FACTOR); // Create a mapping from triangle edge into face. struct EdgeToFace { @@ -649,8 +671,8 @@ void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callbac for (uint32_t facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx) for (int i = 0; i < 3; ++ i) { EdgeToFace &e2f = edges_map[facet_idx*3+i]; - e2f.vertex_low = this->mesh->stl.v_indices[facet_idx].vertex[i]; - e2f.vertex_high = this->mesh->stl.v_indices[facet_idx].vertex[(i + 1) % 3]; + e2f.vertex_low = this->mesh->its.indices[facet_idx][i]; + e2f.vertex_high = this->mesh->its.indices[facet_idx][(i + 1) % 3]; e2f.face = facet_idx; // 1 based indexing, to be always strictly positive. e2f.face_edge = i + 1; @@ -818,7 +840,7 @@ void TriangleMeshSlicer::slice(const std::vector &z, std::vector* lines, boost::mutex* lines_mutex, const std::vector &z) const { - const stl_facet &facet = m_use_quaternion ? this->mesh->stl.facet_start[facet_idx].rotated(m_quaternion) : this->mesh->stl.facet_start[facet_idx]; + const stl_facet &facet = m_use_quaternion ? (this->mesh->stl.facet_start.data() + facet_idx)->rotated(m_quaternion) : *(this->mesh->stl.facet_start.data() + facet_idx); // find facet extents const float min_z = fminf(facet.vertex[0](2), fminf(facet.vertex[1](2), facet.vertex[2](2))); @@ -887,7 +909,7 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( // Reorder vertices so that the first one is the one with lowest Z. // This is needed to get all intersection lines in a consistent order // (external on the right of the line) - const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex; + const stl_triangle_vertex_indices &vertices = this->mesh->its.indices[facet_idx]; int i = (facet.vertex[1].z() == min_z) ? 1 : ((facet.vertex[2].z() == min_z) ? 2 : 0); // These are used only if the cut plane is tilted: @@ -1714,7 +1736,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - slicing object"; float scaled_z = scale_(z); for (uint32_t facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx) { - stl_facet* facet = &this->mesh->stl.facet_start[facet_idx]; + const stl_facet* facet = &this->mesh->stl.facet_start[facet_idx]; // find facet extents float min_z = std::min(facet->vertex[0](2), std::min(facet->vertex[1](2), facet->vertex[2](2))); @@ -1736,10 +1758,12 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) if (min_z > z || (min_z == z && max_z > z)) { // facet is above the cut plane and does not belong to it - if (upper != NULL) stl_add_facet(&upper->stl, facet); + if (upper != nullptr) + stl_add_facet(&upper->stl, facet); } else if (max_z < z || (max_z == z && min_z < z)) { // facet is below the cut plane and does not belong to it - if (lower != NULL) stl_add_facet(&lower->stl, facet); + if (lower != nullptr) + stl_add_facet(&lower->stl, facet); } else if (min_z < z && max_z > z) { // Facet is cut by the slicing plane. @@ -1786,22 +1810,24 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) quadrilateral[1].vertex[2] = v0v1; if (v0(2) > z) { - if (upper != NULL) stl_add_facet(&upper->stl, &triangle); - if (lower != NULL) { + if (upper != nullptr) + stl_add_facet(&upper->stl, &triangle); + if (lower != nullptr) { stl_add_facet(&lower->stl, &quadrilateral[0]); stl_add_facet(&lower->stl, &quadrilateral[1]); } } else { - if (upper != NULL) { + if (upper != nullptr) { stl_add_facet(&upper->stl, &quadrilateral[0]); stl_add_facet(&upper->stl, &quadrilateral[1]); } - if (lower != NULL) stl_add_facet(&lower->stl, &triangle); + if (lower != nullptr) + stl_add_facet(&lower->stl, &triangle); } } } - if (upper != NULL) { + if (upper != nullptr) { BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating upper part"; ExPolygons section; this->make_expolygons_simple(upper_lines, §ion); @@ -1815,7 +1841,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) } } - if (lower != NULL) { + if (lower != nullptr) { BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating lower part"; ExPolygons section; this->make_expolygons_simple(lower_lines, §ion); @@ -1905,10 +1931,10 @@ TriangleMesh make_cylinder(double r, double h, double fa) //FIXME better to discretize an Icosahedron recursively http://www.songho.ca/opengl/gl_sphere.html TriangleMesh make_sphere(double radius, double fa) { - int sectorCount = ceil(2. * M_PI / fa); - int stackCount = ceil(M_PI / fa); - float sectorStep = 2. * M_PI / sectorCount; - float stackStep = M_PI / stackCount; + int sectorCount = int(ceil(2. * M_PI / fa)); + int stackCount = int(ceil(M_PI / fa)); + float sectorStep = float(2. * M_PI / sectorCount); + float stackStep = float(M_PI / stackCount); Pointf3s vertices; vertices.reserve((stackCount - 1) * sectorCount + 2); diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index c284f6482b..81390b79b0 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -21,19 +21,13 @@ typedef std::vector TriangleMeshPtrs; class TriangleMesh { public: - TriangleMesh() : repaired(false) { stl_initialize(&this->stl); } + TriangleMesh() : repaired(false) {} TriangleMesh(const Pointf3s &points, const std::vector &facets); - TriangleMesh(const TriangleMesh &other) : repaired(false) { stl_initialize(&this->stl); *this = other; } - TriangleMesh(TriangleMesh &&other) : repaired(false) { stl_initialize(&this->stl); this->swap(other); } - ~TriangleMesh() { clear(); } - TriangleMesh& operator=(const TriangleMesh &other); - TriangleMesh& operator=(TriangleMesh &&other) { this->swap(other); return *this; } - void clear() { stl_close(&this->stl); this->repaired = false; } - void swap(TriangleMesh &other) { std::swap(this->stl, other.stl); std::swap(this->repaired, other.repaired); } - void ReadSTLFile(const char* input_file) { stl_open(&stl, input_file); } - void write_ascii(const char* output_file) { stl_write_ascii(&this->stl, output_file, ""); } - void write_binary(const char* output_file) { stl_write_binary(&this->stl, output_file, ""); } - void repair(); + void clear() { this->stl.clear(); this->its.clear(); this->repaired = false; } + bool ReadSTLFile(const char* input_file) { return stl_open(&stl, input_file); } + bool write_ascii(const char* output_file) { return stl_write_ascii(&this->stl, output_file, ""); } + bool write_binary(const char* output_file) { return stl_write_binary(&this->stl, output_file, ""); } + void repair(bool update_shared_vertices = true); float volume(); void check_topology(); bool is_manifold() const { return this->stl.stats.connected_facets_3_edge == (int)this->stl.stats.number_of_facets; } @@ -58,7 +52,7 @@ public: TriangleMeshPtrs split() const; void merge(const TriangleMesh &mesh); ExPolygons horizontal_projection() const; - const float* first_vertex() const { return this->stl.facet_start ? &this->stl.facet_start->vertex[0](0) : nullptr; } + const float* first_vertex() const { return this->stl.facet_start.empty() ? nullptr : &this->stl.facet_start.front().vertex[0](0); } // 2D convex hull of a 3D mesh projected into the Z=0 plane. Polygon convex_hull(); BoundingBoxf3 bounding_box() const; @@ -69,12 +63,19 @@ public: void reset_repair_stats(); bool needed_repair() const; void require_shared_vertices(); - bool has_shared_vertices() const { return stl.v_shared != NULL; } + bool has_shared_vertices() const { return ! this->its.vertices.empty(); } size_t facets_count() const { return this->stl.stats.number_of_facets; } bool empty() const { return this->facets_count() == 0; } bool is_splittable() const; + // Estimate of the memory occupied by this structure, important for keeping an eye on the Undo / Redo stack allocation. + size_t memsize() const; + // Release optional data from the mesh if the object is on the Undo / Redo stack only. Returns the amount of memory released. + size_t release_optional(); + // Restore optional data possibly released by release_optional(). + void restore_optional(); stl_file stl; + indexed_triangle_set its; bool repaired; private: @@ -200,4 +201,24 @@ TriangleMesh make_sphere(double rho, double fa=(2*PI/360)); } +// Serialization through the Cereal library +#include +namespace cereal { + template struct specialize {}; + template void load(Archive &archive, Slic3r::TriangleMesh &mesh) { + stl_file &stl = mesh.stl; + stl.stats.type = inmemory; + archive(stl.stats.number_of_facets, stl.stats.original_num_facets); + stl_allocate(&stl); + archive.loadBinary((char*)stl.facet_start.data(), stl.facet_start.size() * 50); + stl_get_size(&stl); + mesh.repair(); + } + template void save(Archive &archive, const Slic3r::TriangleMesh &mesh) { + const stl_file& stl = mesh.stl; + archive(stl.stats.number_of_facets, stl.stats.original_num_facets); + archive.saveBinary((char*)stl.facet_start.data(), stl.facet_start.size() * 50); + } +} + #endif diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index e76fccdb8a..2b1fdb2417 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -18,9 +18,12 @@ extern void trace(unsigned int level, const char *message); // Format memory allocated, separate thousands by comma. extern std::string format_memsize_MB(size_t n); // Return string to be added to the boost::log output to inform about the current process memory allocation. -// The string is non-empty only if the loglevel >= info (3). -extern std::string log_memory_info(); +// The string is non-empty if the loglevel >= info (3) or ignore_loglevel==true. +// Latter is used to get the memory info from SysInfoDialog. +extern std::string log_memory_info(bool ignore_loglevel = false); extern void disable_multi_threading(); +// Returns the size of physical memory (RAM) in bytes. +extern size_t total_physical_memory(); // Set a path with GUI resource files. void set_var_dir(const std::string &path); @@ -58,7 +61,7 @@ extern std::string normalize_utf8_nfc(const char *src); // Safely rename a file even if the target exists. // On Windows, the file explorer (or anti-virus or whatever else) often locks the file // for a short while, so the file may not be movable. Retry while we see recoverable errors. -extern int rename_file(const std::string &from, const std::string &to); +extern std::error_code rename_file(const std::string &from, const std::string &to); // Copy a file, adjust the access attributes, so that the target is writable. extern int copy_file(const std::string &from, const std::string &to); @@ -167,10 +170,10 @@ template size_t next_highest_power_of_2(T v, extern std::string xml_escape(std::string text); -#if defined __GNUC__ & __GNUC__ < 5 +#if defined __GNUC__ && __GNUC__ < 5 && !defined __clang__ // Older GCCs don't have std::is_trivially_copyable // cf. https://gcc.gnu.org/onlinedocs/gcc-4.9.4/libstdc++/manual/manual/status.html#status.iso.2011 -#warning "GCC version < 5, faking std::is_trivially_copyable" +// #warning "GCC version < 5, faking std::is_trivially_copyable" template struct IsTriviallyCopyable { static constexpr bool value = true; }; #else template struct IsTriviallyCopyable : public std::is_trivially_copyable {}; @@ -182,7 +185,7 @@ class ScopeGuard public: typedef std::function Closure; private: - bool committed; +// bool committed; Closure closure; public: diff --git a/src/libslic3r/Zipper.cpp b/src/libslic3r/Zipper.cpp index e821c29689..348be49ccb 100644 --- a/src/libslic3r/Zipper.cpp +++ b/src/libslic3r/Zipper.cpp @@ -1,9 +1,8 @@ #include #include "Zipper.hpp" -#include +#include "miniz_extension.hpp" #include -#include #include "I18N.hpp" //! macro used to mark string used at localization, @@ -124,16 +123,9 @@ Zipper::Zipper(const std::string &zipfname, e_compression compression) memset(&m_impl->arch, 0, sizeof(m_impl->arch)); - FILE *f = boost::nowide::fopen(zipfname.c_str(), "wb"); - - if (f == nullptr) { - m_impl->arch.m_last_error = MZ_ZIP_FILE_OPEN_FAILED; + if (!open_zip_writer(&m_impl->arch, zipfname)) { m_impl->blow_up(); } - - // Initialize the archive data - if(!mz_zip_writer_init_cfile(&m_impl->arch, f, 0)) - m_impl->blow_up(); } Zipper::~Zipper() @@ -148,13 +140,9 @@ Zipper::~Zipper() BOOST_LOG_TRIVIAL(error) << m_impl->formatted_errorstr(); } - FILE *f = mz_zip_get_cfile(&m_impl->arch); - // The file should be closed no matter what... - if(!mz_zip_writer_end(&m_impl->arch)) + if(!close_zip_writer(&m_impl->arch)) BOOST_LOG_TRIVIAL(error) << m_impl->formatted_errorstr(); - - if(f != nullptr) fclose(f); } Zipper::Zipper(Zipper &&m): diff --git a/src/libslic3r/Zipper.hpp b/src/libslic3r/Zipper.hpp index 7d95ffdac7..a574de9596 100644 --- a/src/libslic3r/Zipper.hpp +++ b/src/libslic3r/Zipper.hpp @@ -1,6 +1,7 @@ #ifndef ZIPPER_HPP #define ZIPPER_HPP +#include #include #include diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 560d746962..afbf94fa39 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -19,6 +19,7 @@ #include #include "Technologies.hpp" +#include "Semver.hpp" typedef int32_t coord_t; typedef double coordf_t; @@ -48,10 +49,19 @@ typedef double coordf_t; //FIXME Better to use an inline function with an explicit return type. //inline coord_t scale_(coordf_t v) { return coord_t(floor(v / SCALING_FACTOR + 0.5f)); } #define scale_(val) ((val) / SCALING_FACTOR) + #define SCALED_EPSILON scale_(EPSILON) #define SLIC3R_DEBUG_OUT_PATH_PREFIX "out/" +#if defined(_MSC_VER) && _MSC_VER < 1900 +# define SLIC3R_CONSTEXPR +# define SLIC3R_NOEXCEPT +#else +#define SLIC3R_CONSTEXPR constexpr +#define SLIC3R_NOEXCEPT noexcept +#endif + inline std::string debug_out_path(const char *name, ...) { char buffer[2048]; @@ -83,6 +93,8 @@ inline std::string debug_out_path(const char *name, ...) namespace Slic3r { +extern Semver SEMVER; + template inline T unscale(Q v) { return T(v) * T(SCALING_FACTOR); } diff --git a/src/libslic3r/miniz_extension.cpp b/src/libslic3r/miniz_extension.cpp new file mode 100644 index 0000000000..e87b1a552e --- /dev/null +++ b/src/libslic3r/miniz_extension.cpp @@ -0,0 +1,59 @@ +#include "miniz_extension.hpp" + +#if defined(_MSC_VER) || defined(__MINGW64__) +#include "boost/nowide/cstdio.hpp" +#endif + +namespace Slic3r { + +namespace { +bool open_zip(mz_zip_archive *zip, const char *fname, bool isread) +{ + if (!zip) return false; + const char *mode = isread ? "rb" : "wb"; + + FILE *f = nullptr; +#if defined(_MSC_VER) || defined(__MINGW64__) + f = boost::nowide::fopen(fname, mode); +#elif defined(__GNUC__) && defined(_LARGEFILE64_SOURCE) + f = fopen64(fname, mode); +#else + f = fopen(fname, mode); +#endif + + if (!f) { + zip->m_last_error = MZ_ZIP_FILE_OPEN_FAILED; + return false; + } + + return isread ? mz_zip_reader_init_cfile(zip, f, 0, 0) + : mz_zip_writer_init_cfile(zip, f, 0); +} + +bool close_zip(mz_zip_archive *zip, bool isread) +{ + bool ret = false; + if (zip) { + FILE *f = mz_zip_get_cfile(zip); + ret = bool(isread ? mz_zip_reader_end(zip) + : mz_zip_writer_end(zip)); + if (f) fclose(f); + } + return ret; +} +} + +bool open_zip_reader(mz_zip_archive *zip, const std::string &fname) +{ + return open_zip(zip, fname.c_str(), true); +} + +bool open_zip_writer(mz_zip_archive *zip, const std::string &fname) +{ + return open_zip(zip, fname.c_str(), false); +} + +bool close_zip_reader(mz_zip_archive *zip) { return close_zip(zip, true); } +bool close_zip_writer(mz_zip_archive *zip) { return close_zip(zip, false); } + +} diff --git a/src/libslic3r/miniz_extension.hpp b/src/libslic3r/miniz_extension.hpp new file mode 100644 index 0000000000..8d0967cbcc --- /dev/null +++ b/src/libslic3r/miniz_extension.hpp @@ -0,0 +1,16 @@ +#ifndef MINIZ_EXTENSION_HPP +#define MINIZ_EXTENSION_HPP + +#include +#include + +namespace Slic3r { + +bool open_zip_reader(mz_zip_archive *zip, const std::string &fname_utf8); +bool open_zip_writer(mz_zip_archive *zip, const std::string &fname_utf8); +bool close_zip_reader(mz_zip_archive *zip); +bool close_zip_writer(mz_zip_archive *zip); + +} + +#endif // MINIZ_EXTENSION_HPP diff --git a/src/libslic3r/pchheader.hpp b/src/libslic3r/pchheader.hpp index b27dfe6a2d..c0ffe21083 100644 --- a/src/libslic3r/pchheader.hpp +++ b/src/libslic3r/pchheader.hpp @@ -100,7 +100,10 @@ #include #include -#include +#include + +#include +#include #include "BoundingBox.hpp" #include "ClipperUtils.hpp" diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 3482f708ad..e26ed38394 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -7,10 +7,19 @@ #include #ifdef WIN32 -#include -#include + #include + #include #else -#include + #include + #include + #include + #include + #ifdef BSD + #include + #endif + #ifdef __APPLE__ + #include + #endif #endif #include @@ -24,7 +33,6 @@ #include #include #include -#include #include #include @@ -84,10 +92,10 @@ unsigned get_logging_level() } // Force set_logging_level(<=error) after loading of the DLL. -// Switch boost::filesystem to utf8. +// This is currently only needed if libslic3r is loaded as a shared library into Perl interpreter +// to perform unit and integration tests. static struct RunOnInit { RunOnInit() { - boost::nowide::nowide_filesystem(); set_logging_level(1); } } g_RunOnInit; @@ -165,67 +173,247 @@ const std::string& data_dir() return g_data_dir; } +#ifdef _WIN32 +// The following helpers are borrowed from the LLVM project https://github.com/llvm +namespace WindowsSupport +{ + template + class ScopedHandle { + typedef typename HandleTraits::handle_type handle_type; + handle_type Handle; + ScopedHandle(const ScopedHandle &other) = delete; + void operator=(const ScopedHandle &other) = delete; + public: + ScopedHandle() : Handle(HandleTraits::GetInvalid()) {} + explicit ScopedHandle(handle_type h) : Handle(h) {} + ~ScopedHandle() { if (HandleTraits::IsValid(Handle)) HandleTraits::Close(Handle); } + handle_type take() { + handle_type t = Handle; + Handle = HandleTraits::GetInvalid(); + return t; + } + ScopedHandle &operator=(handle_type h) { + if (HandleTraits::IsValid(Handle)) + HandleTraits::Close(Handle); + Handle = h; + return *this; + } + // True if Handle is valid. + explicit operator bool() const { return HandleTraits::IsValid(Handle) ? true : false; } + operator handle_type() const { return Handle; } + }; + + struct CommonHandleTraits { + typedef HANDLE handle_type; + static handle_type GetInvalid() { return INVALID_HANDLE_VALUE; } + static void Close(handle_type h) { ::CloseHandle(h); } + static bool IsValid(handle_type h) { return h != GetInvalid(); } + }; + + typedef ScopedHandle ScopedFileHandle; + + std::error_code map_windows_error(unsigned windows_error_code) + { + #define MAP_ERR_TO_COND(x, y) case x: return std::make_error_code(std::errc::y) + switch (windows_error_code) { + MAP_ERR_TO_COND(ERROR_ACCESS_DENIED, permission_denied); + MAP_ERR_TO_COND(ERROR_ALREADY_EXISTS, file_exists); + MAP_ERR_TO_COND(ERROR_BAD_UNIT, no_such_device); + MAP_ERR_TO_COND(ERROR_BUFFER_OVERFLOW, filename_too_long); + MAP_ERR_TO_COND(ERROR_BUSY, device_or_resource_busy); + MAP_ERR_TO_COND(ERROR_BUSY_DRIVE, device_or_resource_busy); + MAP_ERR_TO_COND(ERROR_CANNOT_MAKE, permission_denied); + MAP_ERR_TO_COND(ERROR_CANTOPEN, io_error); + MAP_ERR_TO_COND(ERROR_CANTREAD, io_error); + MAP_ERR_TO_COND(ERROR_CANTWRITE, io_error); + MAP_ERR_TO_COND(ERROR_CURRENT_DIRECTORY, permission_denied); + MAP_ERR_TO_COND(ERROR_DEV_NOT_EXIST, no_such_device); + MAP_ERR_TO_COND(ERROR_DEVICE_IN_USE, device_or_resource_busy); + MAP_ERR_TO_COND(ERROR_DIR_NOT_EMPTY, directory_not_empty); + MAP_ERR_TO_COND(ERROR_DIRECTORY, invalid_argument); + MAP_ERR_TO_COND(ERROR_DISK_FULL, no_space_on_device); + MAP_ERR_TO_COND(ERROR_FILE_EXISTS, file_exists); + MAP_ERR_TO_COND(ERROR_FILE_NOT_FOUND, no_such_file_or_directory); + MAP_ERR_TO_COND(ERROR_HANDLE_DISK_FULL, no_space_on_device); + MAP_ERR_TO_COND(ERROR_INVALID_ACCESS, permission_denied); + MAP_ERR_TO_COND(ERROR_INVALID_DRIVE, no_such_device); + MAP_ERR_TO_COND(ERROR_INVALID_FUNCTION, function_not_supported); + MAP_ERR_TO_COND(ERROR_INVALID_HANDLE, invalid_argument); + MAP_ERR_TO_COND(ERROR_INVALID_NAME, invalid_argument); + MAP_ERR_TO_COND(ERROR_LOCK_VIOLATION, no_lock_available); + MAP_ERR_TO_COND(ERROR_LOCKED, no_lock_available); + MAP_ERR_TO_COND(ERROR_NEGATIVE_SEEK, invalid_argument); + MAP_ERR_TO_COND(ERROR_NOACCESS, permission_denied); + MAP_ERR_TO_COND(ERROR_NOT_ENOUGH_MEMORY, not_enough_memory); + MAP_ERR_TO_COND(ERROR_NOT_READY, resource_unavailable_try_again); + MAP_ERR_TO_COND(ERROR_OPEN_FAILED, io_error); + MAP_ERR_TO_COND(ERROR_OPEN_FILES, device_or_resource_busy); + MAP_ERR_TO_COND(ERROR_OUTOFMEMORY, not_enough_memory); + MAP_ERR_TO_COND(ERROR_PATH_NOT_FOUND, no_such_file_or_directory); + MAP_ERR_TO_COND(ERROR_BAD_NETPATH, no_such_file_or_directory); + MAP_ERR_TO_COND(ERROR_READ_FAULT, io_error); + MAP_ERR_TO_COND(ERROR_RETRY, resource_unavailable_try_again); + MAP_ERR_TO_COND(ERROR_SEEK, io_error); + MAP_ERR_TO_COND(ERROR_SHARING_VIOLATION, permission_denied); + MAP_ERR_TO_COND(ERROR_TOO_MANY_OPEN_FILES, too_many_files_open); + MAP_ERR_TO_COND(ERROR_WRITE_FAULT, io_error); + MAP_ERR_TO_COND(ERROR_WRITE_PROTECT, permission_denied); + MAP_ERR_TO_COND(WSAEACCES, permission_denied); + MAP_ERR_TO_COND(WSAEBADF, bad_file_descriptor); + MAP_ERR_TO_COND(WSAEFAULT, bad_address); + MAP_ERR_TO_COND(WSAEINTR, interrupted); + MAP_ERR_TO_COND(WSAEINVAL, invalid_argument); + MAP_ERR_TO_COND(WSAEMFILE, too_many_files_open); + MAP_ERR_TO_COND(WSAENAMETOOLONG, filename_too_long); + default: + return std::error_code(windows_error_code, std::system_category()); + } + #undef MAP_ERR_TO_COND + } + + static std::error_code rename_internal(HANDLE from_handle, const std::wstring &wide_to, bool replace_if_exists) + { + std::vector rename_info_buf(sizeof(FILE_RENAME_INFO) - sizeof(wchar_t) + (wide_to.size() * sizeof(wchar_t))); + FILE_RENAME_INFO &rename_info = *reinterpret_cast(rename_info_buf.data()); + rename_info.ReplaceIfExists = replace_if_exists; + rename_info.RootDirectory = 0; + rename_info.FileNameLength = DWORD(wide_to.size() * sizeof(wchar_t)); + std::copy(wide_to.begin(), wide_to.end(), &rename_info.FileName[0]); + + ::SetLastError(ERROR_SUCCESS); + if (! ::SetFileInformationByHandle(from_handle, FileRenameInfo, &rename_info, (DWORD)rename_info_buf.size())) { + unsigned Error = GetLastError(); + if (Error == ERROR_SUCCESS) + Error = ERROR_CALL_NOT_IMPLEMENTED; // Wine doesn't always set error code. + return map_windows_error(Error); + } + + return std::error_code(); + } + + static std::error_code real_path_from_handle(HANDLE H, std::wstring &buffer) + { + buffer.resize(MAX_PATH + 1); + DWORD CountChars = ::GetFinalPathNameByHandleW(H, (LPWSTR)buffer.data(), (DWORD)buffer.size() - 1, FILE_NAME_NORMALIZED); + if (CountChars > buffer.size()) { + // The buffer wasn't big enough, try again. In this case the return value *does* indicate the size of the null terminator. + buffer.resize((size_t)CountChars); + CountChars = ::GetFinalPathNameByHandleW(H, (LPWSTR)buffer.data(), (DWORD)buffer.size() - 1, FILE_NAME_NORMALIZED); + } + if (CountChars == 0) + return map_windows_error(GetLastError()); + buffer.resize(CountChars); + return std::error_code(); + } + + std::error_code rename(const std::string &from, const std::string &to) + { + // Convert to utf-16. + std::wstring wide_from = boost::nowide::widen(from); + std::wstring wide_to = boost::nowide::widen(to); + + ScopedFileHandle from_handle; + // Retry this a few times to defeat badly behaved file system scanners. + for (unsigned retry = 0; retry != 200; ++ retry) { + if (retry != 0) + ::Sleep(10); + from_handle = ::CreateFileW((LPWSTR)wide_from.data(), GENERIC_READ | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (from_handle) + break; + } + if (! from_handle) + return map_windows_error(GetLastError()); + + // We normally expect this loop to succeed after a few iterations. If it + // requires more than 200 tries, it's more likely that the failures are due to + // a true error, so stop trying. + for (unsigned retry = 0; retry != 200; ++ retry) { + auto errcode = rename_internal(from_handle, wide_to, true); + + if (errcode == std::error_code(ERROR_CALL_NOT_IMPLEMENTED, std::system_category())) { + // Wine doesn't support SetFileInformationByHandle in rename_internal. + // Fall back to MoveFileEx. + if (std::error_code errcode2 = real_path_from_handle(from_handle, wide_from)) + return errcode2; + if (::MoveFileExW((LPCWSTR)wide_from.data(), (LPCWSTR)wide_to.data(), MOVEFILE_REPLACE_EXISTING)) + return std::error_code(); + return map_windows_error(GetLastError()); + } + + if (! errcode || errcode != std::errc::permission_denied) + return errcode; + + // The destination file probably exists and is currently open in another + // process, either because the file was opened without FILE_SHARE_DELETE or + // it is mapped into memory (e.g. using MemoryBuffer). Rename it in order to + // move it out of the way of the source file. Use FILE_FLAG_DELETE_ON_CLOSE + // to arrange for the destination file to be deleted when the other process + // closes it. + ScopedFileHandle to_handle(::CreateFileW((LPCWSTR)wide_to.data(), GENERIC_READ | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL)); + if (! to_handle) { + auto errcode = map_windows_error(GetLastError()); + // Another process might have raced with us and moved the existing file + // out of the way before we had a chance to open it. If that happens, try + // to rename the source file again. + if (errcode == std::errc::no_such_file_or_directory) + continue; + return errcode; + } + + BY_HANDLE_FILE_INFORMATION FI; + if (! ::GetFileInformationByHandle(to_handle, &FI)) + return map_windows_error(GetLastError()); + + // Try to find a unique new name for the destination file. + for (unsigned unique_id = 0; unique_id != 200; ++ unique_id) { + std::wstring tmp_filename = wide_to + L".tmp" + std::to_wstring(unique_id); + std::error_code errcode = rename_internal(to_handle, tmp_filename, false); + if (errcode) { + if (errcode == std::make_error_code(std::errc::file_exists) || errcode == std::make_error_code(std::errc::permission_denied)) { + // Again, another process might have raced with us and moved the file + // before we could move it. Check whether this is the case, as it + // might have caused the permission denied error. If that was the + // case, we don't need to move it ourselves. + ScopedFileHandle to_handle2(::CreateFileW((LPCWSTR)wide_to.data(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)); + if (! to_handle2) { + auto errcode = map_windows_error(GetLastError()); + if (errcode == std::errc::no_such_file_or_directory) + break; + return errcode; + } + BY_HANDLE_FILE_INFORMATION FI2; + if (! ::GetFileInformationByHandle(to_handle2, &FI2)) + return map_windows_error(GetLastError()); + if (FI.nFileIndexHigh != FI2.nFileIndexHigh || FI.nFileIndexLow != FI2.nFileIndexLow || FI.dwVolumeSerialNumber != FI2.dwVolumeSerialNumber) + break; + continue; + } + return errcode; + } + break; + } + + // Okay, the old destination file has probably been moved out of the way at + // this point, so try to rename the source file again. Still, another + // process might have raced with us to create and open the destination + // file, so we need to keep doing this until we succeed. + } + + // The most likely root cause. + return std::make_error_code(std::errc::permission_denied); + } +} // namespace WindowsSupport +#endif /* _WIN32 */ // borrowed from LVVM lib/Support/Windows/Path.inc -int rename_file(const std::string &from, const std::string &to) +std::error_code rename_file(const std::string &from, const std::string &to) { - int ec = 0; - #ifdef _WIN32 - - // Convert to utf-16. - std::wstring wide_from = boost::nowide::widen(from); - std::wstring wide_to = boost::nowide::widen(to); - - // Retry while we see recoverable errors. - // System scanners (eg. indexer) might open the source file when it is written - // and closed. - bool TryReplace = true; - - // This loop may take more than 2000 x 1ms to finish. - for (int i = 0; i < 2000; ++ i) { - if (i > 0) - // Sleep 1ms - ::Sleep(1); - if (TryReplace) { - // Try ReplaceFile first, as it is able to associate a new data stream - // with the destination even if the destination file is currently open. - if (::ReplaceFileW(wide_to.data(), wide_from.data(), NULL, 0, NULL, NULL)) - return 0; - DWORD ReplaceError = ::GetLastError(); - ec = -1; // ReplaceError - // If ReplaceFileW returned ERROR_UNABLE_TO_MOVE_REPLACEMENT or - // ERROR_UNABLE_TO_MOVE_REPLACEMENT_2, retry but only use MoveFileExW(). - if (ReplaceError == ERROR_UNABLE_TO_MOVE_REPLACEMENT || - ReplaceError == ERROR_UNABLE_TO_MOVE_REPLACEMENT_2) { - TryReplace = false; - continue; - } - // If ReplaceFileW returned ERROR_UNABLE_TO_REMOVE_REPLACED, retry - // using ReplaceFileW(). - if (ReplaceError == ERROR_UNABLE_TO_REMOVE_REPLACED) - continue; - // We get ERROR_FILE_NOT_FOUND if the destination file is missing. - // MoveFileEx can handle this case. - if (ReplaceError != ERROR_ACCESS_DENIED && ReplaceError != ERROR_FILE_NOT_FOUND && ReplaceError != ERROR_SHARING_VIOLATION) - break; - } - if (::MoveFileExW(wide_from.c_str(), wide_to.c_str(), MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING)) - return 0; - DWORD MoveError = ::GetLastError(); - ec = -1; // MoveError - if (MoveError != ERROR_ACCESS_DENIED && MoveError != ERROR_SHARING_VIOLATION) - break; - } - + return WindowsSupport::rename(from, to); #else - boost::nowide::remove(to.c_str()); - ec = boost::nowide::rename(from.c_str(), to.c_str()); - + return std::make_error_code(static_cast(boost::nowide::rename(from.c_str(), to.c_str()))); #endif - - return ec; } int copy_file(const std::string &from, const std::string &to) @@ -414,57 +602,163 @@ std::string format_memsize_MB(size_t n) scale *= 1000; } char buf[8]; - sprintf(buf, "%d", n); + sprintf(buf, "%d", (int)n); out = buf; while (scale != 1) { scale /= 1000; n = n2 / scale; n2 = n2 % scale; - sprintf(buf, ",%03d", n); + sprintf(buf, ",%03d", (int)n); out += buf; } return out + "MB"; } -#ifdef WIN32 - -#ifndef PROCESS_MEMORY_COUNTERS_EX - // MingW32 doesn't have this struct in psapi.h - typedef struct _PROCESS_MEMORY_COUNTERS_EX { - DWORD cb; - DWORD PageFaultCount; - SIZE_T PeakWorkingSetSize; - SIZE_T WorkingSetSize; - SIZE_T QuotaPeakPagedPoolUsage; - SIZE_T QuotaPagedPoolUsage; - SIZE_T QuotaPeakNonPagedPoolUsage; - SIZE_T QuotaNonPagedPoolUsage; - SIZE_T PagefileUsage; - SIZE_T PeakPagefileUsage; - SIZE_T PrivateUsage; - } PROCESS_MEMORY_COUNTERS_EX, *PPROCESS_MEMORY_COUNTERS_EX; -#endif /* PROCESS_MEMORY_COUNTERS_EX */ - -std::string log_memory_info() +// Returns platform-specific string to be used as log output or parsed in SysInfoDialog. +// The latter parses the string with (semi)colons as separators, it should look about as +// "desc1: value1; desc2: value2" or similar (spaces should not matter). +std::string log_memory_info(bool ignore_loglevel) { std::string out; - if (logSeverity <= boost::log::trivial::info) { + if (ignore_loglevel || logSeverity <= boost::log::trivial::info) { +#ifdef WIN32 + #ifndef PROCESS_MEMORY_COUNTERS_EX + // MingW32 doesn't have this struct in psapi.h + typedef struct _PROCESS_MEMORY_COUNTERS_EX { + DWORD cb; + DWORD PageFaultCount; + SIZE_T PeakWorkingSetSize; + SIZE_T WorkingSetSize; + SIZE_T QuotaPeakPagedPoolUsage; + SIZE_T QuotaPagedPoolUsage; + SIZE_T QuotaPeakNonPagedPoolUsage; + SIZE_T QuotaNonPagedPoolUsage; + SIZE_T PagefileUsage; + SIZE_T PeakPagefileUsage; + SIZE_T PrivateUsage; + } PROCESS_MEMORY_COUNTERS_EX, *PPROCESS_MEMORY_COUNTERS_EX; + #endif /* PROCESS_MEMORY_COUNTERS_EX */ + + HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, ::GetCurrentProcessId()); if (hProcess != nullptr) { PROCESS_MEMORY_COUNTERS_EX pmc; if (GetProcessMemoryInfo(hProcess, (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc))) - out = " WorkingSet: " + format_memsize_MB(pmc.WorkingSetSize) + " PrivateBytes: " + format_memsize_MB(pmc.PrivateUsage) + " Pagefile(peak): " + format_memsize_MB(pmc.PagefileUsage) + "(" + format_memsize_MB(pmc.PeakPagefileUsage) + ")"; + out = " WorkingSet: " + format_memsize_MB(pmc.WorkingSetSize) + "; PrivateBytes: " + format_memsize_MB(pmc.PrivateUsage) + "; Pagefile(peak): " + format_memsize_MB(pmc.PagefileUsage) + "(" + format_memsize_MB(pmc.PeakPagefileUsage) + ")"; + else + out += " Used memory: N/A"; CloseHandle(hProcess); } +#elif defined(__linux__) or defined(__APPLE__) + // Get current memory usage. + #ifdef __APPLE__ + struct mach_task_basic_info info; + mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT; + out += " Resident memory: "; + if ( task_info( mach_task_self( ), MACH_TASK_BASIC_INFO, (task_info_t)&info, &infoCount ) == KERN_SUCCESS ) + out += format_memsize_MB((size_t)info.resident_size); + else + out += "N/A"; + #else // i.e. __linux__ + size_t tSize = 0, resident = 0, share = 0; + std::ifstream buffer("/proc/self/statm"); + if (buffer && (buffer >> tSize >> resident >> share)) { + size_t page_size = (size_t)sysconf(_SC_PAGE_SIZE); // in case x86-64 is configured to use 2MB pages + size_t rss = resident * page_size; + out += " Resident memory: " + format_memsize_MB(rss); + out += "; Shared memory: " + format_memsize_MB(share * page_size); + out += "; Private memory: " + format_memsize_MB(rss - share * page_size); + } + else + out += " Used memory: N/A"; + #endif + // Now get peak memory usage. + out += "; Peak memory usage: "; + rusage memory_info; + if (getrusage(RUSAGE_SELF, &memory_info) == 0) + { + size_t peak_mem_usage = (size_t)memory_info.ru_maxrss; + #ifdef __linux__ + peak_mem_usage *= 1024;// getrusage returns the value in kB on linux + #endif + out += format_memsize_MB(peak_mem_usage); + } + else + out += "N/A"; +#endif } return out; } -#else -std::string log_memory_info() +// Returns the size of physical memory (RAM) in bytes. +// http://nadeausoftware.com/articles/2012/09/c_c_tip_how_get_physical_memory_size_system +size_t total_physical_memory() { - return std::string(); -} +#if defined(_WIN32) && (defined(__CYGWIN__) || defined(__CYGWIN32__)) + // Cygwin under Windows. ------------------------------------ + // New 64-bit MEMORYSTATUSEX isn't available. Use old 32.bit + MEMORYSTATUS status; + status.dwLength = sizeof(status); + GlobalMemoryStatus( &status ); + return (size_t)status.dwTotalPhys; +#elif defined(_WIN32) + // Windows. ------------------------------------------------- + // Use new 64-bit MEMORYSTATUSEX, not old 32-bit MEMORYSTATUS + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + GlobalMemoryStatusEx( &status ); + return (size_t)status.ullTotalPhys; +#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__)) + // UNIX variants. ------------------------------------------- + // Prefer sysctl() over sysconf() except sysctl() HW_REALMEM and HW_PHYSMEM + +#if defined(CTL_HW) && (defined(HW_MEMSIZE) || defined(HW_PHYSMEM64)) + int mib[2]; + mib[0] = CTL_HW; +#if defined(HW_MEMSIZE) + mib[1] = HW_MEMSIZE; // OSX. --------------------- +#elif defined(HW_PHYSMEM64) + mib[1] = HW_PHYSMEM64; // NetBSD, OpenBSD. --------- #endif + int64_t size = 0; // 64-bit + size_t len = sizeof( size ); + if ( sysctl( mib, 2, &size, &len, NULL, 0 ) == 0 ) + return (size_t)size; + return 0L; // Failed? + +#elif defined(_SC_AIX_REALMEM) + // AIX. ----------------------------------------------------- + return (size_t)sysconf( _SC_AIX_REALMEM ) * (size_t)1024L; + +#elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE) + // FreeBSD, Linux, OpenBSD, and Solaris. -------------------- + return (size_t)sysconf( _SC_PHYS_PAGES ) * + (size_t)sysconf( _SC_PAGESIZE ); + +#elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGE_SIZE) + // Legacy. -------------------------------------------------- + return (size_t)sysconf( _SC_PHYS_PAGES ) * + (size_t)sysconf( _SC_PAGE_SIZE ); + +#elif defined(CTL_HW) && (defined(HW_PHYSMEM) || defined(HW_REALMEM)) + // DragonFly BSD, FreeBSD, NetBSD, OpenBSD, and OSX. -------- + int mib[2]; + mib[0] = CTL_HW; +#if defined(HW_REALMEM) + mib[1] = HW_REALMEM; // FreeBSD. ----------------- +#elif defined(HW_PYSMEM) + mib[1] = HW_PHYSMEM; // Others. ------------------ +#endif + unsigned int size = 0; // 32-bit + size_t len = sizeof( size ); + if ( sysctl( mib, 2, &size, &len, NULL, 0 ) == 0 ) + return (size_t)size; + return 0L; // Failed? +#endif // sysctl and sysconf variants + +#else + return 0L; // Unknown OS. +#endif +} }; // namespace Slic3r diff --git a/src/qhull/CMakeLists.txt b/src/qhull/CMakeLists.txt index a3e7da1264..9ca0bdff23 100644 --- a/src/qhull/CMakeLists.txt +++ b/src/qhull/CMakeLists.txt @@ -8,6 +8,22 @@ # Created by modification of the original qhull CMakeLists. # Lukas Matena (25.7.2018), lukasmatena@seznam.cz +# see bug report: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=925540 + +find_package(Qhull 7.2 QUIET) + +add_library(qhull INTERFACE) + +if(Qhull_FOUND) + +message(STATUS "Using qhull from system.") +if(SLIC3R_STATIC) + target_link_libraries(qhull INTERFACE Qhull::qhullcpp Qhull::qhullstatic_r) +else() + target_link_libraries(qhull INTERFACE Qhull::qhullcpp Qhull::qhull_r) +endif() + +else(Qhull_FOUND) project(qhull) cmake_minimum_required(VERSION 2.6) @@ -112,7 +128,7 @@ set(libqhull_SOURCES ################################################## # combined library (reentrant qhull and qhullcpp) for Slic3r: -set(qhull_STATIC qhull) +set(qhull_STATIC qhullstatic) add_library(${qhull_STATIC} STATIC ${libqhull_SOURCES}) set_target_properties(${qhull_STATIC} PROPERTIES VERSION ${qhull_VERSION}) @@ -123,4 +139,7 @@ endif(UNIX) ################################################## # LIBDIR is defined in the main xs CMake file: -target_include_directories(${qhull_STATIC} PRIVATE ${LIBDIR}/qhull/src) +target_include_directories(${qhull_STATIC} BEFORE PUBLIC ${LIBDIR}/qhull/src) +target_link_libraries(qhull INTERFACE ${qhull_STATIC}) + +endif() diff --git a/src/semver/CMakeLists.txt b/src/semver/CMakeLists.txt index e3457bf291..c273121d49 100644 --- a/src/semver/CMakeLists.txt +++ b/src/semver/CMakeLists.txt @@ -5,3 +5,5 @@ add_library(semver STATIC semver.c semver.h ) + +encoding_check(semver) diff --git a/src/semver/semver.c b/src/semver/semver.c index 527738644d..e8bd6edcfc 100644 --- a/src/semver/semver.c +++ b/src/semver/semver.c @@ -22,6 +22,10 @@ static const size_t MAX_SIZE = sizeof(char) * 255; static const int MAX_SAFE_INT = (unsigned int) -1 >> 1; +#ifdef _WIN32 + #define strdup _strdup +#endif + /** * Define comparison operators, storing the * ASCII code per each symbol in hexadecimal notation. @@ -50,8 +54,8 @@ strcut (char *str, int begin, int len) { if((int)l < 0 || (int)l > MAX_SAFE_INT) return -1; - if (len < 0) len = l - begin + 1; - if (begin + len > (int)l) len = l - begin; + if (len < 0) len = (int)l - begin + 1; + if (begin + len > (int)l) len = (int)l - begin; memmove(str + begin, str + begin + len, l - len + 1 - begin); return len; @@ -104,7 +108,7 @@ parse_int (const char *s) { static char * parse_slice (char *buf, char sep) { char *pr, *part; - int plen; + size_t plen; /* Find separator in buf */ pr = strchr(buf, sep); @@ -210,8 +214,9 @@ semver_parse_version (const char *str, semver_t *ver) { static int compare_prerelease (char *x, char *y) { char *lastx, *lasty, *xptr, *yptr, *endptr; - int xlen, ylen, xisnum, yisnum, xnum, ynum; - int xn, yn, min, res; + size_t xlen, ylen, xn, yn, min; + int xisnum, yisnum, xnum, ynum; + int res; if (x == NULL && y == NULL) return 0; if (y == NULL && x) return -1; if (x == NULL && y) return 1; @@ -572,7 +577,7 @@ semver_clean (char *s) { for (i = 0; i < len; i++) { if (contains(s[i], VALID_CHARS, mlen) == 0) { - res = strcut(s, i, 1); + res = strcut(s, (int)i, 1); if(res == -1) return -1; --len; --i; } diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 570e23baa6..b3e2990f91 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -81,6 +81,8 @@ set(SLIC3R_GUI_SOURCES GUI/GUI_ObjectManipulation.hpp GUI/GUI_ObjectSettings.cpp GUI/GUI_ObjectSettings.hpp + GUI/GUI_ObjectLayers.cpp + GUI/GUI_ObjectLayers.hpp GUI/LambdaObjectDialog.cpp GUI/LambdaObjectDialog.hpp GUI/Tab.cpp @@ -146,6 +148,8 @@ set(SLIC3R_GUI_SOURCES Utils/PresetUpdater.hpp Utils/Time.cpp Utils/Time.hpp + Utils/UndoRedo.cpp + Utils/UndoRedo.hpp Utils/HexFile.cpp Utils/HexFile.hpp ) @@ -159,7 +163,9 @@ endif () add_library(libslic3r_gui STATIC ${SLIC3R_GUI_SOURCES}) -target_link_libraries(libslic3r_gui libslic3r avrdude imgui ${GLEW_LIBRARIES}) +encoding_check(libslic3r_gui) + +target_link_libraries(libslic3r_gui libslic3r avrdude cereal imgui ${GLEW_LIBRARIES}) if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) add_precompiled_header(libslic3r_gui pchheader.hpp FORCEINCLUDE) endif () diff --git a/src/slic3r/Config/Snapshot.cpp b/src/slic3r/Config/Snapshot.cpp index b208554b50..3757ec25b3 100644 --- a/src/slic3r/Config/Snapshot.cpp +++ b/src/slic3r/Config/Snapshot.cpp @@ -366,7 +366,7 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot: // Snapshot header. snapshot.time_captured = Slic3r::Utils::get_current_time_utc(); snapshot.id = Slic3r::Utils::format_time_ISO8601Z(snapshot.time_captured); - snapshot.slic3r_version_captured = *Semver::parse(SLIC3R_VERSION); // XXX: have Semver Slic3r version + snapshot.slic3r_version_captured = Slic3r::SEMVER; snapshot.comment = comment; snapshot.reason = reason; // Active presets at the time of the snapshot. diff --git a/src/slic3r/Config/Snapshot.hpp b/src/slic3r/Config/Snapshot.hpp index a916dfe92a..9a73916916 100644 --- a/src/slic3r/Config/Snapshot.hpp +++ b/src/slic3r/Config/Snapshot.hpp @@ -8,8 +8,8 @@ #include +#include "libslic3r/Semver.hpp" #include "Version.hpp" -#include "../Utils/Semver.hpp" namespace Slic3r { diff --git a/src/slic3r/Config/Version.cpp b/src/slic3r/Config/Version.cpp index 2eda135d6e..175abff69a 100644 --- a/src/slic3r/Config/Version.cpp +++ b/src/slic3r/Config/Version.cpp @@ -15,7 +15,6 @@ namespace Slic3r { namespace GUI { namespace Config { -static const Semver s_current_slic3r_semver(SLIC3R_VERSION); // Optimized lexicographic compare of two pre-release versions, ignoring the numeric suffix. static int compare_prerelease(const char *p1, const char *p2) @@ -64,7 +63,7 @@ bool Version::is_slic3r_supported(const Semver &slic3r_version) const bool Version::is_current_slic3r_supported() const { - return this->is_slic3r_supported(s_current_slic3r_semver); + return this->is_slic3r_supported(Slic3r::SEMVER); } #if 0 @@ -192,12 +191,18 @@ size_t Index::load(const boost::filesystem::path &path) { m_configs.clear(); m_vendor = path.stem().string(); + m_path = path; boost::nowide::ifstream ifs(path.string()); std::string line; size_t idx_line = 0; Version ver; while (std::getline(ifs, line)) { +#ifndef _MSVCVER + // On a Unix system, getline does not remove the trailing carriage returns, if the index is shared over a Windows filesystem. Remove them manually. + while (! line.empty() && line.back() == '\r') + line.pop_back(); +#endif ++ idx_line; // Skip the initial white spaces. char *key = left_trim(const_cast(line.data())); diff --git a/src/slic3r/Config/Version.hpp b/src/slic3r/Config/Version.hpp index e689286af8..19c565ffb4 100644 --- a/src/slic3r/Config/Version.hpp +++ b/src/slic3r/Config/Version.hpp @@ -7,7 +7,7 @@ #include #include "libslic3r/FileParserError.hpp" -#include "../Utils/Semver.hpp" +#include "libslic3r/Semver.hpp" namespace Slic3r { namespace GUI { @@ -72,6 +72,9 @@ public: // if the index is valid. const_iterator recommended() const; + // Returns the filesystem path from which this index has originally been loaded + const boost::filesystem::path& path() const { return m_path; } + // Load all vendor specific indices. // Throws Slic3r::file_parser_error and the standard std file access exceptions. static std::vector load_db(); @@ -79,6 +82,7 @@ public: private: std::string m_vendor; std::vector m_configs; + boost::filesystem::path m_path; }; } // namespace Config diff --git a/src/slic3r/GUI/2DBed.cpp b/src/slic3r/GUI/2DBed.cpp index ce20124612..d2075f673d 100644 --- a/src/slic3r/GUI/2DBed.cpp +++ b/src/slic3r/GUI/2DBed.cpp @@ -18,12 +18,9 @@ wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(25 * wxGetApp().em_unit(), - #ifdef __APPLE__ m_user_drawn_background = false; #endif /*__APPLE__*/ - Bind(wxEVT_PAINT, ([this](wxPaintEvent &/* e */) { repaint(); })); - Bind(wxEVT_LEFT_DOWN, ([this](wxMouseEvent &event) { mouse_event(event); })); - Bind(wxEVT_MOTION, ([this](wxMouseEvent &event) { mouse_event(event); })); - Bind(wxEVT_SIZE, ([this](wxSizeEvent & /* e */) { Refresh(); })); } -void Bed_2D::repaint() + +void Bed_2D::repaint(const std::vector& shape) { wxAutoBufferedPaintDC dc(this); auto cw = GetSize().GetWidth(); @@ -43,29 +40,20 @@ void Bed_2D::repaint() dc.DrawRectangle(rect.GetLeft(), rect.GetTop(), rect.GetWidth(), rect.GetHeight()); } - // turn cw and ch from sizes to max coordinates - cw--; - ch--; + if (shape.empty()) + return; + + // reduce size to have some space around the drawn shape + cw -= (2 * Border); + ch -= (2 * Border); auto cbb = BoundingBoxf(Vec2d(0, 0),Vec2d(cw, ch)); - // leave space for origin point - cbb.min(0) += 4; - cbb.max -= Vec2d(4., 4.); - - // leave space for origin label - cbb.max(1) -= 13; - - // read new size - cw = cbb.size()(0); - ch = cbb.size()(1); - auto ccenter = cbb.center(); // get bounding box of bed shape in G - code coordinates - auto bed_shape = m_bed_shape; - auto bed_polygon = Polygon::new_scale(m_bed_shape); - auto bb = BoundingBoxf(m_bed_shape); - bb.merge(Vec2d(0, 0)); // origin needs to be in the visible area + auto bed_polygon = Polygon::new_scale(shape); + auto bb = BoundingBoxf(shape); + bb.merge(Vec2d(0, 0)); // origin needs to be in the visible area auto bw = bb.size()(0); auto bh = bb.size()(1); auto bcenter = bb.center(); @@ -76,17 +64,17 @@ void Bed_2D::repaint() ccenter(0) - bcenter(0) * sfactor, ccenter(1) - bcenter(1) * sfactor ); + m_scale_factor = sfactor; - m_shift = Vec2d(shift(0) + cbb.min(0), - shift(1) - (cbb.max(1) - GetSize().GetHeight())); + m_shift = Vec2d(shift(0) + cbb.min(0), shift(1) - (cbb.max(1) - ch)); // draw bed fill dc.SetBrush(wxBrush(wxColour(255, 255, 255), wxBRUSHSTYLE_SOLID)); wxPointList pt_list; - for (auto pt: m_bed_shape) - { - Point pt_pix = to_pixels(pt); - pt_list.push_back(new wxPoint(pt_pix(0), pt_pix(1))); + for (auto pt : shape) + { + Point pt_pix = to_pixels(pt, ch); + pt_list.push_back(new wxPoint(pt_pix(0), pt_pix(1))); } dc.DrawPolygon(&pt_list, 0, 0); @@ -105,9 +93,9 @@ void Bed_2D::repaint() for (auto pl : polylines) { for (size_t i = 0; i < pl.points.size()-1; i++) { - Point pt1 = to_pixels(unscale(pl.points[i])); - Point pt2 = to_pixels(unscale(pl.points[i+1])); - dc.DrawLine(pt1(0), pt1(1), pt2(0), pt2(1)); + Point pt1 = to_pixels(unscale(pl.points[i]), ch); + Point pt2 = to_pixels(unscale(pl.points[i + 1]), ch); + dc.DrawLine(pt1(0), pt1(1), pt2(0), pt2(1)); } } @@ -116,7 +104,7 @@ void Bed_2D::repaint() dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT)); dc.DrawPolygon(&pt_list, 0, 0); - auto origin_px = to_pixels(Vec2d(0, 0)); + auto origin_px = to_pixels(Vec2d(0, 0), ch); // draw axes auto axes_len = 50; @@ -153,7 +141,7 @@ void Bed_2D::repaint() // draw current position if (m_pos!= Vec2d(0, 0)) { - auto pos_px = to_pixels(m_pos); + auto pos_px = to_pixels(m_pos, ch); dc.SetPen(wxPen(wxColour(200, 0, 0), 2, wxPENSTYLE_SOLID)); dc.SetBrush(wxBrush(wxColour(200, 0, 0), wxBRUSHSTYLE_TRANSPARENT)); dc.DrawCircle(pos_px(0), pos_px(1), 5); @@ -161,38 +149,17 @@ void Bed_2D::repaint() dc.DrawLine(pos_px(0) - 15, pos_px(1), pos_px(0) + 15, pos_px(1)); dc.DrawLine(pos_px(0), pos_px(1) - 15, pos_px(0), pos_px(1) + 15); } - - m_painted = true; } + // convert G - code coordinates into pixels -Point Bed_2D::to_pixels(Vec2d point) +Point Bed_2D::to_pixels(const Vec2d& point, int height) { auto p = point * m_scale_factor + m_shift; - return Point(p(0), GetSize().GetHeight() - p(1)); + return Point(p(0) + Border, height - p(1) + Border); } -void Bed_2D::mouse_event(wxMouseEvent event) -{ - if (!m_interactive) return; - if (!m_painted) return; - - auto pos = event.GetPosition(); - auto point = to_units(Point(pos.x, pos.y)); - if (event.LeftDown() || event.Dragging()) { - if (m_on_move) - m_on_move(point) ; - Refresh(); - } -} - -// convert pixels into G - code coordinates -Vec2d Bed_2D::to_units(Point point) -{ - return (Vec2d(point(0), GetSize().GetHeight() - point(1)) - m_shift) * (1. / m_scale_factor); -} - -void Bed_2D::set_pos(Vec2d pos) +void Bed_2D::set_pos(const Vec2d& pos) { m_pos = pos; Refresh(); diff --git a/src/slic3r/GUI/2DBed.hpp b/src/slic3r/GUI/2DBed.hpp index 579ef44458..80926bea72 100644 --- a/src/slic3r/GUI/2DBed.hpp +++ b/src/slic3r/GUI/2DBed.hpp @@ -9,27 +9,21 @@ namespace GUI { class Bed_2D : public wxPanel { + static const int Border = 10; + bool m_user_drawn_background = true; - bool m_painted = false; - bool m_interactive = false; - double m_scale_factor; + double m_scale_factor; Vec2d m_shift = Vec2d::Zero(); Vec2d m_pos = Vec2d::Zero(); - std::function m_on_move = nullptr; - Point to_pixels(Vec2d point); - Vec2d to_units(Point point); - void repaint(); - void mouse_event(wxMouseEvent event); - void set_pos(Vec2d pos); + Point to_pixels(const Vec2d& point, int height); + void set_pos(const Vec2d& pos); public: - Bed_2D(wxWindow* parent); - ~Bed_2D() {} + explicit Bed_2D(wxWindow* parent); - std::vector m_bed_shape; - + void repaint(const std::vector& shape); }; diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 8392e534a4..5b7473980f 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -9,10 +9,12 @@ #include "GUI_App.hpp" #include "PresetBundle.hpp" #include "Gizmos/GLGizmoBase.hpp" +#include "GLCanvas3D.hpp" #include #include +#include static const float GROUND_Z = -0.02f; @@ -21,7 +23,6 @@ namespace GUI { bool GeometryBuffer::set_from_triangles(const Polygons& triangles, float z, bool generate_tex_coords) { -#if ENABLE_TEXTURES_FROM_SVG m_vertices.clear(); unsigned int v_size = 3 * (unsigned int)triangles.size(); @@ -81,75 +82,12 @@ bool GeometryBuffer::set_from_triangles(const Polygons& triangles, float z, bool } } } -#else - m_vertices.clear(); - m_tex_coords.clear(); - - unsigned int v_size = 9 * (unsigned int)triangles.size(); - unsigned int t_size = 6 * (unsigned int)triangles.size(); - if (v_size == 0) - return false; - - m_vertices = std::vector(v_size, 0.0f); - if (generate_tex_coords) - m_tex_coords = std::vector(t_size, 0.0f); - - float min_x = unscale(triangles[0].points[0](0)); - float min_y = unscale(triangles[0].points[0](1)); - float max_x = min_x; - float max_y = min_y; - - unsigned int v_coord = 0; - unsigned int t_coord = 0; - for (const Polygon& t : triangles) - { - for (unsigned int v = 0; v < 3; ++v) - { - const Point& p = t.points[v]; - float x = unscale(p(0)); - float y = unscale(p(1)); - - m_vertices[v_coord++] = x; - m_vertices[v_coord++] = y; - m_vertices[v_coord++] = z; - - if (generate_tex_coords) - { - m_tex_coords[t_coord++] = x; - m_tex_coords[t_coord++] = y; - - min_x = std::min(min_x, x); - max_x = std::max(max_x, x); - min_y = std::min(min_y, y); - max_y = std::max(max_y, y); - } - } - } - - if (generate_tex_coords) - { - float size_x = max_x - min_x; - float size_y = max_y - min_y; - - if ((size_x != 0.0f) && (size_y != 0.0f)) - { - float inv_size_x = 1.0f / size_x; - float inv_size_y = -1.0f / size_y; - for (unsigned int i = 0; i < m_tex_coords.size(); i += 2) - { - m_tex_coords[i] = (m_tex_coords[i] - min_x) * inv_size_x; - m_tex_coords[i + 1] = (m_tex_coords[i + 1] - min_y) * inv_size_y; - } - } - } -#endif // ENABLE_TEXTURES_FROM_SVG return true; } bool GeometryBuffer::set_from_lines(const Lines& lines, float z) { -#if ENABLE_TEXTURES_FROM_SVG m_vertices.clear(); unsigned int v_size = 2 * (unsigned int)lines.size(); @@ -173,37 +111,14 @@ bool GeometryBuffer::set_from_lines(const Lines& lines, float z) v2.position[2] = z; ++v_count; } -#else - m_vertices.clear(); - m_tex_coords.clear(); - - unsigned int size = 6 * (unsigned int)lines.size(); - if (size == 0) - return false; - - m_vertices = std::vector(size, 0.0f); - - unsigned int coord = 0; - for (const Line& l : lines) - { - m_vertices[coord++] = unscale(l.a(0)); - m_vertices[coord++] = unscale(l.a(1)); - m_vertices[coord++] = z; - m_vertices[coord++] = unscale(l.b(0)); - m_vertices[coord++] = unscale(l.b(1)); - m_vertices[coord++] = z; - } -#endif // ENABLE_TEXTURES_FROM_SVG return true; } -#if ENABLE_TEXTURES_FROM_SVG const float* GeometryBuffer::get_vertices_data() const { return (m_vertices.size() > 0) ? (const float*)m_vertices.data() : nullptr; } -#endif // ENABLE_TEXTURES_FROM_SVG const double Bed3D::Axes::Radius = 0.5; const double Bed3D::Axes::ArrowBaseRadius = 2.5 * Bed3D::Axes::Radius; @@ -211,7 +126,7 @@ const double Bed3D::Axes::ArrowLength = 5.0; Bed3D::Axes::Axes() : origin(Vec3d::Zero()) -, length(Vec3d::Zero()) +, length(25.0 * Vec3d::Ones()) { m_quadric = ::gluNewQuadric(); if (m_quadric != nullptr) @@ -273,24 +188,44 @@ void Bed3D::Axes::render_axis(double length) const Bed3D::Bed3D() : m_type(Custom) -#if ENABLE_TEXTURES_FROM_SVG + , m_custom_texture("") + , m_custom_model("") + , m_requires_canvas_update(false) , m_vbo_id(0) -#endif // ENABLE_TEXTURES_FROM_SVG , m_scale_factor(1.0f) { } -bool Bed3D::set_shape(const Pointfs& shape) +bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model) { EType new_type = detect_type(shape); - if (m_shape == shape && m_type == new_type) + + // check that the passed custom texture filename is valid + std::string cst_texture(custom_texture); + if (!cst_texture.empty()) + { + if ((!boost::algorithm::iends_with(custom_texture, ".png") && !boost::algorithm::iends_with(custom_texture, ".svg")) || !boost::filesystem::exists(custom_texture)) + cst_texture = ""; + } + + // check that the passed custom texture filename is valid + std::string cst_model(custom_model); + if (!cst_model.empty()) + { + if (!boost::algorithm::iends_with(custom_model, ".stl") || !boost::filesystem::exists(custom_model)) + cst_model = ""; + } + + if ((m_shape == shape) && (m_type == new_type) && (m_custom_texture == cst_texture) && (m_custom_model == cst_model)) // No change, no need to update the UI. return false; m_shape = shape; + m_custom_texture = cst_texture; + m_custom_model = cst_model; m_type = new_type; - calc_bounding_box(); + calc_bounding_boxes(); ExPolygon poly; for (const Vec2d& p : m_shape) @@ -305,13 +240,13 @@ bool Bed3D::set_shape(const Pointfs& shape) m_polygon = offset_ex(poly.contour, (float)bed_bbox.radius() * 1.7f, jtRound, scale_(0.5))[0].contour; -#if ENABLE_TEXTURES_FROM_SVG reset(); -#endif // ENABLE_TEXTURES_FROM_SVG + m_texture.reset(); + m_model.reset(); // Set the origin and size for painting of the coordinate system axes. m_axes.origin = Vec3d(0.0, 0.0, (double)GROUND_Z); - m_axes.length = 0.1 * get_bounding_box().max_size() * Vec3d::Ones(); + m_axes.length = 0.1 * m_bounding_box.max_size() * Vec3d::Ones(); // Let the calee to update the UI. return true; @@ -327,86 +262,54 @@ Point Bed3D::point_projection(const Point& point) const return m_polygon.point_projection(point); } -#if ENABLE_TEXTURES_FROM_SVG -void Bed3D::render(float theta, bool useVBOs, float scale_factor) const +void Bed3D::render(GLCanvas3D& canvas, float theta, float scale_factor) const { m_scale_factor = scale_factor; - EType type = useVBOs ? m_type : Custom; - switch (type) - - { - case MK2: - { - render_prusa("mk2", theta > 90.0f); - break; - } - case MK3: - { - render_prusa("mk3", theta > 90.0f); - break; - } - case SL1: - { - render_prusa("sl1", theta > 90.0f); - break; - } - default: - case Custom: - { - render_custom(); - break; - } - } -} -#else -void Bed3D::render(float theta, bool useVBOs, float scale_factor) const -{ - m_scale_factor = scale_factor; - - if (m_shape.empty()) - return; + render_axes(); switch (m_type) { case MK2: { - render_prusa("mk2", theta, useVBOs); + render_prusa(canvas, "mk2", theta > 90.0f); break; } case MK3: { - render_prusa("mk3", theta, useVBOs); + render_prusa(canvas, "mk3", theta > 90.0f); break; } case SL1: { - render_prusa("sl1", theta, useVBOs); + render_prusa(canvas, "sl1", theta > 90.0f); break; } default: case Custom: { - render_custom(); + render_custom(canvas, theta > 90.0f); break; } } } -#endif // ENABLE_TEXTURES_FROM_SVG -void Bed3D::render_axes() const -{ - if (!m_shape.empty()) - m_axes.render(); -} - -void Bed3D::calc_bounding_box() +void Bed3D::calc_bounding_boxes() const { m_bounding_box = BoundingBoxf3(); for (const Vec2d& p : m_shape) { m_bounding_box.merge(Vec3d(p(0), p(1), 0.0)); } + + m_extended_bounding_box = m_bounding_box; + + // extend to contain axes + m_extended_bounding_box.merge(m_axes.length + Axes::ArrowLength * Vec3d::Ones()); + + // extend to contain model, if any + if (!m_model.get_filename().empty()) + m_extended_bounding_box.merge(m_model.get_transformed_bounding_box()); } void Bed3D::calc_triangles(const ExPolygon& poly) @@ -414,7 +317,7 @@ void Bed3D::calc_triangles(const ExPolygon& poly) Polygons triangles; poly.triangulate(&triangles); - if (!m_triangles.set_from_triangles(triangles, GROUND_Z, m_type != Custom)) + if (!m_triangles.set_from_triangles(triangles, GROUND_Z, true)) printf("Unable to create bed triangles\n"); } @@ -486,315 +389,249 @@ Bed3D::EType Bed3D::detect_type(const Pointfs& shape) const return type; } -#if ENABLE_TEXTURES_FROM_SVG -void Bed3D::render_prusa(const std::string &key, bool bottom) const +void Bed3D::render_axes() const { - std::string tex_path = resources_dir() + "/icons/bed/" + key; + if (!m_shape.empty()) + m_axes.render(); +} - std::string model_path = resources_dir() + "/models/" + key; +void Bed3D::render_prusa(GLCanvas3D& canvas, const std::string& key, bool bottom) const +{ + if (!bottom) + render_model(m_custom_model.empty() ? resources_dir() + "/models/" + key + "_bed.stl" : m_custom_model); - // use anisotropic filter if graphic card allows - GLfloat max_anisotropy = 0.0f; - if (glewIsSupported("GL_EXT_texture_filter_anisotropic")) - glsafe(::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max_anisotropy)); + render_texture(m_custom_texture.empty() ? resources_dir() + "/icons/bed/" + key + ".svg" : m_custom_texture, bottom, canvas); +} - // use higher resolution images if graphic card allows - GLint max_tex_size; - glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_tex_size)); - - // clamp or the texture generation becomes too slow - max_tex_size = std::min(max_tex_size, 8192); - - std::string filename = tex_path + ".svg"; +void Bed3D::render_texture(const std::string& filename, bool bottom, GLCanvas3D& canvas) const +{ + if (filename.empty()) + { + m_texture.reset(); + render_default(bottom); + return; + } if ((m_texture.get_id() == 0) || (m_texture.get_source() != filename)) { - if (!m_texture.load_from_svg_file(filename, true, max_tex_size)) + m_texture.reset(); + + if (boost::algorithm::iends_with(filename, ".svg")) { - render_custom(); + // use higher resolution images if graphic card and opengl version allow + GLint max_tex_size = GLCanvas3DManager::get_gl_info().get_max_tex_size(); + if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != filename)) + { + // generate a temporary lower resolution texture to show while no main texture levels have been compressed + if (!m_temp_texture.load_from_svg_file(filename, false, false, false, max_tex_size / 8)) + { + render_default(bottom); + return; + } + } + + // starts generating the main texture, compression will run asynchronously + if (!m_texture.load_from_svg_file(filename, true, true, true, max_tex_size)) + { + render_default(bottom); + return; + } + } + else if (boost::algorithm::iends_with(filename, ".png")) + { + // generate a temporary lower resolution texture to show while no main texture levels have been compressed + if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != filename)) + { + if (!m_temp_texture.load_from_file(filename, false, GLTexture::None, false)) + { + render_default(bottom); + return; + } + } + + // starts generating the main texture, compression will run asynchronously + if (!m_texture.load_from_file(filename, true, GLTexture::MultiThreaded, true)) + { + render_default(bottom); + return; + } + } + else + { + render_default(bottom); return; } + } + else if (m_texture.unsent_compressed_data_available()) + { + // sends to gpu the already available compressed levels of the main texture + m_texture.send_compressed_data_to_gpu(); - if (max_anisotropy > 0.0f) - { - glsafe(::glBindTexture(GL_TEXTURE_2D, m_texture.get_id())); - glsafe(::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy)); - glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); - } + // the temporary texture is not needed anymore, reset it + if (m_temp_texture.get_id() != 0) + m_temp_texture.reset(); + + m_requires_canvas_update = true; + } + else if (m_requires_canvas_update && m_texture.all_compressed_data_sent_to_gpu()) + { + canvas.stop_keeping_dirty(); + m_requires_canvas_update = false; } - if (!bottom) + if (m_triangles.get_vertices_count() > 0) { - filename = model_path + "_bed.stl"; - if ((m_model.get_filename() != filename) && m_model.init_from_file(filename, true)) { - Vec3d offset = m_bounding_box.center() - Vec3d(0.0, 0.0, 0.5 * m_model.get_bounding_box().size()(2)); - if (key == "mk2") - // hardcoded value to match the stl model - offset += Vec3d(0.0, 7.5, -0.03); - else if (key == "mk3") - // hardcoded value to match the stl model - offset += Vec3d(0.0, 5.5, 2.43); - else if (key == "sl1") - // hardcoded value to match the stl model - offset += Vec3d(0.0, 0.0, -0.03); + if (m_shader.get_shader_program_id() == 0) + m_shader.init("printbed.vs", "printbed.fs"); - m_model.center_around(offset); - } - - if (!m_model.get_filename().empty()) + if (m_shader.is_initialized()) { - glsafe(::glEnable(GL_LIGHTING)); - m_model.render(); - glsafe(::glDisable(GL_LIGHTING)); - } - } + m_shader.start_using(); + m_shader.set_uniform("transparent_background", bottom); + m_shader.set_uniform("svg_source", boost::algorithm::iends_with(m_texture.get_source(), ".svg")); - unsigned int triangles_vcount = m_triangles.get_vertices_count(); - if (triangles_vcount > 0) - { - if (m_vbo_id == 0) - { - glsafe(::glGenBuffers(1, &m_vbo_id)); + if (m_vbo_id == 0) + { + glsafe(::glGenBuffers(1, &m_vbo_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)m_triangles.get_vertices_data_size(), (const GLvoid*)m_triangles.get_vertices_data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + } + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glDepthMask(GL_FALSE)); + + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + + if (bottom) + glsafe(::glFrontFace(GL_CW)); + + unsigned int stride = m_triangles.get_vertex_data_size(); + + GLint position_id = m_shader.get_attrib_location("v_position"); + GLint tex_coords_id = m_shader.get_attrib_location("v_tex_coords"); + + // show the temporary texture while no compressed data is available + GLuint tex_id = (GLuint)m_temp_texture.get_id(); + if (tex_id == 0) + tex_id = (GLuint)m_texture.get_id(); + + glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)m_triangles.get_vertices_data_size(), (const GLvoid*)m_triangles.get_vertices_data(), GL_STATIC_DRAW)); + + if (position_id != -1) + { + glsafe(::glEnableVertexAttribArray(position_id)); + glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_position_offset())); + } + if (tex_coords_id != -1) + { + glsafe(::glEnableVertexAttribArray(tex_coords_id)); + glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_tex_coords_offset())); + } + + glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)m_triangles.get_vertices_count())); + + if (tex_coords_id != -1) + glsafe(::glDisableVertexAttribArray(tex_coords_id)); + + if (position_id != -1) + glsafe(::glDisableVertexAttribArray(position_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - } - - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glDepthMask(GL_FALSE)); - - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - - if (bottom) - glsafe(::glFrontFace(GL_CW)); - - render_prusa_shader(bottom); - - if (bottom) - glsafe(::glFrontFace(GL_CCW)); - - glsafe(::glDisable(GL_BLEND)); - glsafe(::glDepthMask(GL_TRUE)); - } -} - -void Bed3D::render_prusa_shader(bool transparent) const -{ - if (m_shader.get_shader_program_id() == 0) - m_shader.init("printbed.vs", "printbed.fs"); - - if (m_shader.is_initialized()) - { - m_shader.start_using(); - m_shader.set_uniform("transparent_background", transparent); - - unsigned int stride = m_triangles.get_vertex_data_size(); - - GLint position_id = m_shader.get_attrib_location("v_position"); - GLint tex_coords_id = m_shader.get_attrib_location("v_tex_coords"); - - glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)m_texture.get_id())); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id)); - - if (position_id != -1) - { - glsafe(::glEnableVertexAttribArray(position_id)); - glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)m_triangles.get_position_offset())); - } - if (tex_coords_id != -1) - { - glsafe(::glEnableVertexAttribArray(tex_coords_id)); - glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)m_triangles.get_tex_coords_offset())); - } - - glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)m_triangles.get_vertices_count())); - - if (tex_coords_id != -1) - glsafe(::glDisableVertexAttribArray(tex_coords_id)); - - if (position_id != -1) - glsafe(::glDisableVertexAttribArray(position_id)); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); - - m_shader.stop_using(); - } -} -#else -void Bed3D::render_prusa(const std::string &key, float theta, bool useVBOs) const -{ - std::string tex_path = resources_dir() + "/icons/bed/" + key; - - // use higher resolution images if graphic card allows - GLint max_tex_size; - glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_tex_size)); - - // temporary set to lowest resolution - max_tex_size = 2048; - - if (max_tex_size >= 8192) - tex_path += "_8192"; - else if (max_tex_size >= 4096) - tex_path += "_4096"; - - std::string model_path = resources_dir() + "/models/" + key; - - // use anisotropic filter if graphic card allows - GLfloat max_anisotropy = 0.0f; - if (glewIsSupported("GL_EXT_texture_filter_anisotropic")) - glsafe(::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max_anisotropy)); - - std::string filename = tex_path + "_top.png"; - if ((m_top_texture.get_id() == 0) || (m_top_texture.get_source() != filename)) - { - if (!m_top_texture.load_from_file(filename, true)) - { - render_custom(); - return; - } - - if (max_anisotropy > 0.0f) - { - glsafe(::glBindTexture(GL_TEXTURE_2D, m_top_texture.get_id())); - glsafe(::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy)); glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); + + if (bottom) + glsafe(::glFrontFace(GL_CCW)); + + glsafe(::glDisable(GL_BLEND)); + glsafe(::glDepthMask(GL_TRUE)); + + m_shader.stop_using(); } } - - filename = tex_path + "_bottom.png"; - if ((m_bottom_texture.get_id() == 0) || (m_bottom_texture.get_source() != filename)) - { - if (!m_bottom_texture.load_from_file(filename, true)) - { - render_custom(); - return; - } - - if (max_anisotropy > 0.0f) - { - glsafe(::glBindTexture(GL_TEXTURE_2D, m_bottom_texture.get_id())); - glsafe(::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy)); - glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); - } - } - - if (theta <= 90.0f) - { - filename = model_path + "_bed.stl"; - if ((m_model.get_filename() != filename) && m_model.init_from_file(filename, useVBOs)) { - Vec3d offset = m_bounding_box.center() - Vec3d(0.0, 0.0, 0.5 * m_model.get_bounding_box().size()(2)); - if (key == "mk2") - // hardcoded value to match the stl model - offset += Vec3d(0.0, 7.5, -0.03); - else if (key == "mk3") - // hardcoded value to match the stl model - offset += Vec3d(0.0, 5.5, 2.43); - else if (key == "sl1") - // hardcoded value to match the stl model - offset += Vec3d(0.0, 0.0, -0.03); - - m_model.center_around(offset); - } - - if (!m_model.get_filename().empty()) - { - glsafe(::glEnable(GL_LIGHTING)); - m_model.render(); - glsafe(::glDisable(GL_LIGHTING)); - } - } - - unsigned int triangles_vcount = m_triangles.get_vertices_count(); - if (triangles_vcount > 0) - { - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glDepthMask(GL_FALSE)); - - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - - glsafe(::glEnable(GL_TEXTURE_2D)); - glsafe(::glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE)); - - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_TEXTURE_COORD_ARRAY)); - - if (theta > 90.0f) - glsafe(::glFrontFace(GL_CW)); - - glsafe(::glBindTexture(GL_TEXTURE_2D, (theta <= 90.0f) ? (GLuint)m_top_texture.get_id() : (GLuint)m_bottom_texture.get_id())); - glsafe(::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_triangles.get_vertices())); - glsafe(::glTexCoordPointer(2, GL_FLOAT, 0, (GLvoid*)m_triangles.get_tex_coords())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount)); - - if (theta > 90.0f) - glsafe(::glFrontFace(GL_CCW)); - - glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); - glsafe(::glDisableClientState(GL_TEXTURE_COORD_ARRAY)); - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - - glsafe(::glDisable(GL_TEXTURE_2D)); - - glsafe(::glDisable(GL_BLEND)); - glsafe(::glDepthMask(GL_TRUE)); - } } -#endif // ENABLE_TEXTURES_FROM_SVG -void Bed3D::render_custom() const +void Bed3D::render_model(const std::string& filename) const { -#if ENABLE_TEXTURES_FROM_SVG - m_texture.reset(); -#else - m_top_texture.reset(); - m_bottom_texture.reset(); -#endif // ENABLE_TEXTURES_FROM_SVG + if (filename.empty()) + return; - unsigned int triangles_vcount = m_triangles.get_vertices_count(); - if (triangles_vcount > 0) + if ((m_model.get_filename() != filename) && m_model.init_from_file(filename)) + { + // move the model so that its origin (0.0, 0.0, 0.0) goes into the bed shape center and a bit down to avoid z-fighting with the texture quad + Vec3d shift = m_bounding_box.center(); + shift(2) = -0.03; + m_model.set_offset(shift); + + // update extended bounding box + calc_bounding_boxes(); + } + + if (!m_model.get_filename().empty()) { glsafe(::glEnable(GL_LIGHTING)); - glsafe(::glDisable(GL_DEPTH_TEST)); - - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - - glsafe(::glColor4f(0.35f, 0.35f, 0.35f, 0.4f)); - glsafe(::glNormal3d(0.0f, 0.0f, 1.0f)); -#if ENABLE_TEXTURES_FROM_SVG - glsafe(::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_triangles.get_vertices_data())); -#else - glsafe(::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_triangles.get_vertices())); -#endif // ENABLE_TEXTURES_FROM_SVG - glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount)); - - // draw grid - unsigned int gridlines_vcount = m_gridlines.get_vertices_count(); - - // we need depth test for grid, otherwise it would disappear when looking the object from below - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glLineWidth(3.0f * m_scale_factor)); - glsafe(::glColor4f(0.2f, 0.2f, 0.2f, 0.4f)); -#if ENABLE_TEXTURES_FROM_SVG - glsafe(::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_gridlines.get_vertices_data())); -#else - glsafe(::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_gridlines.get_vertices())); -#endif // ENABLE_TEXTURES_FROM_SVG - glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)gridlines_vcount)); - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - - glsafe(::glDisable(GL_BLEND)); + m_model.render(); glsafe(::glDisable(GL_LIGHTING)); } } -#if ENABLE_TEXTURES_FROM_SVG +void Bed3D::render_custom(GLCanvas3D& canvas, bool bottom) const +{ + if (m_custom_texture.empty() && m_custom_model.empty()) + { + render_default(bottom); + return; + } + + if (!bottom) + render_model(m_custom_model); + + render_texture(m_custom_texture, bottom, canvas); +} + +void Bed3D::render_default(bool bottom) const +{ + m_texture.reset(); + + unsigned int triangles_vcount = m_triangles.get_vertices_count(); + if (triangles_vcount > 0) + { + bool has_model = !m_model.get_filename().empty(); + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + + if (!has_model && !bottom) + { + // draw background + glsafe(::glDepthMask(GL_FALSE)); + glsafe(::glColor4f(0.35f, 0.35f, 0.35f, 0.4f)); + glsafe(::glNormal3d(0.0f, 0.0f, 1.0f)); + glsafe(::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_triangles.get_vertices_data())); + glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount)); + glsafe(::glDepthMask(GL_TRUE)); + } + + // draw grid + glsafe(::glLineWidth(3.0f * m_scale_factor)); + if (has_model && !bottom) + glsafe(::glColor4f(0.75f, 0.75f, 0.75f, 1.0f)); + else + glsafe(::glColor4f(0.2f, 0.2f, 0.2f, 0.4f)); + glsafe(::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_gridlines.get_vertices_data())); + glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)m_gridlines.get_vertices_count())); + + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + + glsafe(::glDisable(GL_BLEND)); + } +} + void Bed3D::reset() { if (m_vbo_id > 0) @@ -803,7 +640,6 @@ void Bed3D::reset() m_vbo_id = 0; } } -#endif // ENABLE_TEXTURES_FROM_SVG } // GUI } // Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index e60cdf94e1..c9a30e6ec2 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -3,9 +3,7 @@ #include "GLTexture.hpp" #include "3DScene.hpp" -#if ENABLE_TEXTURES_FROM_SVG #include "GLShader.hpp" -#endif // ENABLE_TEXTURES_FROM_SVG class GLUquadric; typedef class GLUquadric GLUquadricObj; @@ -13,9 +11,10 @@ typedef class GLUquadric GLUquadricObj; namespace Slic3r { namespace GUI { +class GLCanvas3D; + class GeometryBuffer { -#if ENABLE_TEXTURES_FROM_SVG struct Vertex { float position[3]; @@ -29,27 +28,17 @@ class GeometryBuffer }; std::vector m_vertices; -#else - std::vector m_vertices; - std::vector m_tex_coords; -#endif // ENABLE_TEXTURES_FROM_SVG public: bool set_from_triangles(const Polygons& triangles, float z, bool generate_tex_coords); bool set_from_lines(const Lines& lines, float z); -#if ENABLE_TEXTURES_FROM_SVG const float* get_vertices_data() const; unsigned int get_vertices_data_size() const { return (unsigned int)m_vertices.size() * get_vertex_data_size(); } unsigned int get_vertex_data_size() const { return (unsigned int)(5 * sizeof(float)); } - unsigned int get_position_offset() const { return 0; } - unsigned int get_tex_coords_offset() const { return (unsigned int)(3 * sizeof(float)); } + size_t get_position_offset() const { return 0; } + size_t get_tex_coords_offset() const { return (size_t)(3 * sizeof(float)); } unsigned int get_vertices_count() const { return (unsigned int)m_vertices.size(); } -#else - const float* get_vertices() const { return m_vertices.data(); } - const float* get_tex_coords() const { return m_tex_coords.data(); } - unsigned int get_vertices_count() const { return (unsigned int)m_vertices.size() / 3; } -#endif // ENABLE_TEXTURES_FROM_SVG }; class Bed3D @@ -85,18 +74,20 @@ public: private: EType m_type; Pointfs m_shape; - BoundingBoxf3 m_bounding_box; + std::string m_custom_texture; + std::string m_custom_model; + mutable BoundingBoxf3 m_bounding_box; + mutable BoundingBoxf3 m_extended_bounding_box; Polygon m_polygon; GeometryBuffer m_triangles; GeometryBuffer m_gridlines; -#if ENABLE_TEXTURES_FROM_SVG mutable GLTexture m_texture; + // temporary texture shown until the main texture has still no levels compressed + mutable GLTexture m_temp_texture; + // used to trigger 3D scene update once all compressed textures have been sent to GPU + mutable bool m_requires_canvas_update; mutable Shader m_shader; mutable unsigned int m_vbo_id; -#else - mutable GLTexture m_top_texture; - mutable GLTexture m_bottom_texture; -#endif // ENABLE_TEXTURES_FROM_SVG mutable GLBed m_model; Axes m_axes; @@ -104,9 +95,7 @@ private: public: Bed3D(); -#if ENABLE_TEXTURES_FROM_SVG ~Bed3D() { reset(); } -#endif // ENABLE_TEXTURES_FROM_SVG EType get_type() const { return m_type; } @@ -115,30 +104,26 @@ public: const Pointfs& get_shape() const { return m_shape; } // Return true if the bed shape changed, so the calee will update the UI. - bool set_shape(const Pointfs& shape); + bool set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model); - const BoundingBoxf3& get_bounding_box() const { return m_bounding_box; } + const BoundingBoxf3& get_bounding_box(bool extended) const { return extended ? m_extended_bounding_box : m_bounding_box; } bool contains(const Point& point) const; Point point_projection(const Point& point) const; - void render(float theta, bool useVBOs, float scale_factor) const; - void render_axes() const; + void render(GLCanvas3D& canvas, float theta, float scale_factor) const; private: - void calc_bounding_box(); + void calc_bounding_boxes() const; void calc_triangles(const ExPolygon& poly); void calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox); EType detect_type(const Pointfs& shape) const; -#if ENABLE_TEXTURES_FROM_SVG - void render_prusa(const std::string& key, bool bottom) const; - void render_prusa_shader(bool transparent) const; -#else - void render_prusa(const std::string &key, float theta, bool useVBOs) const; -#endif // ENABLE_TEXTURES_FROM_SVG - void render_custom() const; -#if ENABLE_TEXTURES_FROM_SVG + void render_axes() const; + void render_prusa(GLCanvas3D& canvas, const std::string& key, bool bottom) const; + void render_texture(const std::string& filename, bool bottom, GLCanvas3D& canvas) const; + void render_model(const std::string& filename) const; + void render_custom(GLCanvas3D& canvas, bool bottom) const; + void render_default(bool bottom) const; void reset(); -#endif // ENABLE_TEXTURES_FROM_SVG }; } // GUI diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 59480de1ce..d764d6cef8 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -12,6 +12,7 @@ #include "libslic3r/GCode/Analyzer.hpp" #include "slic3r/GUI/PresetBundle.hpp" #include "libslic3r/Format/STL.hpp" +#include "libslic3r/Utils.hpp" #include #include @@ -24,6 +25,8 @@ #include #include +#include + #include #include @@ -55,21 +58,6 @@ void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char namespace Slic3r { -void GLIndexedVertexArray::load_mesh_flat_shading(const TriangleMesh &mesh) -{ - assert(triangle_indices.empty() && vertices_and_normals_interleaved_size == 0); - assert(quad_indices.empty() && triangle_indices_size == 0); - assert(vertices_and_normals_interleaved.size() % 6 == 0 && quad_indices_size == vertices_and_normals_interleaved.size()); - - this->vertices_and_normals_interleaved.reserve(this->vertices_and_normals_interleaved.size() + 3 * 3 * 2 * mesh.facets_count()); - - for (int i = 0; i < (int)mesh.stl.stats.number_of_facets; ++i) { - const stl_facet &facet = mesh.stl.facet_start[i]; - for (int j = 0; j < 3; ++ j) - this->push_geometry(facet.vertex[j](0), facet.vertex[j](1), facet.vertex[j](2), facet.normal(0), facet.normal(1), facet.normal(2)); - } -} - void GLIndexedVertexArray::load_mesh_full_shading(const TriangleMesh &mesh) { assert(triangle_indices.empty() && vertices_and_normals_interleaved_size == 0); @@ -89,37 +77,39 @@ void GLIndexedVertexArray::load_mesh_full_shading(const TriangleMesh &mesh) } } -void GLIndexedVertexArray::finalize_geometry(bool use_VBOs) +void GLIndexedVertexArray::finalize_geometry(bool opengl_initialized) { assert(this->vertices_and_normals_interleaved_VBO_id == 0); assert(this->triangle_indices_VBO_id == 0); assert(this->quad_indices_VBO_id == 0); - this->setup_sizes(); + if (! opengl_initialized) { + // Shrink the data vectors to conserve memory in case the data cannot be transfered to the OpenGL driver yet. + this->shrink_to_fit(); + return; + } - if (use_VBOs) { - if (! empty()) { - glsafe(::glGenBuffers(1, &this->vertices_and_normals_interleaved_VBO_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved.size() * 4, this->vertices_and_normals_interleaved.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - this->vertices_and_normals_interleaved.clear(); - } - if (! this->triangle_indices.empty()) { - glsafe(::glGenBuffers(1, &this->triangle_indices_VBO_id)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_id)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices.size() * 4, this->triangle_indices.data(), GL_STATIC_DRAW)); - this->triangle_indices.clear(); - } - if (! this->quad_indices.empty()) { - glsafe(::glGenBuffers(1, &this->quad_indices_VBO_id)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices_VBO_id)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices.size() * 4, this->quad_indices.data(), GL_STATIC_DRAW)); - this->quad_indices.clear(); - } - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + if (! this->vertices_and_normals_interleaved.empty()) { + glsafe(::glGenBuffers(1, &this->vertices_and_normals_interleaved_VBO_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved.size() * 4, this->vertices_and_normals_interleaved.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + this->vertices_and_normals_interleaved.clear(); + } + if (! this->triangle_indices.empty()) { + glsafe(::glGenBuffers(1, &this->triangle_indices_VBO_id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_id)); + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices.size() * 4, this->triangle_indices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + this->triangle_indices.clear(); + } + if (! this->quad_indices.empty()) { + glsafe(::glGenBuffers(1, &this->quad_indices_VBO_id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices_VBO_id)); + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices.size() * 4, this->quad_indices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + this->quad_indices.clear(); } - this->shrink_to_fit(); } void GLIndexedVertexArray::release_geometry() @@ -137,89 +127,68 @@ void GLIndexedVertexArray::release_geometry() this->quad_indices_VBO_id = 0; } this->clear(); - this->shrink_to_fit(); } void GLIndexedVertexArray::render() const { - if (this->vertices_and_normals_interleaved_VBO_id) { - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id)); - glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)))); - glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr)); - } else { - glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), this->vertices_and_normals_interleaved.data() + 3)); - glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), this->vertices_and_normals_interleaved.data())); - } + assert(this->vertices_and_normals_interleaved_VBO_id != 0); + assert(this->triangle_indices_VBO_id != 0 || this->quad_indices_VBO_id != 0); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id)); + glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)))); + glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr)); + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - if (this->indexed()) { - if (this->vertices_and_normals_interleaved_VBO_id) { - // Render using the Vertex Buffer Objects. - if (this->triangle_indices_size > 0) { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_id)); - glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(this->triangle_indices_size), GL_UNSIGNED_INT, nullptr)); - } - if (this->quad_indices_size > 0) { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices_VBO_id)); - glsafe(::glDrawElements(GL_QUADS, GLsizei(this->quad_indices_size), GL_UNSIGNED_INT, nullptr)); - } - glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } else { - // Render in an immediate mode. - if (! this->triangle_indices.empty()) - glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(this->triangle_indices_size), GL_UNSIGNED_INT, this->triangle_indices.data())); - if (! this->quad_indices.empty()) - glsafe(::glDrawElements(GL_QUADS, GLsizei(this->quad_indices_size), GL_UNSIGNED_INT, this->quad_indices.data())); - } - } else - glsafe(::glDrawArrays(GL_TRIANGLES, 0, GLsizei(this->vertices_and_normals_interleaved_size / 6))); - - if (this->vertices_and_normals_interleaved_VBO_id) - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); -} - -void GLIndexedVertexArray::render( - const std::pair &tverts_range, - const std::pair &qverts_range) const -{ - assert(this->indexed()); - if (! this->indexed()) - return; - - if (this->vertices_and_normals_interleaved_VBO_id) { - // Render using the Vertex Buffer Objects. - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id)); - glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)))); - glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr)); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - if (this->triangle_indices_size > 0) { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_id)); - glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(std::min(this->triangle_indices_size, tverts_range.second - tverts_range.first)), GL_UNSIGNED_INT, (const void*)(tverts_range.first * 4))); - } - if (this->quad_indices_size > 0) { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices_VBO_id)); - glsafe(::glDrawElements(GL_QUADS, GLsizei(std::min(this->quad_indices_size, qverts_range.second - qverts_range.first)), GL_UNSIGNED_INT, (const void*)(qverts_range.first * 4))); - } - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } else { - // Render in an immediate mode. - glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), this->vertices_and_normals_interleaved.data() + 3)); - glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), this->vertices_and_normals_interleaved.data())); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - if (! this->triangle_indices.empty()) - glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(std::min(this->triangle_indices_size, tverts_range.second - tverts_range.first)), GL_UNSIGNED_INT, (const void*)(this->triangle_indices.data() + tverts_range.first))); - if (! this->quad_indices.empty()) - glsafe(::glDrawElements(GL_QUADS, GLsizei(std::min(this->quad_indices_size, qverts_range.second - qverts_range.first)), GL_UNSIGNED_INT, (const void*)(this->quad_indices.data() + qverts_range.first))); + // Render using the Vertex Buffer Objects. + if (this->triangle_indices_size > 0) { + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_id)); + glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(this->triangle_indices_size), GL_UNSIGNED_INT, nullptr)); + glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + if (this->quad_indices_size > 0) { + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices_VBO_id)); + glsafe(::glDrawElements(GL_QUADS, GLsizei(this->quad_indices_size), GL_UNSIGNED_INT, nullptr)); + glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); } glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); +} + +void GLIndexedVertexArray::render( + const std::pair& tverts_range, + const std::pair& qverts_range) const +{ + assert(this->vertices_and_normals_interleaved_VBO_id != 0); + assert(this->triangle_indices_VBO_id != 0 || this->quad_indices_VBO_id != 0); + + // Render using the Vertex Buffer Objects. + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id)); + glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)))); + glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr)); + + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); + + if (this->triangle_indices_size > 0) { + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_id)); + glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(std::min(this->triangle_indices_size, tverts_range.second - tverts_range.first)), GL_UNSIGNED_INT, (const void*)(tverts_range.first * 4))); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + if (this->quad_indices_size > 0) { + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices_VBO_id)); + glsafe(::glDrawElements(GL_QUADS, GLsizei(std::min(this->quad_indices_size, qverts_range.second - qverts_range.first)), GL_UNSIGNED_INT, (const void*)(qverts_range.first * 4))); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); } const float GLVolume::SELECTED_COLOR[4] = { 0.0f, 1.0f, 0.0f, 1.0f }; @@ -241,13 +210,12 @@ GLVolume::GLVolume(float r, float g, float b, float a) : m_transformed_bounding_box_dirty(true) , m_sla_shift_z(0.0) , m_transformed_convex_hull_bounding_box_dirty(true) - , m_convex_hull(nullptr) - , m_convex_hull_owned(false) // geometry_id == 0 -> invalid , geometry_id(std::pair(0, 0)) , extruder_id(0) , selected(false) , disabled(false) + , printable(true) , is_active(true) , zoom_to_volumes(true) , shader_outside_printer_detection_enabled(false) @@ -268,12 +236,6 @@ GLVolume::GLVolume(float r, float g, float b, float a) set_render_color(r, g, b, a); } -GLVolume::~GLVolume() -{ - if (m_convex_hull_owned) - delete m_convex_hull; -} - void GLVolume::set_render_color(float r, float g, float b, float a) { render_color[0] = r; @@ -311,6 +273,13 @@ void GLVolume::set_render_color() set_render_color(color, 4); } + if (!printable) + { + render_color[0] /= 4; + render_color[1] /= 4; + render_color[2] /= 4; + } + if (force_transparent) render_color[3] = color[3]; } @@ -335,12 +304,6 @@ void GLVolume::set_color_from_model_volume(const ModelVolume *model_volume) color[3] = model_volume->is_model_part() ? 1.f : 0.5f; } -void GLVolume::set_convex_hull(const TriangleMesh *convex_hull, bool owned) -{ - m_convex_hull = convex_hull; - m_convex_hull_owned = owned; -} - Transform3d GLVolume::world_matrix() const { Transform3d m = m_instance_transformation.get_matrix() * m_volume_transformation.get_matrix(); @@ -357,11 +320,12 @@ bool GLVolume::is_left_handed() const const BoundingBoxf3& GLVolume::transformed_bounding_box() const { - assert(bounding_box.defined || bounding_box.min(0) >= bounding_box.max(0) || bounding_box.min(1) >= bounding_box.max(1) || bounding_box.min(2) >= bounding_box.max(2)); + const BoundingBoxf3& box = bounding_box(); + assert(box.defined || box.min(0) >= box.max(0) || box.min(1) >= box.max(1) || box.min(2) >= box.max(2)); if (m_transformed_bounding_box_dirty) { - m_transformed_bounding_box = bounding_box.transformed(world_matrix()); + m_transformed_bounding_box = box.transformed(world_matrix()); m_transformed_bounding_box_dirty = false; } @@ -377,16 +341,17 @@ const BoundingBoxf3& GLVolume::transformed_convex_hull_bounding_box() const BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box(const Transform3d &trafo) const { - return (m_convex_hull != nullptr && m_convex_hull->stl.stats.number_of_facets > 0) ? + return (m_convex_hull && m_convex_hull->stl.stats.number_of_facets > 0) ? m_convex_hull->transformed_bounding_box(trafo) : - bounding_box.transformed(trafo); + bounding_box().transformed(trafo); } + void GLVolume::set_range(double min_z, double max_z) { - this->qverts_range.first = 0; + this->qverts_range.first = 0; this->qverts_range.second = this->indexed_vertex_array.quad_indices_size; - this->tverts_range.first = 0; + this->tverts_range.first = 0; this->tverts_range.second = this->indexed_vertex_array.triangle_indices_size; if (! this->print_zs.empty()) { // The Z layer range is specified. @@ -426,58 +391,17 @@ void GLVolume::render() const glFrontFace(GL_CW); glsafe(::glCullFace(GL_BACK)); glsafe(::glPushMatrix()); - glsafe(::glMultMatrixd(world_matrix().data())); - if (this->indexed_vertex_array.indexed()) - this->indexed_vertex_array.render(this->tverts_range, this->qverts_range); - else - this->indexed_vertex_array.render(); + + this->indexed_vertex_array.render(this->tverts_range, this->qverts_range); + glsafe(::glPopMatrix()); if (this->is_left_handed()) glFrontFace(GL_CCW); } -void GLVolume::render_VBOs(int color_id, int detection_id, int worldmatrix_id) const +void GLVolume::render(int color_id, int detection_id, int worldmatrix_id) const { - if (!is_active) - return; - - if (!indexed_vertex_array.vertices_and_normals_interleaved_VBO_id) - return; - - if (this->is_left_handed()) - glFrontFace(GL_CW); - - GLsizei n_triangles = GLsizei(std::min(indexed_vertex_array.triangle_indices_size, tverts_range.second - tverts_range.first)); - GLsizei n_quads = GLsizei(std::min(indexed_vertex_array.quad_indices_size, qverts_range.second - qverts_range.first)); - if (n_triangles + n_quads == 0) - { - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - if (color_id >= 0) - { - float color[4]; - ::memcpy((void*)color, (const void*)render_color, 4 * sizeof(float)); - glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)color)); - } - else - glsafe(::glColor4fv(render_color)); - - if (detection_id != -1) - glsafe(::glUniform1i(detection_id, shader_outside_printer_detection_enabled ? 1 : 0)); - - if (worldmatrix_id != -1) - glsafe(::glUniformMatrix4fv(worldmatrix_id, 1, GL_FALSE, (const GLfloat*)world_matrix().cast().data())); - - render(); - - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - - return; - } - if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)render_color)); else @@ -489,74 +413,7 @@ void GLVolume::render_VBOs(int color_id, int detection_id, int worldmatrix_id) c if (worldmatrix_id != -1) glsafe(::glUniformMatrix4fv(worldmatrix_id, 1, GL_FALSE, (const GLfloat*)world_matrix().cast().data())); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, indexed_vertex_array.vertices_and_normals_interleaved_VBO_id)); - glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)))); - glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr)); - - glsafe(::glPushMatrix()); - - glsafe(::glMultMatrixd(world_matrix().data())); - - if (n_triangles > 0) - { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexed_vertex_array.triangle_indices_VBO_id)); - glsafe(::glDrawElements(GL_TRIANGLES, n_triangles, GL_UNSIGNED_INT, (const void*)(tverts_range.first * 4))); - } - if (n_quads > 0) - { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexed_vertex_array.quad_indices_VBO_id)); - glsafe(::glDrawElements(GL_QUADS, n_quads, GL_UNSIGNED_INT, (const void*)(qverts_range.first * 4))); - } - - glsafe(::glPopMatrix()); - - if (this->is_left_handed()) - glFrontFace(GL_CCW); -} - -void GLVolume::render_legacy() const -{ - assert(!indexed_vertex_array.vertices_and_normals_interleaved_VBO_id); - if (!is_active) - return; - - if (this->is_left_handed()) - glFrontFace(GL_CW); - - GLsizei n_triangles = GLsizei(std::min(indexed_vertex_array.triangle_indices_size, tverts_range.second - tverts_range.first)); - GLsizei n_quads = GLsizei(std::min(indexed_vertex_array.quad_indices_size, qverts_range.second - qverts_range.first)); - if (n_triangles + n_quads == 0) - { - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - glsafe(::glColor4fv(render_color)); - render(); - - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - - return; - } - - glsafe(::glColor4fv(render_color)); - glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), indexed_vertex_array.vertices_and_normals_interleaved.data() + 3)); - glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), indexed_vertex_array.vertices_and_normals_interleaved.data())); - - glsafe(::glPushMatrix()); - - glsafe(::glMultMatrixd(world_matrix().data())); - - if (n_triangles > 0) - glsafe(::glDrawElements(GL_TRIANGLES, n_triangles, GL_UNSIGNED_INT, indexed_vertex_array.triangle_indices.data() + tverts_range.first)); - - if (n_quads > 0) - glsafe(::glDrawElements(GL_QUADS, n_quads, GL_UNSIGNED_INT, indexed_vertex_array.quad_indices.data() + qverts_range.first)); - - glsafe(::glPopMatrix()); - - if (this->is_left_handed()) - glFrontFace(GL_CCW); + render(); } bool GLVolume::is_sla_support() const { return this->composite_id.volume_id == -int(slaposSupportTree); } @@ -567,106 +424,104 @@ std::vector GLVolumeCollection::load_object( int obj_idx, const std::vector &instance_idxs, const std::string &color_by, - bool use_VBOs) + bool opengl_initialized) { std::vector volumes_idx; - for (int volume_idx = 0; volume_idx < int(model_object->volumes.size()); ++ volume_idx) + for (int volume_idx = 0; volume_idx < int(model_object->volumes.size()); ++volume_idx) for (int instance_idx : instance_idxs) - volumes_idx.emplace_back(this->GLVolumeCollection::load_object_volume(model_object, obj_idx, volume_idx, instance_idx, color_by, use_VBOs)); - return volumes_idx; + volumes_idx.emplace_back(this->GLVolumeCollection::load_object_volume(model_object, obj_idx, volume_idx, instance_idx, color_by, opengl_initialized)); + return volumes_idx; } int GLVolumeCollection::load_object_volume( - const ModelObject *model_object, - int obj_idx, - int volume_idx, - int instance_idx, - const std::string &color_by, - bool use_VBOs) + const ModelObject *model_object, + int obj_idx, + int volume_idx, + int instance_idx, + const std::string &color_by, + bool opengl_initialized) { const ModelVolume *model_volume = model_object->volumes[volume_idx]; const int extruder_id = model_volume->extruder_id(); - const ModelInstance *instance = model_object->instances[instance_idx]; - const TriangleMesh& mesh = model_volume->mesh; - float color[4]; + const ModelInstance *instance = model_object->instances[instance_idx]; + const TriangleMesh &mesh = model_volume->mesh(); + float color[4]; memcpy(color, GLVolume::MODEL_COLOR[((color_by == "volume") ? volume_idx : obj_idx) % 4], sizeof(float) * 3); -/* if (model_volume->is_support_blocker()) { - color[0] = 1.0f; - color[1] = 0.2f; - color[2] = 0.2f; - } else if (model_volume->is_support_enforcer()) { - color[0] = 0.2f; - color[1] = 0.2f; - color[2] = 1.0f; - } - color[3] = model_volume->is_model_part() ? 1.f : 0.5f; */ + /* if (model_volume->is_support_blocker()) { + color[0] = 1.0f; + color[1] = 0.2f; + color[2] = 0.2f; + } else if (model_volume->is_support_enforcer()) { + color[0] = 0.2f; + color[1] = 0.2f; + color[2] = 1.0f; + } + color[3] = model_volume->is_model_part() ? 1.f : 0.5f; */ color[3] = model_volume->is_model_part() ? 1.f : 0.5f; this->volumes.emplace_back(new GLVolume(color)); - GLVolume &v = *this->volumes.back(); + GLVolume& v = *this->volumes.back(); v.set_color_from_model_volume(model_volume); - v.indexed_vertex_array.load_mesh(mesh, use_VBOs); - - // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry(). - v.bounding_box = v.indexed_vertex_array.bounding_box(); - v.indexed_vertex_array.finalize_geometry(use_VBOs); - v.composite_id = GLVolume::CompositeID(obj_idx, volume_idx, instance_idx); + v.indexed_vertex_array.load_mesh(mesh); + v.indexed_vertex_array.finalize_geometry(opengl_initialized); + v.composite_id = GLVolume::CompositeID(obj_idx, volume_idx, instance_idx); if (model_volume->is_model_part()) { - // GLVolume will reference a convex hull from model_volume! - v.set_convex_hull(&model_volume->get_convex_hull(), false); + // GLVolume will reference a convex hull from model_volume! + v.set_convex_hull(model_volume->get_convex_hull_shared_ptr()); if (extruder_id != -1) v.extruder_id = extruder_id; } - v.is_modifier = ! model_volume->is_model_part(); + v.is_modifier = !model_volume->is_model_part(); v.shader_outside_printer_detection_enabled = model_volume->is_model_part(); v.set_instance_transformation(instance->get_transformation()); v.set_volume_transformation(model_volume->get_transformation()); - return int(this->volumes.size() - 1); + return int(this->volumes.size() - 1); } // Load SLA auxiliary GLVolumes (for support trees or pad). // This function produces volumes for multiple instances in a single shot, // as some object specific mesh conversions may be expensive. void GLVolumeCollection::load_object_auxiliary( - const SLAPrintObject *print_object, + const SLAPrintObject *print_object, int obj_idx, // pairs of - const std::vector> &instances, + const std::vector>& instances, SLAPrintObjectStep milestone, // Timestamp of the last change of the milestone size_t timestamp, - bool use_VBOs) + bool opengl_initialized) { assert(print_object->is_step_done(milestone)); Transform3d mesh_trafo_inv = print_object->trafo().inverse(); // Get the support mesh. TriangleMesh mesh = print_object->get_mesh(milestone); mesh.transform(mesh_trafo_inv); - // Convex hull is required for out of print bed detection. - TriangleMesh convex_hull = mesh.convex_hull_3d(); - for (const std::pair &instance_idx : instances) { - const ModelInstance &model_instance = *print_object->model_object()->instances[instance_idx.first]; + // Convex hull is required for out of print bed detection. + TriangleMesh convex_hull = mesh.convex_hull_3d(); + for (const std::pair& instance_idx : instances) { + const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first]; this->volumes.emplace_back(new GLVolume((milestone == slaposBasePool) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR)); - GLVolume &v = *this->volumes.back(); - v.indexed_vertex_array.load_mesh(mesh, use_VBOs); - // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry(). - v.bounding_box = v.indexed_vertex_array.bounding_box(); - v.indexed_vertex_array.finalize_geometry(use_VBOs); - v.composite_id = GLVolume::CompositeID(obj_idx, - int(milestone), (int)instance_idx.first); + GLVolume& v = *this->volumes.back(); + v.indexed_vertex_array.load_mesh(mesh); + v.indexed_vertex_array.finalize_geometry(opengl_initialized); + v.composite_id = GLVolume::CompositeID(obj_idx, -int(milestone), (int)instance_idx.first); v.geometry_id = std::pair(timestamp, model_instance.id().id); - // Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance. - v.set_convex_hull((&instance_idx == &instances.back()) ? new TriangleMesh(std::move(convex_hull)) : new TriangleMesh(convex_hull), true); - v.is_modifier = false; + // Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance. + if (&instance_idx == &instances.back()) + v.set_convex_hull(std::move(convex_hull)); + else + v.set_convex_hull(convex_hull); + v.is_modifier = false; v.shader_outside_printer_detection_enabled = (milestone == slaposSupportTree); v.set_instance_transformation(model_instance.get_transformation()); - // Leave the volume transformation at identity. + // Leave the volume transformation at identity. // v.set_volume_transformation(model_volume->get_transformation()); } } int GLVolumeCollection::load_wipe_tower_preview( - int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width) + int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized) { if (depth < 0.01f) return int(this->volumes.size() - 1); @@ -691,45 +546,42 @@ int GLVolumeCollection::load_wipe_tower_preview( { 38.453f, 0, 1 }, { 0, 0, 1 }, { 0, -depth, 1 }, { 100.0f, -depth, 1 }, { 100.0f, 0, 1 }, { 61.547f, 0, 1 }, { 55.7735f, -10.0f, 1 }, { 44.2265f, 10.0f, 1 } }; int out_facets_idx[][3] = { { 0, 1, 2 }, { 3, 4, 5 }, { 6, 5, 0 }, { 3, 5, 6 }, { 6, 2, 7 }, { 6, 0, 2 }, { 8, 9, 10 }, { 11, 12, 13 }, { 10, 11, 14 }, { 14, 11, 13 }, { 15, 8, 14 }, {8, 10, 14}, {3, 12, 4}, {3, 13, 12}, {6, 13, 3}, {6, 14, 13}, {7, 14, 6}, {7, 15, 14}, {2, 15, 7}, {2, 8, 15}, {1, 8, 2}, {1, 9, 8}, - {0, 9, 1}, {0, 10, 9}, {5, 10, 0}, {5, 11, 10}, {4, 11, 5}, {4, 12, 11}}; - for (int i=0;i<16;++i) - points.push_back(Vec3d(out_points_idx[i][0] / (100.f/min_width), out_points_idx[i][1] + depth, out_points_idx[i][2])); - for (int i=0;i<28;++i) + {0, 9, 1}, {0, 10, 9}, {5, 10, 0}, {5, 11, 10}, {4, 11, 5}, {4, 12, 11} }; + for (int i = 0; i < 16; ++i) + points.push_back(Vec3d(out_points_idx[i][0] / (100.f / min_width), out_points_idx[i][1] + depth, out_points_idx[i][2])); + for (int i = 0; i < 28; ++i) facets.push_back(Vec3crd(out_facets_idx[i][0], out_facets_idx[i][1], out_facets_idx[i][2])); TriangleMesh tooth_mesh(points, facets); // We have the mesh ready. It has one tooth and width of min_width. We will now append several of these together until we are close to // the required width of the block. Than we can scale it precisely. - size_t n = std::max(1, int(width/min_width)); // How many shall be merged? - for (size_t i=0;ivolumes.emplace_back(new GLVolume(color)); - GLVolume &v = *this->volumes.back(); - v.indexed_vertex_array.load_mesh(mesh, use_VBOs); + GLVolume& v = *this->volumes.back(); + v.indexed_vertex_array.load_mesh(mesh); + v.indexed_vertex_array.finalize_geometry(opengl_initialized); v.set_volume_offset(Vec3d(pos_x, pos_y, 0.0)); - v.set_volume_rotation(Vec3d(0., 0., (M_PI/180.) * rotation_angle)); - - // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry(). - v.bounding_box = v.indexed_vertex_array.bounding_box(); - v.indexed_vertex_array.finalize_geometry(use_VBOs); - v.composite_id = GLVolume::CompositeID(obj_idx, 0, 0); + v.set_volume_rotation(Vec3d(0., 0., (M_PI / 180.) * rotation_angle)); + v.composite_id = GLVolume::CompositeID(obj_idx, 0, 0); v.geometry_id.first = 0; v.geometry_id.second = wipe_tower_instance_id().id; v.is_wipe_tower = true; - v.shader_outside_printer_detection_enabled = ! size_unknown; + v.shader_outside_printer_detection_enabled = !size_unknown; return int(this->volumes.size() - 1); } @@ -753,7 +605,7 @@ GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCo { for (GLVolumeWithIdAndZ& volume : list) { - volume.second.second = volume.first->bounding_box.transformed(view_matrix * volume.first->world_matrix()).max(2); + volume.second.second = volume.first->bounding_box().transformed(view_matrix * volume.first->world_matrix()).max(2); } std::sort(list.begin(), list.end(), @@ -770,7 +622,7 @@ GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCo return list; } -void GLVolumeCollection::render_VBOs(GLVolumeCollection::ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func) const +void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func) const { glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); @@ -808,7 +660,7 @@ void GLVolumeCollection::render_VBOs(GLVolumeCollection::ERenderType type, bool GLVolumeWithIdAndZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func); for (GLVolumeWithIdAndZ& volume : to_render) { volume.first->set_render_color(); - volume.first->render_VBOs(color_id, print_box_detection_id, print_box_worldmatrix_id); + volume.first->render(color_id, print_box_detection_id, print_box_worldmatrix_id); } glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); @@ -823,34 +675,6 @@ void GLVolumeCollection::render_VBOs(GLVolumeCollection::ERenderType type, bool glsafe(::glDisable(GL_BLEND)); } -void GLVolumeCollection::render_legacy(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func) const -{ - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - - glsafe(::glCullFace(GL_BACK)); - if (disable_cullface) - glsafe(::glDisable(GL_CULL_FACE)); - - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - - GLVolumeWithIdAndZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func); - for (GLVolumeWithIdAndZ& volume : to_render) - { - volume.first->set_render_color(); - volume.first->render_legacy(); - } - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - if (disable_cullface) - glsafe(::glEnable(GL_CULL_FACE)); - - glsafe(::glDisable(GL_BLEND)); -} - bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, ModelInstance::EPrintVolumeState* out_state) { if (config == nullptr) @@ -1010,6 +834,230 @@ std::vector GLVolumeCollection::get_current_print_zs(bool active_only) c return print_zs; } +size_t GLVolumeCollection::cpu_memory_used() const +{ + size_t memsize = sizeof(*this) + this->volumes.capacity() * sizeof(GLVolume); + for (const GLVolume *volume : this->volumes) + memsize += volume->cpu_memory_used(); + return memsize; +} + +size_t GLVolumeCollection::gpu_memory_used() const +{ + size_t memsize = 0; + for (const GLVolume *volume : this->volumes) + memsize += volume->gpu_memory_used(); + return memsize; +} + +std::string GLVolumeCollection::log_memory_info() const +{ + return " (GLVolumeCollection RAM: " + format_memsize_MB(this->cpu_memory_used()) + " GPU: " + format_memsize_MB(this->gpu_memory_used()) + " Both: " + format_memsize_MB(this->gpu_memory_used()) + ")"; +} + +bool can_export_to_obj(const GLVolume& volume) +{ + if (!volume.is_active || !volume.is_extrusion_path) + return false; + + if (volume.indexed_vertex_array.triangle_indices.empty() && (std::min(volume.indexed_vertex_array.triangle_indices_size, volume.tverts_range.second - volume.tverts_range.first) == 0)) + return false; + + if (volume.indexed_vertex_array.quad_indices.empty() && (std::min(volume.indexed_vertex_array.quad_indices_size, volume.qverts_range.second - volume.qverts_range.first) == 0)) + return false; + + return true; +} + +bool GLVolumeCollection::has_toolpaths_to_export() const +{ + for (const GLVolume* volume : this->volumes) + { + if (can_export_to_obj(*volume)) + return true; + } + + return false; +} + +void GLVolumeCollection::export_toolpaths_to_obj(const char* filename) const +{ + if (filename == nullptr) + return; + + if (!has_toolpaths_to_export()) + return; + + // collect color information to generate materials + std::set> colors; + for (const GLVolume* volume : this->volumes) + { + if (!can_export_to_obj(*volume)) + continue; + + std::array color; + ::memcpy((void*)color.data(), (const void*)volume->color, 4 * sizeof(float)); + colors.insert(color); + } + + // save materials file + boost::filesystem::path mat_filename(filename); + mat_filename.replace_extension("mtl"); + FILE* fp = boost::nowide::fopen(mat_filename.string().c_str(), "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) << "GLVolumeCollection::export_toolpaths_to_obj: Couldn't open " << mat_filename.string().c_str() << " for writing"; + return; + } + + fprintf(fp, "# G-Code Toolpaths Materials\n"); + fprintf(fp, "# Generated by %s based on Slic3r\n", SLIC3R_BUILD_ID); + + unsigned int colors_count = 1; + for (const std::array& color : colors) + { + fprintf(fp, "\nnewmtl material_%d\n", colors_count++); + fprintf(fp, "Ka 1 1 1\n"); + fprintf(fp, "Kd %f %f %f\n", color[0], color[1], color[2]); + fprintf(fp, "Ks 0 0 0\n"); + } + + fclose(fp); + + // save geometry file + fp = boost::nowide::fopen(filename, "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) << "GLVolumeCollection::export_toolpaths_to_obj: Couldn't open " << filename << " for writing"; + return; + } + + fprintf(fp, "# G-Code Toolpaths\n"); + fprintf(fp, "# Generated by %s based on Slic3r\n", SLIC3R_BUILD_ID); + fprintf(fp, "\nmtllib ./%s\n", mat_filename.filename().string().c_str()); + + unsigned int vertices_count = 0; + unsigned int volumes_count = 0; + + for (const GLVolume* volume : this->volumes) + { + if (!can_export_to_obj(*volume)) + continue; + + std::vector vertices_and_normals_interleaved; + std::vector triangle_indices; + std::vector quad_indices; + + if (!volume->indexed_vertex_array.vertices_and_normals_interleaved.empty()) + // data are in CPU memory + vertices_and_normals_interleaved = volume->indexed_vertex_array.vertices_and_normals_interleaved; + else if ((volume->indexed_vertex_array.vertices_and_normals_interleaved_VBO_id != 0) && (volume->indexed_vertex_array.vertices_and_normals_interleaved_size != 0)) + { + // data are in GPU memory + vertices_and_normals_interleaved = std::vector(volume->indexed_vertex_array.vertices_and_normals_interleaved_size, 0.0f); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, volume->indexed_vertex_array.vertices_and_normals_interleaved_VBO_id)); + glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, 0, vertices_and_normals_interleaved.size() * sizeof(float), vertices_and_normals_interleaved.data())); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + } + else + continue; + + if (!volume->indexed_vertex_array.triangle_indices.empty()) + { + // data are in CPU memory + size_t size = std::min(volume->indexed_vertex_array.triangle_indices.size(), volume->tverts_range.second - volume->tverts_range.first); + if (size != 0) + { + std::vector::const_iterator it_begin = volume->indexed_vertex_array.triangle_indices.begin() + volume->tverts_range.first; + std::vector::const_iterator it_end = volume->indexed_vertex_array.triangle_indices.begin() + volume->tverts_range.first + size; + std::copy(it_begin, it_end, std::back_inserter(triangle_indices)); + } + } + else if ((volume->indexed_vertex_array.triangle_indices_VBO_id != 0) && (volume->indexed_vertex_array.triangle_indices_size != 0)) + { + // data are in GPU memory + size_t size = std::min(volume->indexed_vertex_array.triangle_indices_size, volume->tverts_range.second - volume->tverts_range.first); + if (size != 0) + { + triangle_indices = std::vector(size, 0); + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, volume->indexed_vertex_array.triangle_indices_VBO_id)); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, volume->tverts_range.first * sizeof(int), size * sizeof(int), triangle_indices.data())); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + } + + if (!volume->indexed_vertex_array.quad_indices.empty()) + { + // data are in CPU memory + size_t size = std::min(volume->indexed_vertex_array.quad_indices.size(), volume->qverts_range.second - volume->qverts_range.first); + if (size != 0) + { + std::vector::const_iterator it_begin = volume->indexed_vertex_array.quad_indices.begin() + volume->qverts_range.first; + std::vector::const_iterator it_end = volume->indexed_vertex_array.quad_indices.begin() + volume->qverts_range.first + size; + std::copy(it_begin, it_end, std::back_inserter(quad_indices)); + } + } + else if ((volume->indexed_vertex_array.quad_indices_VBO_id != 0) && (volume->indexed_vertex_array.quad_indices_size != 0)) + { + // data are in GPU memory + size_t size = std::min(volume->indexed_vertex_array.quad_indices_size, volume->qverts_range.second - volume->qverts_range.first); + if (size != 0) + { + quad_indices = std::vector(size, 0); + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, volume->indexed_vertex_array.quad_indices_VBO_id)); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, volume->qverts_range.first * sizeof(int), size * sizeof(int), quad_indices.data())); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + } + + if (triangle_indices.empty() && quad_indices.empty()) + continue; + + fprintf(fp, "\n# vertices volume %d\n", volumes_count); + for (unsigned int i = 0; i < vertices_and_normals_interleaved.size(); i += 6) + { + fprintf(fp, "v %f %f %f\n", vertices_and_normals_interleaved[i + 3], vertices_and_normals_interleaved[i + 4], vertices_and_normals_interleaved[i + 5]); + } + + fprintf(fp, "\n# normals volume %d\n", volumes_count); + for (unsigned int i = 0; i < vertices_and_normals_interleaved.size(); i += 6) + { + fprintf(fp, "vn %f %f %f\n", vertices_and_normals_interleaved[i + 0], vertices_and_normals_interleaved[i + 1], vertices_and_normals_interleaved[i + 2]); + } + + std::array color; + ::memcpy((void*)color.data(), (const void*)volume->color, 4 * sizeof(float)); + colors.insert(color); + fprintf(fp, "\n# material volume %d\n", volumes_count); + fprintf(fp, "usemtl material_%lld\n", 1 + std::distance(colors.begin(), colors.find(color))); + + fprintf(fp, "\n# triangular facets volume %d\n", volumes_count); + for (unsigned int i = 0; i < triangle_indices.size(); i += 3) + { + int id_v1 = vertices_count + 1 + triangle_indices[i + 0]; + int id_v2 = vertices_count + 1 + triangle_indices[i + 1]; + int id_v3 = vertices_count + 1 + triangle_indices[i + 2]; + fprintf(fp, "f %d//%d %d//%d %d//%d\n", id_v1, id_v1, id_v2, id_v2, id_v3, id_v3); + } + + fprintf(fp, "\n# quadrangular facets volume %d\n", volumes_count); + for (unsigned int i = 0; i < quad_indices.size(); i += 4) + { + int id_v1 = vertices_count + 1 + quad_indices[i + 0]; + int id_v2 = vertices_count + 1 + quad_indices[i + 1]; + int id_v3 = vertices_count + 1 + quad_indices[i + 2]; + int id_v4 = vertices_count + 1 + quad_indices[i + 3]; + fprintf(fp, "f %d//%d %d//%d %d//%d %d//%d\n", id_v1, id_v1, id_v2, id_v2, id_v3, id_v3, id_v4, id_v4); + } + + ++volumes_count; + vertices_count += vertices_and_normals_interleaved.size() / 6; + } + + fclose(fp); +} + // caller is responsible for supplying NO lines with zero length static void thick_lines_to_indexed_vertex_array( const Lines &lines, @@ -1031,19 +1079,18 @@ static void thick_lines_to_indexed_vertex_array( // right, left, top, bottom int idx_prev[4] = { -1, -1, -1, -1 }; double bottom_z_prev = 0.; - Vec2d b1_prev(Vec2d::Zero()); - Vec2d v_prev(Vec2d::Zero()); + Vec2d b1_prev(Vec2d::Zero()); + Vec2d v_prev(Vec2d::Zero()); int idx_initial[4] = { -1, -1, -1, -1 }; double width_initial = 0.; double bottom_z_initial = 0.0; + double len_prev = 0.0; // loop once more in case of closed loops size_t lines_end = closed ? (lines.size() + 1) : lines.size(); for (size_t ii = 0; ii < lines_end; ++ ii) { size_t i = (ii == lines.size()) ? 0 : ii; const Line &line = lines[i]; - double len = unscale(line.length()); - double inv_len = 1.0 / len; double bottom_z = top_z - heights[i]; double middle_z = 0.5 * (top_z + bottom_z); double width = widths[i]; @@ -1052,8 +1099,8 @@ static void thick_lines_to_indexed_vertex_array( bool is_last = (ii == lines_end - 1); bool is_closing = closed && is_last; - Vec2d v = unscale(line.vector()); - v *= inv_len; + Vec2d v = unscale(line.vector()).normalized(); + double len = unscale(line.length()); Vec2d a = unscale(line.a); Vec2d b = unscale(line.b); @@ -1072,9 +1119,7 @@ static void thick_lines_to_indexed_vertex_array( } // calculate new XY normals - Vector n = line.normal(); - Vec3d xy_right_normal = unscale(n(0), n(1), 0); - xy_right_normal *= inv_len; + Vec2d xy_right_normal = unscale(line.normal()).normalized(); int idx_a[4]; int idx_b[4]; @@ -1102,9 +1147,9 @@ static void thick_lines_to_indexed_vertex_array( idx_a[BOTTOM] = idx_last ++; volume.push_geometry(a(0), a(1), bottom_z, 0., 0., -1.); idx_a[LEFT ] = idx_last ++; - volume.push_geometry(a2(0), a2(1), middle_z, -xy_right_normal(0), -xy_right_normal(1), -xy_right_normal(2)); + volume.push_geometry(a2(0), a2(1), middle_z, -xy_right_normal(0), -xy_right_normal(1), 0.0); idx_a[RIGHT] = idx_last ++; - volume.push_geometry(a1(0), a1(1), middle_z, xy_right_normal(0), xy_right_normal(1), xy_right_normal(2)); + volume.push_geometry(a1(0), a1(1), middle_z, xy_right_normal(0), xy_right_normal(1), 0.0); } else { idx_a[BOTTOM] = idx_prev[BOTTOM]; @@ -1119,18 +1164,36 @@ static void thick_lines_to_indexed_vertex_array( // Continuing a previous segment. // Share left / right vertices if possible. double v_dot = v_prev.dot(v); - bool sharp = v_dot < 0.707; // sin(45 degrees) + // To reduce gpu memory usage, we try to reuse vertices + // To reduce the visual artifacts, due to averaged normals, we allow to reuse vertices only when any of two adjacent edges + // is longer than a fixed threshold. + // The following value is arbitrary, it comes from tests made on a bunch of models showing the visual artifacts + double len_threshold = 2.5; + + // Generate new vertices if the angle between adjacent edges is greater than 45 degrees or thresholds conditions are met + bool sharp = (v_dot < 0.707) || (len_prev > len_threshold) || (len > len_threshold); if (sharp) { if (!bottom_z_different) { // Allocate new left / right points for the start of this segment as these points will receive their own normals to indicate a sharp turn. idx_a[RIGHT] = idx_last++; - volume.push_geometry(a1(0), a1(1), middle_z, xy_right_normal(0), xy_right_normal(1), xy_right_normal(2)); + volume.push_geometry(a1(0), a1(1), middle_z, xy_right_normal(0), xy_right_normal(1), 0.0); idx_a[LEFT] = idx_last++; - volume.push_geometry(a2(0), a2(1), middle_z, -xy_right_normal(0), -xy_right_normal(1), -xy_right_normal(2)); + volume.push_geometry(a2(0), a2(1), middle_z, -xy_right_normal(0), -xy_right_normal(1), 0.0); + if (cross2(v_prev, v) > 0.) { + // Right turn. Fill in the right turn wedge. + volume.push_triangle(idx_prev[RIGHT], idx_a[RIGHT], idx_prev[TOP]); + volume.push_triangle(idx_prev[RIGHT], idx_prev[BOTTOM], idx_a[RIGHT]); + } + else { + // Left turn. Fill in the left turn wedge. + volume.push_triangle(idx_prev[LEFT], idx_prev[TOP], idx_a[LEFT]); + volume.push_triangle(idx_prev[LEFT], idx_a[LEFT], idx_prev[BOTTOM]); + } } } - if (v_dot > 0.9) { + else + { if (!bottom_z_different) { // The two successive segments are nearly collinear. @@ -1138,45 +1201,6 @@ static void thick_lines_to_indexed_vertex_array( idx_a[RIGHT] = idx_prev[RIGHT]; } } - else if (!sharp) { - if (!bottom_z_different) - { - // Create a sharp corner with an overshot and average the left / right normals. - // At the crease angle of 45 degrees, the overshot at the corner will be less than (1-1/cos(PI/8)) = 8.2% over an arc. - Vec2d intersection(Vec2d::Zero()); - Geometry::ray_ray_intersection(b1_prev, v_prev, a1, v, intersection); - a1 = intersection; - a2 = 2. * a - intersection; - assert((a - a1).norm() < width); - assert((a - a2).norm() < width); - float *n_left_prev = volume.vertices_and_normals_interleaved.data() + idx_prev[LEFT ] * 6; - float *p_left_prev = n_left_prev + 3; - float *n_right_prev = volume.vertices_and_normals_interleaved.data() + idx_prev[RIGHT] * 6; - float *p_right_prev = n_right_prev + 3; - p_left_prev [0] = float(a2(0)); - p_left_prev [1] = float(a2(1)); - p_right_prev[0] = float(a1(0)); - p_right_prev[1] = float(a1(1)); - xy_right_normal(0) += n_right_prev[0]; - xy_right_normal(1) += n_right_prev[1]; - xy_right_normal *= 1. / xy_right_normal.norm(); - n_left_prev [0] = float(-xy_right_normal(0)); - n_left_prev [1] = float(-xy_right_normal(1)); - n_right_prev[0] = float( xy_right_normal(0)); - n_right_prev[1] = float( xy_right_normal(1)); - idx_a[LEFT ] = idx_prev[LEFT ]; - idx_a[RIGHT] = idx_prev[RIGHT]; - } - } - else if (cross2(v_prev, v) > 0.) { - // Right turn. Fill in the right turn wedge. - volume.push_triangle(idx_prev[RIGHT], idx_a [RIGHT], idx_prev[TOP] ); - volume.push_triangle(idx_prev[RIGHT], idx_prev[BOTTOM], idx_a [RIGHT] ); - } else { - // Left turn. Fill in the left turn wedge. - volume.push_triangle(idx_prev[LEFT], idx_prev[TOP], idx_a [LEFT] ); - volume.push_triangle(idx_prev[LEFT], idx_a [LEFT], idx_prev[BOTTOM]); - } if (is_closing) { if (!sharp) { if (!bottom_z_different) @@ -1215,14 +1239,15 @@ static void thick_lines_to_indexed_vertex_array( } // Generate new vertices for the end of this line segment. idx_b[LEFT ] = idx_last ++; - volume.push_geometry(b2(0), b2(1), middle_z, -xy_right_normal(0), -xy_right_normal(1), -xy_right_normal(2)); + volume.push_geometry(b2(0), b2(1), middle_z, -xy_right_normal(0), -xy_right_normal(1), 0.0); idx_b[RIGHT ] = idx_last ++; - volume.push_geometry(b1(0), b1(1), middle_z, xy_right_normal(0), xy_right_normal(1), xy_right_normal(2)); + volume.push_geometry(b1(0), b1(1), middle_z, xy_right_normal(0), xy_right_normal(1), 0.0); memcpy(idx_prev, idx_b, 4 * sizeof(int)); bottom_z_prev = bottom_z; b1_prev = b1; v_prev = v; + len_prev = len; if (bottom_z_different && (closed || (!is_first && !is_last))) { @@ -1276,9 +1301,10 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines, int idx_initial[4] = { -1, -1, -1, -1 }; int idx_prev[4] = { -1, -1, -1, -1 }; double z_prev = 0.0; - Vec3d n_right_prev = Vec3d::Zero(); - Vec3d n_top_prev = Vec3d::Zero(); - Vec3d unit_v_prev = Vec3d::Zero(); + double len_prev = 0.0; + Vec3d n_right_prev = Vec3d::Zero(); + Vec3d n_top_prev = Vec3d::Zero(); + Vec3d unit_v_prev = Vec3d::Zero(); double width_initial = 0.0; // new vertices around the line endpoints @@ -1297,21 +1323,23 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines, double width = widths[i]; Vec3d unit_v = unscale(line.vector()).normalized(); + double len = unscale(line.length()); Vec3d n_top = Vec3d::Zero(); Vec3d n_right = Vec3d::Zero(); - Vec3d unit_positive_z(0.0, 0.0, 1.0); - + if ((line.a(0) == line.b(0)) && (line.a(1) == line.b(1))) { // vertical segment - n_right = (line.a(2) < line.b(2)) ? Vec3d(-1.0, 0.0, 0.0) : Vec3d(1.0, 0.0, 0.0); - n_top = Vec3d(0.0, 1.0, 0.0); + n_top = Vec3d::UnitY(); + n_right = Vec3d::UnitX(); + if (line.a(2) < line.b(2)) + n_right = -n_right; } else { - // generic segment - n_right = unit_v.cross(unit_positive_z).normalized(); + // horizontal segment + n_right = unit_v.cross(Vec3d::UnitZ()).normalized(); n_top = n_right.cross(unit_v).normalized(); } @@ -1372,9 +1400,16 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines, // Continuing a previous segment. // Share left / right vertices if possible. double v_dot = unit_v_prev.dot(unit_v); - bool is_sharp = v_dot < 0.707; // sin(45 degrees) bool is_right_turn = n_top_prev.dot(unit_v_prev.cross(unit_v)) > 0.0; + // To reduce gpu memory usage, we try to reuse vertices + // To reduce the visual artifacts, due to averaged normals, we allow to reuse vertices only when any of two adjacent edges + // is longer than a fixed threshold. + // The following value is arbitrary, it comes from tests made on a bunch of models showing the visual artifacts + double len_threshold = 2.5; + + // Generate new vertices if the angle between adjacent edges is greater than 45 degrees or thresholds conditions are met + bool is_sharp = (v_dot < 0.707) || (len_prev > len_threshold) || (len > len_threshold); if (is_sharp) { // Allocate new left / right points for the start of this segment as these points will receive their own normals to indicate a sharp turn. @@ -1382,65 +1417,26 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines, volume.push_geometry(a[RIGHT], n_right); idx_a[LEFT] = idx_last++; volume.push_geometry(a[LEFT], n_left); - } - if (v_dot > 0.9) + if (is_right_turn) + { + // Right turn. Fill in the right turn wedge. + volume.push_triangle(idx_prev[RIGHT], idx_a[RIGHT], idx_prev[TOP]); + volume.push_triangle(idx_prev[RIGHT], idx_prev[BOTTOM], idx_a[RIGHT]); + } + else + { + // Left turn. Fill in the left turn wedge. + volume.push_triangle(idx_prev[LEFT], idx_prev[TOP], idx_a[LEFT]); + volume.push_triangle(idx_prev[LEFT], idx_a[LEFT], idx_prev[BOTTOM]); + } + } + else { // The two successive segments are nearly collinear. idx_a[LEFT] = idx_prev[LEFT]; idx_a[RIGHT] = idx_prev[RIGHT]; } - else if (!is_sharp) - { - // Create a sharp corner with an overshot and average the left / right normals. - // At the crease angle of 45 degrees, the overshot at the corner will be less than (1-1/cos(PI/8)) = 8.2% over an arc. - - // averages normals - Vec3d average_n_right = 0.5 * (n_right + n_right_prev).normalized(); - Vec3d average_n_left = -average_n_right; - Vec3d average_rl_displacement = 0.5 * width * average_n_right; - - // updates vertices around a - a[RIGHT] = l_a + average_rl_displacement; - a[LEFT] = l_a - average_rl_displacement; - - // updates previous line normals - float* normal_left_prev = volume.vertices_and_normals_interleaved.data() + idx_prev[LEFT] * 6; - normal_left_prev[0] = float(average_n_left(0)); - normal_left_prev[1] = float(average_n_left(1)); - normal_left_prev[2] = float(average_n_left(2)); - - float* normal_right_prev = volume.vertices_and_normals_interleaved.data() + idx_prev[RIGHT] * 6; - normal_right_prev[0] = float(average_n_right(0)); - normal_right_prev[1] = float(average_n_right(1)); - normal_right_prev[2] = float(average_n_right(2)); - - // updates previous line's vertices around b - float* b_left_prev = normal_left_prev + 3; - b_left_prev[0] = float(a[LEFT](0)); - b_left_prev[1] = float(a[LEFT](1)); - b_left_prev[2] = float(a[LEFT](2)); - - float* b_right_prev = normal_right_prev + 3; - b_right_prev[0] = float(a[RIGHT](0)); - b_right_prev[1] = float(a[RIGHT](1)); - b_right_prev[2] = float(a[RIGHT](2)); - - idx_a[LEFT] = idx_prev[LEFT]; - idx_a[RIGHT] = idx_prev[RIGHT]; - } - else if (is_right_turn) - { - // Right turn. Fill in the right turn wedge. - volume.push_triangle(idx_prev[RIGHT], idx_a[RIGHT], idx_prev[TOP]); - volume.push_triangle(idx_prev[RIGHT], idx_prev[BOTTOM], idx_a[RIGHT]); - } - else - { - // Left turn. Fill in the left turn wedge. - volume.push_triangle(idx_prev[LEFT], idx_prev[TOP], idx_a[LEFT]); - volume.push_triangle(idx_prev[LEFT], idx_a[LEFT], idx_prev[BOTTOM]); - } if (ii == lines.size()) { @@ -1492,6 +1488,7 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines, n_right_prev = n_right; n_top_prev = n_top; unit_v_prev = unit_v; + len_prev = len; if (!closed) { @@ -1705,8 +1702,7 @@ void _3DScene::point3_to_verts(const Vec3crd& point, double width, double height GUI::GLCanvas3DManager _3DScene::s_canvas_mgr; GLModel::GLModel() - : m_useVBOs(false) - , m_filename("") + : m_filename("") { m_volume.shader_outside_printer_detection_enabled = false; } @@ -1754,19 +1750,11 @@ void GLModel::set_scale(const Vec3d& scale) void GLModel::reset() { - m_volume.release_geometry(); + m_volume.indexed_vertex_array.release_geometry(); m_filename = ""; } void GLModel::render() const -{ - if (m_useVBOs) - render_VBOs(); - else - render_legacy(); -} - -void GLModel::render_VBOs() const { glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); @@ -1779,7 +1767,8 @@ void GLModel::render_VBOs() const glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program_id)); GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1; glcheck(); - m_volume.render_VBOs(color_id, -1, -1); + + m_volume.render(color_id, -1, -1); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); @@ -1790,26 +1779,7 @@ void GLModel::render_VBOs() const glsafe(::glDisable(GL_BLEND)); } -void GLModel::render_legacy() const -{ - glsafe(::glEnable(GL_LIGHTING)); - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - - glsafe(::glCullFace(GL_BACK)); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - - m_volume.render_legacy(); - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - glsafe(::glDisable(GL_BLEND)); - glsafe(::glDisable(GL_LIGHTING)); -} - -bool GLArrow::on_init(bool useVBOs) +bool GLArrow::on_init() { Pointf3s vertices; std::vector triangles; @@ -1862,9 +1832,8 @@ bool GLArrow::on_init(bool useVBOs) triangles.emplace_back(6, 0, 7); triangles.emplace_back(7, 13, 6); - m_useVBOs = useVBOs; - m_volume.indexed_vertex_array.load_mesh(TriangleMesh(vertices, triangles), useVBOs); - m_volume.finalize_geometry(m_useVBOs); + m_volume.indexed_vertex_array.load_mesh(TriangleMesh(vertices, triangles)); + m_volume.indexed_vertex_array.finalize_geometry(true); return true; } @@ -1876,7 +1845,7 @@ GLCurvedArrow::GLCurvedArrow(unsigned int resolution) m_resolution = 1; } -bool GLCurvedArrow::on_init(bool useVBOs) +bool GLCurvedArrow::on_init() { Pointf3s vertices; std::vector triangles; @@ -1977,14 +1946,12 @@ bool GLCurvedArrow::on_init(bool useVBOs) triangles.emplace_back(vertices_per_level, vertices_per_level + 1, 0); triangles.emplace_back(vertices_per_level, 2 * vertices_per_level + 1, vertices_per_level + 1); - m_useVBOs = useVBOs; - m_volume.indexed_vertex_array.load_mesh(TriangleMesh(vertices, triangles), useVBOs); - m_volume.bounding_box = m_volume.indexed_vertex_array.bounding_box(); - m_volume.finalize_geometry(m_useVBOs); + m_volume.indexed_vertex_array.load_mesh(TriangleMesh(vertices, triangles)); + m_volume.indexed_vertex_array.finalize_geometry(true); return true; } -bool GLBed::on_init_from_file(const std::string& filename, bool useVBOs) +bool GLBed::on_init_from_file(const std::string& filename) { reset(); @@ -2005,28 +1972,19 @@ bool GLBed::on_init_from_file(const std::string& filename, bool useVBOs) } m_filename = filename; - m_useVBOs = useVBOs; - ModelObject* model_object = model.objects.front(); - model_object->center_around_origin(); - - TriangleMesh mesh = model.mesh(); - mesh.repair(); - - m_volume.indexed_vertex_array.load_mesh(mesh, useVBOs); + m_volume.indexed_vertex_array.load_mesh(model.mesh()); + m_volume.indexed_vertex_array.finalize_geometry(true); float color[4] = { 0.235f, 0.235f, 0.235f, 1.0f }; set_color(color, 4); - m_volume.bounding_box = m_volume.indexed_vertex_array.bounding_box(); - m_volume.finalize_geometry(m_useVBOs); - return true; } std::string _3DScene::get_gl_info(bool format_as_html, bool extensions) { - return s_canvas_mgr.get_gl_info(format_as_html, extensions); + return Slic3r::GUI::GLCanvas3DManager::get_gl_info().to_string(format_as_html, extensions); } bool _3DScene::add_canvas(wxGLCanvas* canvas, GUI::Bed3D& bed, GUI::Camera& camera, GUI::GLToolbar& view_toolbar) @@ -2049,6 +2007,11 @@ bool _3DScene::init(wxGLCanvas* canvas) return s_canvas_mgr.init(canvas); } +void _3DScene::destroy() +{ + s_canvas_mgr.destroy(); +} + GUI::GLCanvas3D* _3DScene::get_canvas(wxGLCanvas* canvas) { return s_canvas_mgr.get_canvas(canvas); diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 2ca11073be..5acc85217f 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -10,6 +10,7 @@ #include "slic3r/GUI/GLCanvas3DManager.hpp" #include +#include #ifndef NDEBUG #define HAS_GLSAFE @@ -55,7 +56,7 @@ public: vertices_and_normals_interleaved_VBO_id(0), triangle_indices_VBO_id(0), quad_indices_VBO_id(0) - { this->setup_sizes(); } + {} GLIndexedVertexArray(const GLIndexedVertexArray &rhs) : vertices_and_normals_interleaved(rhs.vertices_and_normals_interleaved), triangle_indices(rhs.triangle_indices), @@ -63,7 +64,7 @@ public: vertices_and_normals_interleaved_VBO_id(0), triangle_indices_VBO_id(0), quad_indices_VBO_id(0) - { this->setup_sizes(); } + { assert(! rhs.has_VBOs()); } GLIndexedVertexArray(GLIndexedVertexArray &&rhs) : vertices_and_normals_interleaved(std::move(rhs.vertices_and_normals_interleaved)), triangle_indices(std::move(rhs.triangle_indices)), @@ -71,17 +72,25 @@ public: vertices_and_normals_interleaved_VBO_id(0), triangle_indices_VBO_id(0), quad_indices_VBO_id(0) - { this->setup_sizes(); } + { assert(! rhs.has_VBOs()); } + + ~GLIndexedVertexArray() { release_geometry(); } GLIndexedVertexArray& operator=(const GLIndexedVertexArray &rhs) { assert(vertices_and_normals_interleaved_VBO_id == 0); assert(triangle_indices_VBO_id == 0); - assert(triangle_indices_VBO_id == 0); - this->vertices_and_normals_interleaved = rhs.vertices_and_normals_interleaved; - this->triangle_indices = rhs.triangle_indices; - this->quad_indices = rhs.quad_indices; - this->setup_sizes(); + assert(quad_indices_VBO_id == 0); + assert(rhs.vertices_and_normals_interleaved_VBO_id == 0); + assert(rhs.triangle_indices_VBO_id == 0); + assert(rhs.quad_indices_VBO_id == 0); + this->vertices_and_normals_interleaved = rhs.vertices_and_normals_interleaved; + this->triangle_indices = rhs.triangle_indices; + this->quad_indices = rhs.quad_indices; + this->m_bounding_box = rhs.m_bounding_box; + this->vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size; + this->triangle_indices_size = rhs.triangle_indices_size; + this->quad_indices_size = rhs.quad_indices_size; return *this; } @@ -89,11 +98,17 @@ public: { assert(vertices_and_normals_interleaved_VBO_id == 0); assert(triangle_indices_VBO_id == 0); - assert(triangle_indices_VBO_id == 0); - this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved); - this->triangle_indices = std::move(rhs.triangle_indices); - this->quad_indices = std::move(rhs.quad_indices); - this->setup_sizes(); + assert(quad_indices_VBO_id == 0); + assert(rhs.vertices_and_normals_interleaved_VBO_id == 0); + assert(rhs.triangle_indices_VBO_id == 0); + assert(rhs.quad_indices_VBO_id == 0); + this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved); + this->triangle_indices = std::move(rhs.triangle_indices); + this->quad_indices = std::move(rhs.quad_indices); + this->m_bounding_box = std::move(rhs.m_bounding_box); + this->vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size; + this->triangle_indices_size = rhs.triangle_indices_size; + this->quad_indices_size = rhs.quad_indices_size; return *this; } @@ -104,19 +119,18 @@ public: // When the geometry data is loaded into the graphics card as Vertex Buffer Objects, // the above mentioned std::vectors are cleared and the following variables keep their original length. - size_t vertices_and_normals_interleaved_size; - size_t triangle_indices_size; - size_t quad_indices_size; + size_t vertices_and_normals_interleaved_size{ 0 }; + size_t triangle_indices_size{ 0 }; + size_t quad_indices_size{ 0 }; // IDs of the Vertex Array Objects, into which the geometry has been loaded. - // Zero if the VBOs are not used. - unsigned int vertices_and_normals_interleaved_VBO_id; - unsigned int triangle_indices_VBO_id; - unsigned int quad_indices_VBO_id; + // Zero if the VBOs are not sent to GPU yet. + unsigned int vertices_and_normals_interleaved_VBO_id{ 0 }; + unsigned int triangle_indices_VBO_id{ 0 }; + unsigned int quad_indices_VBO_id{ 0 }; - void load_mesh_flat_shading(const TriangleMesh &mesh); void load_mesh_full_shading(const TriangleMesh &mesh); - void load_mesh(const TriangleMesh &mesh, bool use_VBOs) { use_VBOs ? this->load_mesh_full_shading(mesh) : this->load_mesh_flat_shading(mesh); } + void load_mesh(const TriangleMesh& mesh) { this->load_mesh_full_shading(mesh); } inline bool has_VBOs() const { return vertices_and_normals_interleaved_VBO_id != 0; } @@ -127,14 +141,21 @@ public: } inline void push_geometry(float x, float y, float z, float nx, float ny, float nz) { + assert(this->vertices_and_normals_interleaved_VBO_id == 0); + if (this->vertices_and_normals_interleaved_VBO_id != 0) + return; + if (this->vertices_and_normals_interleaved.size() + 6 > this->vertices_and_normals_interleaved.capacity()) this->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(this->vertices_and_normals_interleaved.size() + 6)); - this->vertices_and_normals_interleaved.push_back(nx); - this->vertices_and_normals_interleaved.push_back(ny); - this->vertices_and_normals_interleaved.push_back(nz); - this->vertices_and_normals_interleaved.push_back(x); - this->vertices_and_normals_interleaved.push_back(y); - this->vertices_and_normals_interleaved.push_back(z); + this->vertices_and_normals_interleaved.emplace_back(nx); + this->vertices_and_normals_interleaved.emplace_back(ny); + this->vertices_and_normals_interleaved.emplace_back(nz); + this->vertices_and_normals_interleaved.emplace_back(x); + this->vertices_and_normals_interleaved.emplace_back(y); + this->vertices_and_normals_interleaved.emplace_back(z); + + this->vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size(); + m_bounding_box.merge(Vec3f(x, y, z).cast()); }; inline void push_geometry(double x, double y, double z, double nx, double ny, double nz) { @@ -146,80 +167,82 @@ public: } inline void push_triangle(int idx1, int idx2, int idx3) { + assert(this->vertices_and_normals_interleaved_VBO_id == 0); + if (this->vertices_and_normals_interleaved_VBO_id != 0) + return; + if (this->triangle_indices.size() + 3 > this->vertices_and_normals_interleaved.capacity()) this->triangle_indices.reserve(next_highest_power_of_2(this->triangle_indices.size() + 3)); - this->triangle_indices.push_back(idx1); - this->triangle_indices.push_back(idx2); - this->triangle_indices.push_back(idx3); + this->triangle_indices.emplace_back(idx1); + this->triangle_indices.emplace_back(idx2); + this->triangle_indices.emplace_back(idx3); + this->triangle_indices_size = this->triangle_indices.size(); }; inline void push_quad(int idx1, int idx2, int idx3, int idx4) { + assert(this->vertices_and_normals_interleaved_VBO_id == 0); + if (this->vertices_and_normals_interleaved_VBO_id != 0) + return; + if (this->quad_indices.size() + 4 > this->vertices_and_normals_interleaved.capacity()) this->quad_indices.reserve(next_highest_power_of_2(this->quad_indices.size() + 4)); - this->quad_indices.push_back(idx1); - this->quad_indices.push_back(idx2); - this->quad_indices.push_back(idx3); - this->quad_indices.push_back(idx4); + this->quad_indices.emplace_back(idx1); + this->quad_indices.emplace_back(idx2); + this->quad_indices.emplace_back(idx3); + this->quad_indices.emplace_back(idx4); + this->quad_indices_size = this->quad_indices.size(); }; // Finalize the initialization of the geometry & indices, // upload the geometry and indices to OpenGL VBO objects // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs. - void finalize_geometry(bool use_VBOs); + void finalize_geometry(bool opengl_initialized); // Release the geometry data, release OpenGL VBOs. void release_geometry(); - // Render either using an immediate mode, or the VBOs. + void render() const; - void render(const std::pair &tverts_range, const std::pair &qverts_range) const; + void render(const std::pair& tverts_range, const std::pair& qverts_range) const; // Is there any geometry data stored? bool empty() const { return vertices_and_normals_interleaved_size == 0; } - // Is this object indexed, or is it just a set of triangles? - bool indexed() const { return ! this->empty() && this->triangle_indices_size + this->quad_indices_size > 0; } - void clear() { this->vertices_and_normals_interleaved.clear(); this->triangle_indices.clear(); this->quad_indices.clear(); - this->setup_sizes(); + this->m_bounding_box.reset(); + vertices_and_normals_interleaved_size = 0; + triangle_indices_size = 0; + quad_indices_size = 0; } // Shrink the internal storage to tighly fit the data stored. - void shrink_to_fit() { - if (! this->has_VBOs()) - this->setup_sizes(); + void shrink_to_fit() { this->vertices_and_normals_interleaved.shrink_to_fit(); this->triangle_indices.shrink_to_fit(); this->quad_indices.shrink_to_fit(); } - BoundingBoxf3 bounding_box() const { - BoundingBoxf3 bbox; - if (! this->vertices_and_normals_interleaved.empty()) { - bbox.defined = true; - bbox.min(0) = bbox.max(0) = this->vertices_and_normals_interleaved[3]; - bbox.min(1) = bbox.max(1) = this->vertices_and_normals_interleaved[4]; - bbox.min(2) = bbox.max(2) = this->vertices_and_normals_interleaved[5]; - for (size_t i = 9; i < this->vertices_and_normals_interleaved.size(); i += 6) { - const float *verts = this->vertices_and_normals_interleaved.data() + i; - bbox.min(0) = std::min(bbox.min(0), verts[0]); - bbox.min(1) = std::min(bbox.min(1), verts[1]); - bbox.min(2) = std::min(bbox.min(2), verts[2]); - bbox.max(0) = std::max(bbox.max(0), verts[0]); - bbox.max(1) = std::max(bbox.max(1), verts[1]); - bbox.max(2) = std::max(bbox.max(2), verts[2]); - } - } - return bbox; + const BoundingBoxf3& bounding_box() const { return m_bounding_box; } + + // Return an estimate of the memory consumed by this class. + size_t cpu_memory_used() const { return sizeof(*this) + vertices_and_normals_interleaved.capacity() * sizeof(float) + triangle_indices.capacity() * sizeof(int) + quad_indices.capacity() * sizeof(int); } + // Return an estimate of the memory held by GPU vertex buffers. + size_t gpu_memory_used() const + { + size_t memsize = 0; + if (this->vertices_and_normals_interleaved_VBO_id != 0) + memsize += this->vertices_and_normals_interleaved_size * 4; + if (this->triangle_indices_VBO_id != 0) + memsize += this->triangle_indices_size * 4; + if (this->quad_indices_VBO_id != 0) + memsize += this->quad_indices_size * 4; + return memsize; } + size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); } private: - inline void setup_sizes() { - vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size(); - triangle_indices_size = this->triangle_indices.size(); - quad_indices_size = this->quad_indices.size(); - } + BoundingBoxf3 m_bounding_box; }; class GLVolume { @@ -243,30 +266,25 @@ public: GLVolume(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f); GLVolume(const float *rgba) : GLVolume(rgba[0], rgba[1], rgba[2], rgba[3]) {} - ~GLVolume(); private: Geometry::Transformation m_instance_transformation; Geometry::Transformation m_volume_transformation; // Shift in z required by sla supports+pad - double m_sla_shift_z; + double m_sla_shift_z; // Bounding box of this volume, in unscaled coordinates. mutable BoundingBoxf3 m_transformed_bounding_box; // Whether or not is needed to recalculate the transformed bounding box. mutable bool m_transformed_bounding_box_dirty; - // Pointer to convex hull of the original mesh, if any. - // This object may or may not own the convex hull instance based on m_convex_hull_owned - const TriangleMesh* m_convex_hull; - bool m_convex_hull_owned; + // Convex hull of the volume, if any. + std::shared_ptr m_convex_hull; // Bounding box of this volume, in unscaled coordinates. mutable BoundingBoxf3 m_transformed_convex_hull_bounding_box; // Whether or not is needed to recalculate the transformed convex hull bounding box. mutable bool m_transformed_convex_hull_bounding_box_dirty; public: - // Bounding box of this volume, in unscaled coordinates. - BoundingBoxf3 bounding_box; // Color of the triangles / quads held by this volume. float color[4]; // Color used to render this volume. @@ -298,6 +316,8 @@ public: bool selected; // Is this object disabled from selection? bool disabled; + // Is this object printable? + bool printable; // Whether or not this volume is active for rendering bool is_active; // Whether or not to use this volume when applying zoom_to_volumes() @@ -331,6 +351,9 @@ public: // Offset into qverts & tverts, or offsets into indices stored into an OpenGL name_index_buffer. std::vector offsets; + // Bounding box of this volume, in unscaled coordinates. + const BoundingBoxf3& bounding_box() const { return this->indexed_vertex_array.bounding_box(); } + void set_render_color(float r, float g, float b, float a); void set_render_color(const float* rgba, unsigned int size); // Sets render color in dependence of current state @@ -395,7 +418,9 @@ public: double get_sla_shift_z() const { return m_sla_shift_z; } void set_sla_shift_z(double z) { m_sla_shift_z = z; } - void set_convex_hull(const TriangleMesh *convex_hull, bool owned); + void set_convex_hull(std::shared_ptr convex_hull) { m_convex_hull = std::move(convex_hull); } + void set_convex_hull(const TriangleMesh &convex_hull) { m_convex_hull = std::make_shared(convex_hull); } + void set_convex_hull(TriangleMesh &&convex_hull) { m_convex_hull = std::make_shared(std::move(convex_hull)); } int object_idx() const { return this->composite_id.object_id; } int volume_idx() const { return this->composite_id.volume_id; } @@ -409,22 +434,32 @@ public: BoundingBoxf3 transformed_convex_hull_bounding_box(const Transform3d &trafo) const; // caching variant const BoundingBoxf3& transformed_convex_hull_bounding_box() const; + // convex hull + const TriangleMesh* convex_hull() const { return m_convex_hull.get(); } bool empty() const { return this->indexed_vertex_array.empty(); } - bool indexed() const { return this->indexed_vertex_array.indexed(); } - void set_range(coordf_t low, coordf_t high); + void set_range(double low, double high); + void render() const; - void render_VBOs(int color_id, int detection_id, int worldmatrix_id) const; - void render_legacy() const; + void render(int color_id, int detection_id, int worldmatrix_id) const; - void finalize_geometry(bool use_VBOs) { this->indexed_vertex_array.finalize_geometry(use_VBOs); } + void finalize_geometry(bool opengl_initialized) { this->indexed_vertex_array.finalize_geometry(opengl_initialized); } void release_geometry() { this->indexed_vertex_array.release_geometry(); } void set_bounding_boxes_as_dirty() { m_transformed_bounding_box_dirty = true; m_transformed_convex_hull_bounding_box_dirty = true; } bool is_sla_support() const; bool is_sla_pad() const; + + // Return an estimate of the memory consumed by this class. + size_t cpu_memory_used() const { + //FIXME what to do wih m_convex_hull? + return sizeof(*this) - sizeof(this->indexed_vertex_array) + this->indexed_vertex_array.cpu_memory_used() + this->print_zs.capacity() * sizeof(coordf_t) + this->offsets.capacity() * sizeof(size_t); + } + // Return an estimate of the memory held by GPU vertex buffers. + size_t gpu_memory_used() const { return this->indexed_vertex_array.gpu_memory_used(); } + size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); } }; typedef std::vector GLVolumePtrs; @@ -459,42 +494,41 @@ public: ~GLVolumeCollection() { clear(); }; std::vector load_object( - const ModelObject *model_object, + const ModelObject *model_object, int obj_idx, - const std::vector &instance_idxs, - const std::string &color_by, - bool use_VBOs); + const std::vector &instance_idxs, + const std::string &color_by, + bool opengl_initialized); int load_object_volume( - const ModelObject *model_object, - int obj_idx, - int volume_idx, - int instance_idx, - const std::string &color_by, - bool use_VBOs); + const ModelObject *model_object, + int obj_idx, + int volume_idx, + int instance_idx, + const std::string &color_by, + bool opengl_initialized); // Load SLA auxiliary GLVolumes (for support trees or pad). void load_object_auxiliary( const SLAPrintObject *print_object, int obj_idx, // pairs of - const std::vector> &instances, + const std::vector>& instances, SLAPrintObjectStep milestone, // Timestamp of the last change of the milestone size_t timestamp, - bool use_VBOs); + bool opengl_initialized); int load_wipe_tower_preview( - int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width); + int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized); // Render the volumes by OpenGL. - void render_VBOs(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func = std::function()) const; - void render_legacy(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func = std::function()) const; + void render(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func = std::function()) const; // Finalize the initialization of the geometry & indices, // upload the geometry and indices to OpenGL VBO objects // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs. - void finalize_geometry(bool use_VBOs) { for (auto *v : volumes) v->finalize_geometry(use_VBOs); } + void finalize_geometry(bool opengl_initialized) { for (auto* v : volumes) v->finalize_geometry(opengl_initialized); } // Release the geometry data assigned to the volumes. // If OpenGL VBOs were allocated, an OpenGL context has to be active to release them. void release_geometry() { for (auto *v : volumes) v->release_geometry(); } @@ -522,6 +556,18 @@ public: // Returns a vector containing the sorted list of all the print_zs of the volumes contained in this collection std::vector get_current_print_zs(bool active_only) const; + // Return an estimate of the memory consumed by this class. + size_t cpu_memory_used() const; + // Return an estimate of the memory held by GPU vertex buffers. + size_t gpu_memory_used() const; + size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); } + // Return CPU, GPU and total memory log line. + std::string log_memory_info() const; + + bool has_toolpaths_to_export() const; + // Export the geometry of the GLVolumes toolpaths of this collection into the file with the given path, in obj format + void export_toolpaths_to_obj(const char* filename) const; + private: GLVolumeCollection(const GLVolumeCollection &other); GLVolumeCollection& operator=(const GLVolumeCollection &); @@ -533,17 +579,17 @@ class GLModel { protected: GLVolume m_volume; - bool m_useVBOs; std::string m_filename; public: GLModel(); virtual ~GLModel(); - bool init(bool useVBOs) { return on_init(useVBOs); } - bool init_from_file(const std::string& filename, bool useVBOs) { return on_init_from_file(filename, useVBOs); } + // init() / init_from_file() shall be called with the OpenGL context active! + bool init() { return on_init(); } + bool init_from_file(const std::string& filename) { return on_init_from_file(filename); } - void center_around(const Vec3d& center) { m_volume.set_volume_offset(center - m_volume.bounding_box.center()); } + void center_around(const Vec3d& center) { m_volume.set_volume_offset(center - m_volume.bounding_box().center()); } void set_color(const float* color, unsigned int size); const Vec3d& get_offset() const; @@ -554,25 +600,22 @@ public: void set_scale(const Vec3d& scale); const std::string& get_filename() const { return m_filename; } - const BoundingBoxf3& get_bounding_box() const { return m_volume.bounding_box; } + const BoundingBoxf3& get_bounding_box() const { return m_volume.bounding_box(); } + const BoundingBoxf3& get_transformed_bounding_box() const { return m_volume.transformed_bounding_box(); } void reset(); void render() const; protected: - virtual bool on_init(bool useVBOs) { return false; } - virtual bool on_init_from_file(const std::string& filename, bool useVBOs) { return false; } - -private: - void render_VBOs() const; - void render_legacy() const; + virtual bool on_init() { return false; } + virtual bool on_init_from_file(const std::string& filename) { return false; } }; class GLArrow : public GLModel { protected: - virtual bool on_init(bool useVBOs); + bool on_init() override; }; class GLCurvedArrow : public GLModel @@ -583,13 +626,13 @@ public: explicit GLCurvedArrow(unsigned int resolution); protected: - virtual bool on_init(bool useVBOs); + bool on_init() override; }; class GLBed : public GLModel { protected: - virtual bool on_init_from_file(const std::string& filename, bool useVBOs); + bool on_init_from_file(const std::string& filename) override; }; class _3DScene @@ -604,6 +647,7 @@ public: static void remove_all_canvases(); static bool init(wxGLCanvas* canvas); + static void destroy(); static GUI::GLCanvas3D* get_canvas(wxGLCanvas* canvas); diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index ad58d94822..a4453c73ea 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -76,9 +76,9 @@ void CopyrightsDialog::fill_entries() { m_entries = { { "wxWidgets" , "2019 wxWidgets" , "https://www.wxwidgets.org/" }, - { "OpenGL" , "1997-2019 The Khronos™ Group Inc" , "https://www.opengl.org/" }, + { "OpenGL" , "1997-2019 The Khronosâ„¢ Group Inc" , "https://www.opengl.org/" }, { "GNU gettext" , "1998, 2019 Free Software Foundation, Inc." , "https://www.gnu.org/software/gettext/" }, - { "PoEdit" , "2019 Václav Slavík" , "https://poedit.net/" }, + { "PoEdit" , "2019 Václav Slavík" , "https://poedit.net/" }, { "ImGUI" , "2014-2019 Omar Cornut" , "https://github.com/ocornut/imgui" }, { "Eigen" , "" , "http://eigen.tuxfamily.org" }, { "ADMesh" , "1995, 1996 Anthony D. Martin; " @@ -106,8 +106,13 @@ void CopyrightsDialog::fill_entries() "2001-2016 Expat maintainers" , "http://www.libexpat.org/" }, { "AVRDUDE" , "2018 Free Software Foundation, Inc." , "http://savannah.nongnu.org/projects/avrdude" }, { "Shinyprofiler" , "2007-2010 Aidin Abedi" , "http://code.google.com/p/shinyprofiler/" }, + { "Real-Time DXT1/DXT5 C compression library" + , "Based on original by fabian \"ryg\" giesen v1.04. " + "Custom version, modified by Yann Collet" , "https://github.com/Cyan4973/RygsDXTc" }, { "Icons for STL and GCODE files." - , "Akira Yasuda" , "http://3dp0.com/icons-for-stl-and-gcode/" } + , "Akira Yasuda" , "http://3dp0.com/icons-for-stl-and-gcode/" }, + { "AppImage packaging for Linux using AppImageKit" + , "2004-2019 Simon Peter and contributors" , "https://appimage.org/" } }; } diff --git a/src/slic3r/GUI/AppConfig.cpp b/src/slic3r/GUI/AppConfig.cpp index d4970880b5..96213447cf 100644 --- a/src/slic3r/GUI/AppConfig.cpp +++ b/src/slic3r/GUI/AppConfig.cpp @@ -15,9 +15,13 @@ #include #include #include +#include #include #include +#include +#include "I18N.hpp" + namespace Slic3r { static const std::string VENDOR_PREFIX = "vendor:"; @@ -54,12 +58,11 @@ void AppConfig::set_defaults() if (get("preset_update").empty()) set("preset_update", "1"); - // Use OpenGL 1.1 even if OpenGL 2.0 is available. This is mainly to support some buggy Intel HD Graphics drivers. - // github.com/prusa3d/PrusaSlicer/issues/233 - if (get("use_legacy_opengl").empty()) - set("use_legacy_opengl", "0"); + // remove old 'use_legacy_opengl' parameter from this config, if present + if (!get("use_legacy_opengl").empty()) + erase("", "use_legacy_opengl"); -#if __APPLE__ +#ifdef __APPLE__ if (get("use_retina_opengl").empty()) set("use_retina_opengl", "1"); #endif @@ -73,6 +76,9 @@ void AppConfig::set_defaults() if (get("custom_toolbar_size").empty()) set("custom_toolbar_size", "100"); + if (get("use_perspective_camera").empty()) + set("use_perspective_camera", "1"); + // Remove legacy window positions/sizes erase("", "main_frame_maximized"); erase("", "main_frame_pos"); @@ -88,7 +94,15 @@ void AppConfig::load() namespace pt = boost::property_tree; pt::ptree tree; boost::nowide::ifstream ifs(AppConfig::config_path()); - pt::read_ini(ifs, tree); + try { + pt::read_ini(ifs, tree); + } catch (pt::ptree_error& ex) { + // Error while parsing config file. We'll customize the error message and rethrow to be displayed. + throw std::runtime_error( + _utf8(L("Error parsing PrusaSlicer config file, it is probably corrupted. " + "Try to manualy delete the file to recover from the error. Your user profiles will not be affected.")) + + "\n\n" + AppConfig::config_path() + "\n\n" + ex.what()); + } // 2) Parse the property_tree, extract the sections and key / value pairs. for (const auto §ion : tree) { @@ -227,6 +241,33 @@ std::string AppConfig::get_last_dir() const return std::string(); } +std::vector AppConfig::get_recent_projects() const +{ + std::vector ret; + const auto it = m_storage.find("recent_projects"); + if (it != m_storage.end()) + { + for (const std::map::value_type& item : it->second) + { + ret.push_back(item.second); + } + } + return ret; +} + +void AppConfig::set_recent_projects(const std::vector& recent_projects) +{ + auto it = m_storage.find("recent_projects"); + if (it == m_storage.end()) + it = m_storage.insert(std::map>::value_type("recent_projects", std::map())).first; + + it->second.clear(); + for (unsigned int i = 0; i < (unsigned int)recent_projects.size(); ++i) + { + it->second[std::to_string(i + 1)] = recent_projects[i]; + } +} + void AppConfig::update_config_dir(const std::string &dir) { this->set("recent", "config_directory", dir); diff --git a/src/slic3r/GUI/AppConfig.hpp b/src/slic3r/GUI/AppConfig.hpp index 5af635a12c..8ad17b9db8 100644 --- a/src/slic3r/GUI/AppConfig.hpp +++ b/src/slic3r/GUI/AppConfig.hpp @@ -6,7 +6,7 @@ #include #include "libslic3r/Config.hpp" -#include "slic3r/Utils/Semver.hpp" +#include "libslic3r/Semver.hpp" namespace Slic3r { @@ -122,6 +122,9 @@ public: // Does the config file exist? static bool exists(); + std::vector get_recent_projects() const; + void set_recent_projects(const std::vector& recent_projects); + private: // Map of section, name -> value std::map> m_storage; diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 94fb6481b7..94ddb1e5ef 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -89,7 +89,7 @@ void BackgroundSlicingProcess::process_fff() // Perform the final post-processing of the export path by applying the print statistics over the file name. std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path); if (copy_file(m_temp_output_path, export_path) != 0) - throw std::runtime_error(_utf8(L("Copying of the temporary G-code to the output G-code failed"))); + throw std::runtime_error(_utf8(L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?"))); m_print->set_status(95, _utf8(L("Running post-processing scripts"))); run_post_process_scripts(export_path, m_fff_print->config()); m_print->set_status(100, (boost::format(_utf8(L("G-code file exported to %1%"))) % export_path).str()); @@ -151,7 +151,12 @@ void BackgroundSlicingProcess::thread_proc() } catch (CanceledException & /* ex */) { // Canceled, this is all right. assert(m_print->canceled()); - } catch (std::exception &ex) { + } catch (const std::bad_alloc& ex) { + wxString errmsg = wxString::Format(_(L("%s has encountered an error. It was likely caused by running out of memory. " + "If you are sure you have enough RAM on your system, this may also be a bug and we would " + "be glad if you reported it.")), SLIC3R_APP_NAME); + error = errmsg.ToStdString() + "\n\n" + std::string(ex.what()); + } catch (std::exception &ex) { error = ex.what(); } catch (...) { error = "Unknown C++ exception."; diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index d471a46c90..a9be260bd5 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -4,23 +4,25 @@ #include #include #include +#include #include "libslic3r/BoundingBox.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/Polygon.hpp" #include "boost/nowide/iostream.hpp" +#include #include namespace Slic3r { namespace GUI { -void BedShapeDialog::build_dialog(ConfigOptionPoints* default_pt) +void BedShapeDialog::build_dialog(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model) { SetFont(wxGetApp().normal_font()); m_panel = new BedShapePanel(this); - m_panel->build_panel(default_pt); + m_panel->build_panel(default_pt, custom_texture, custom_model); auto main_sizer = new wxBoxSizer(wxVERTICAL); main_sizer->Add(m_panel, 1, wxEXPAND); @@ -30,11 +32,9 @@ void BedShapeDialog::build_dialog(ConfigOptionPoints* default_pt) SetMinSize(GetSize()); main_sizer->SetSizeHints(this); - // needed to actually free memory - this->Bind(wxEVT_CLOSE_WINDOW, ([this](wxCloseEvent e) { - EndModal(wxID_OK); - Destroy(); - })); + this->Bind(wxEVT_CLOSE_WINDOW, ([this](wxCloseEvent& evt) { + EndModal(wxID_CANCEL); + })); } void BedShapeDialog::on_dpi_changed(const wxRect &suggested_rect) @@ -53,75 +53,88 @@ void BedShapeDialog::on_dpi_changed(const wxRect &suggested_rect) Refresh(); } -void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) -{ -// on_change(nullptr); +const std::string BedShapePanel::NONE = "None"; +const std::string BedShapePanel::EMPTY_STRING = ""; - auto box = new wxStaticBox(this, wxID_ANY, _(L("Shape"))); - auto sbsizer = new wxStaticBoxSizer(box, wxVERTICAL); +void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model) +{ + m_shape = default_pt.values; + m_custom_texture = custom_texture.value.empty() ? NONE : custom_texture.value; + m_custom_model = custom_model.value.empty() ? NONE : custom_model.value; + + auto sbsizer = new wxStaticBoxSizer(wxVERTICAL, this, _(L("Shape"))); + sbsizer->GetStaticBox()->SetFont(wxGetApp().bold_font()); // shape options - m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, - wxSize(25*wxGetApp().em_unit(), -1), wxCHB_TOP); - sbsizer->Add(m_shape_options_book); + m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, wxSize(25*wxGetApp().em_unit(), -1), wxCHB_TOP); + sbsizer->Add(m_shape_options_book); auto optgroup = init_shape_options_page(_(L("Rectangular"))); - ConfigOptionDef def; - def.type = coPoints; - def.set_default_value(new ConfigOptionPoints{ Vec2d(200, 200) }); - def.label = L("Size"); - def.tooltip = L("Size in X and Y of the rectangular plate."); - Option option(def, "rect_size"); - optgroup->append_single_option_line(option); + ConfigOptionDef def; + def.type = coPoints; + def.set_default_value(new ConfigOptionPoints{ Vec2d(200, 200) }); + def.label = L("Size"); + def.tooltip = L("Size in X and Y of the rectangular plate."); + Option option(def, "rect_size"); + optgroup->append_single_option_line(option); - def.type = coPoints; - def.set_default_value(new ConfigOptionPoints{ Vec2d(0, 0) }); - def.label = L("Origin"); - def.tooltip = L("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle."); - option = Option(def, "rect_origin"); - optgroup->append_single_option_line(option); + def.type = coPoints; + def.set_default_value(new ConfigOptionPoints{ Vec2d(0, 0) }); + def.label = L("Origin"); + def.tooltip = L("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle."); + option = Option(def, "rect_origin"); + optgroup->append_single_option_line(option); - optgroup = init_shape_options_page(_(L("Circular"))); - def.type = coFloat; - def.set_default_value(new ConfigOptionFloat(200)); - def.sidetext = L("mm"); - def.label = L("Diameter"); - def.tooltip = L("Diameter of the print bed. It is assumed that origin (0,0) is located in the center."); - option = Option(def, "diameter"); - optgroup->append_single_option_line(option); + optgroup = init_shape_options_page(_(L("Circular"))); + def.type = coFloat; + def.set_default_value(new ConfigOptionFloat(200)); + def.sidetext = L("mm"); + def.label = L("Diameter"); + def.tooltip = L("Diameter of the print bed. It is assumed that origin (0,0) is located in the center."); + option = Option(def, "diameter"); + optgroup->append_single_option_line(option); - optgroup = init_shape_options_page(_(L("Custom"))); - Line line{ "", "" }; - line.full_width = 1; - line.widget = [this](wxWindow* parent) { - auto btn = new wxButton(parent, wxID_ANY, _(L("Load shape from STL...")), wxDefaultPosition, wxDefaultSize); - - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(btn); + optgroup = init_shape_options_page(_(L("Custom"))); + Line line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + wxButton* shape_btn = new wxButton(parent, wxID_ANY, _(L("Load shape from STL..."))); + wxSizer* shape_sizer = new wxBoxSizer(wxHORIZONTAL); + shape_sizer->Add(shape_btn, 1, wxEXPAND); - btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) - { - load_stl(); - })); + wxSizer* sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(shape_sizer, 1, wxEXPAND); - return sizer; - }; - optgroup->append_line(line); + shape_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) + { + load_stl(); + })); - Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent e) - { + return sizer; + }; + optgroup->append_line(line); + + wxPanel* texture_panel = init_texture_panel(); + wxPanel* model_panel = init_model_panel(); + + Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent& e) + { update_shape(); })); // right pane with preview canvas m_canvas = new Bed_2D(this); - m_canvas->m_bed_shape = default_pt->values; + m_canvas->Bind(wxEVT_PAINT, [this](wxPaintEvent& e) { m_canvas->repaint(m_shape); }); + m_canvas->Bind(wxEVT_SIZE, [this](wxSizeEvent& e) { m_canvas->Refresh(); }); - // main sizer - auto top_sizer = new wxBoxSizer(wxHORIZONTAL); - top_sizer->Add(sbsizer, 0, wxEXPAND | wxLeft | wxTOP | wxBOTTOM, 10); - if (m_canvas) - top_sizer->Add(m_canvas, 1, wxEXPAND | wxALL, 10) ; + wxSizer* left_sizer = new wxBoxSizer(wxVERTICAL); + left_sizer->Add(sbsizer, 0, wxEXPAND); + left_sizer->Add(texture_panel, 1, wxEXPAND); + left_sizer->Add(model_panel, 1, wxEXPAND); + + wxSizer* top_sizer = new wxBoxSizer(wxHORIZONTAL); + top_sizer->Add(left_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 10); + top_sizer->Add(m_canvas, 1, wxEXPAND | wxALL, 10); SetSizerAndFit(top_sizer); @@ -135,23 +148,157 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) // Called from the constructor. // Create a panel for a rectangular / circular / custom bed shape. -ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(wxString title) +ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& title) { - - auto panel = new wxPanel(m_shape_options_book); - ConfigOptionsGroupShp optgroup; - optgroup = std::make_shared(panel, _(L("Settings"))); + wxPanel* panel = new wxPanel(m_shape_options_book); + ConfigOptionsGroupShp optgroup = std::make_shared(panel, _(L("Settings"))); optgroup->label_width = 10; - optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { - update_shape(); - }; - - m_optgroups.push_back(optgroup); - panel->SetSizerAndFit(optgroup->sizer); - m_shape_options_book->AddPage(panel, title); + optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + update_shape(); + }; + + m_optgroups.push_back(optgroup); + panel->SetSizerAndFit(optgroup->sizer); + m_shape_options_book->AddPage(panel, title); - return optgroup; + return optgroup; +} + +wxPanel* BedShapePanel::init_texture_panel() +{ + wxPanel* panel = new wxPanel(this); + ConfigOptionsGroupShp optgroup = std::make_shared(panel, _(L("Texture"))); + + optgroup->label_width = 10; + optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + update_shape(); + }; + + Line line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + wxButton* load_btn = new wxButton(parent, wxID_ANY, _(L("Load..."))); + wxSizer* load_sizer = new wxBoxSizer(wxHORIZONTAL); + load_sizer->Add(load_btn, 1, wxEXPAND); + + wxStaticText* filename_lbl = new wxStaticText(parent, wxID_ANY, _(NONE)); + wxSizer* filename_sizer = new wxBoxSizer(wxHORIZONTAL); + filename_sizer->Add(filename_lbl, 1, wxEXPAND); + + wxButton* remove_btn = new wxButton(parent, wxID_ANY, _(L("Remove"))); + wxSizer* remove_sizer = new wxBoxSizer(wxHORIZONTAL); + remove_sizer->Add(remove_btn, 1, wxEXPAND); + + wxSizer* sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(filename_sizer, 1, wxEXPAND); + sizer->Add(load_sizer, 1, wxEXPAND); + sizer->Add(remove_sizer, 1, wxEXPAND); + + load_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) + { + load_texture(); + })); + + remove_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) + { + m_custom_texture = NONE; + update_shape(); + })); + + filename_lbl->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) + { + e.SetText(_(boost::filesystem::path(m_custom_texture).filename().string())); + wxStaticText* lbl = dynamic_cast(e.GetEventObject()); + if (lbl != nullptr) + { + wxString tooltip_text = (m_custom_texture == NONE) ? "" : _(m_custom_texture); + wxToolTip* tooltip = lbl->GetToolTip(); + if ((tooltip == nullptr) || (tooltip->GetTip() != tooltip_text)) + lbl->SetToolTip(tooltip_text); + } + })); + + remove_btn->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) + { + e.Enable(m_custom_texture != NONE); + })); + + return sizer; + }; + optgroup->append_line(line); + + panel->SetSizerAndFit(optgroup->sizer); + + return panel; +} + +wxPanel* BedShapePanel::init_model_panel() +{ + wxPanel* panel = new wxPanel(this); + ConfigOptionsGroupShp optgroup = std::make_shared(panel, _(L("Model"))); + + optgroup->label_width = 10; + optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + update_shape(); + }; + + Line line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + wxButton* load_btn = new wxButton(parent, wxID_ANY, _(L("Load..."))); + wxSizer* load_sizer = new wxBoxSizer(wxHORIZONTAL); + load_sizer->Add(load_btn, 1, wxEXPAND); + + wxStaticText* filename_lbl = new wxStaticText(parent, wxID_ANY, _(NONE)); + wxSizer* filename_sizer = new wxBoxSizer(wxHORIZONTAL); + filename_sizer->Add(filename_lbl, 1, wxEXPAND); + + wxButton* remove_btn = new wxButton(parent, wxID_ANY, _(L("Remove"))); + wxSizer* remove_sizer = new wxBoxSizer(wxHORIZONTAL); + remove_sizer->Add(remove_btn, 1, wxEXPAND); + + wxSizer* sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(filename_sizer, 1, wxEXPAND); + sizer->Add(load_sizer, 1, wxEXPAND); + sizer->Add(remove_sizer, 1, wxEXPAND); + + load_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) + { + load_model(); + })); + + remove_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) + { + m_custom_model = NONE; + update_shape(); + })); + + filename_lbl->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) + { + e.SetText(_(boost::filesystem::path(m_custom_model).filename().string())); + wxStaticText* lbl = dynamic_cast(e.GetEventObject()); + if (lbl != nullptr) + { + wxString tooltip_text = (m_custom_model == NONE) ? "" : _(m_custom_model); + wxToolTip* tooltip = lbl->GetToolTip(); + if ((tooltip == nullptr) || (tooltip->GetTip() != tooltip_text)) + lbl->SetToolTip(tooltip_text); + } + })); + + remove_btn->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) + { + e.Enable(m_custom_model != NONE); + })); + + return sizer; + }; + optgroup->append_line(line); + + panel->SetSizerAndFit(optgroup->sizer); + + return panel; } // Called from the constructor. @@ -159,20 +306,20 @@ ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(wxString title) // Deduce the bed shape type(rect, circle, custom) // This routine shall be smart enough if the user messes up // with the list of points in the ini file directly. -void BedShapePanel::set_shape(ConfigOptionPoints* points) +void BedShapePanel::set_shape(const ConfigOptionPoints& points) { - auto polygon = Polygon::new_scale(points->values); + auto polygon = Polygon::new_scale(points.values); // is this a rectangle ? - if (points->size() == 4) { - auto lines = polygon.lines(); + if (points.size() == 4) { + auto lines = polygon.lines(); if (lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3])) { // okay, it's a rectangle // find origin coordf_t x_min, x_max, y_min, y_max; - x_max = x_min = points->values[0](0); - y_max = y_min = points->values[0](1); - for (auto pt : points->values) + x_max = x_min = points.values[0](0); + y_max = y_min = points.values[0](1); + for (auto pt : points.values) { x_min = std::min(x_min, pt(0)); x_max = std::max(x_max, pt(0)); @@ -223,8 +370,8 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points) } } - if (points->size() < 3) { - // Invalid polygon.Revert to default bed dimensions. + if (points.size() < 3) { + // Invalid polygon.Revert to default bed dimensions. m_shape_options_book->SetSelection(SHAPE_RECTANGULAR); auto optgroup = m_optgroups[SHAPE_RECTANGULAR]; optgroup->set_value("rect_size", new ConfigOptionPoints{ Vec2d(200, 200) }); @@ -236,8 +383,8 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points) // This is a custom bed shape, use the polygon provided. m_shape_options_book->SetSelection(SHAPE_CUSTOM); // Copy the polygon to the canvas, make a copy of the array. - m_canvas->m_bed_shape = points->values; - update_shape(); + m_loaded_shape = points.values; + update_shape(); } void BedShapePanel::update_preview() @@ -281,11 +428,11 @@ void BedShapePanel::update_shape() x1 -= dx; y0 -= dy; y1 -= dy; - m_canvas->m_bed_shape = { Vec2d(x0, y0), - Vec2d(x1, y0), - Vec2d(x1, y1), - Vec2d(x0, y1)}; - } + m_shape = { Vec2d(x0, y0), + Vec2d(x1, y0), + Vec2d(x1, y1), + Vec2d(x0, y1) }; + } else if(page_idx == SHAPE_CIRCULAR) { double diameter; try{ @@ -297,43 +444,44 @@ void BedShapePanel::update_shape() if (diameter == 0.0) return ; auto r = diameter / 2; auto twopi = 2 * PI; - auto edges = 60; - std::vector points; - for (size_t i = 1; i <= 60; ++i) { - auto angle = i * twopi / edges; + auto edges = 72; + std::vector points; + for (size_t i = 1; i <= edges; ++i) { + auto angle = i * twopi / edges; points.push_back(Vec2d(r*cos(angle), r*sin(angle))); } - m_canvas->m_bed_shape = points; - } + m_shape = points; + } + else if (page_idx == SHAPE_CUSTOM) + m_shape = m_loaded_shape; -// $self->{on_change}->(); update_preview(); } // Loads an stl file, projects it to the XY plane and calculates a polygon. void BedShapePanel::load_stl() { - auto dialog = new wxFileDialog(this, _(L("Choose a file to import bed shape from (STL/OBJ/AMF/3MF/PRUSA):")), "", "", - file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); - if (dialog->ShowModal() != wxID_OK) { - dialog->Destroy(); - return; - } - wxArrayString input_file; - dialog->GetPaths(input_file); - dialog->Destroy(); + wxFileDialog dialog(this, _(L("Choose an STL file to import bed shape from:")), "", "", file_wildcards(FT_STL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (dialog.ShowModal() != wxID_OK) + return; - std::string file_name = input_file[0].ToUTF8().data(); + std::string file_name = dialog.GetPath().ToUTF8().data(); + if (!boost::algorithm::iends_with(file_name, ".stl")) + { + show_error(this, _(L("Invalid file format."))); + return; + } + + wxBusyCursor wait; Model model; try { - model = Model::read_from_file(file_name); - } - catch (std::exception &e) { - auto msg = _(L("Error!")) + " " + file_name + " : " + e.what() + "."; - show_error(this, msg); - exit(1); + model = Model::read_from_file(file_name); } + catch (std::exception &) { + show_error(this, _(L("Error! Invalid model"))); + return; + } auto mesh = model.mesh(); auto expolygons = mesh.horizontal_projection(); @@ -351,8 +499,55 @@ void BedShapePanel::load_stl() std::vector points; for (auto pt : polygon.points) points.push_back(unscale(pt)); - m_canvas->m_bed_shape = points; - update_preview(); + + m_loaded_shape = points; + update_shape(); +} + +void BedShapePanel::load_texture() +{ + wxFileDialog dialog(this, _(L("Choose a file to import bed texture from (PNG/SVG):")), "", "", + file_wildcards(FT_TEX), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + + if (dialog.ShowModal() != wxID_OK) + return; + + m_custom_texture = NONE; + + std::string file_name = dialog.GetPath().ToUTF8().data(); + if (!boost::algorithm::iends_with(file_name, ".png") && !boost::algorithm::iends_with(file_name, ".svg")) + { + show_error(this, _(L("Invalid file format."))); + return; + } + + wxBusyCursor wait; + + m_custom_texture = file_name; + update_shape(); +} + +void BedShapePanel::load_model() +{ + wxFileDialog dialog(this, _(L("Choose an STL file to import bed model from:")), "", "", + file_wildcards(FT_STL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + + if (dialog.ShowModal() != wxID_OK) + return; + + m_custom_model = NONE; + + std::string file_name = dialog.GetPath().ToUTF8().data(); + if (!boost::algorithm::iends_with(file_name, ".stl")) + { + show_error(this, _(L("Invalid file format."))); + return; + } + + wxBusyCursor wait; + + m_custom_model = file_name; + update_shape(); } } // GUI diff --git a/src/slic3r/GUI/BedShapeDialog.hpp b/src/slic3r/GUI/BedShapeDialog.hpp index 72e50a05d5..bf12cc8934 100644 --- a/src/slic3r/GUI/BedShapeDialog.hpp +++ b/src/slic3r/GUI/BedShapeDialog.hpp @@ -16,26 +16,40 @@ namespace GUI { using ConfigOptionsGroupShp = std::shared_ptr; class BedShapePanel : public wxPanel { - Bed_2D* m_canvas; + static const std::string NONE; + static const std::string EMPTY_STRING; + + Bed_2D* m_canvas; + std::vector m_shape; + std::vector m_loaded_shape; + std::string m_custom_texture; + std::string m_custom_model; public: - BedShapePanel(wxWindow* parent) : wxPanel(parent, wxID_ANY) {} - ~BedShapePanel() {} + BedShapePanel(wxWindow* parent) : wxPanel(parent, wxID_ANY), m_custom_texture(NONE), m_custom_model(NONE) {} - void build_panel(ConfigOptionPoints* default_pt); - - ConfigOptionsGroupShp init_shape_options_page(wxString title); - void set_shape(ConfigOptionPoints* points); - void update_preview(); + void build_panel(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model); + + // Returns the resulting bed shape polygon. This value will be stored to the ini file. + const std::vector& get_shape() const { return m_shape; } + const std::string& get_custom_texture() const { return (m_custom_texture != NONE) ? m_custom_texture : EMPTY_STRING; } + const std::string& get_custom_model() const { return (m_custom_model != NONE) ? m_custom_model : EMPTY_STRING; } + +private: + ConfigOptionsGroupShp init_shape_options_page(const wxString& title); + wxPanel* init_texture_panel(); + wxPanel* init_model_panel(); + void set_shape(const ConfigOptionPoints& points); + void update_preview(); void update_shape(); void load_stl(); - - // Returns the resulting bed shape polygon. This value will be stored to the ini file. - std::vector GetValue() { return m_canvas->m_bed_shape; } + void load_texture(); + void load_model(); wxChoicebook* m_shape_options_book; std::vector m_optgroups; + friend class BedShapeDialog; }; class BedShapeDialog : public DPIDialog @@ -44,10 +58,12 @@ class BedShapeDialog : public DPIDialog public: BedShapeDialog(wxWindow* parent) : DPIDialog(parent, wxID_ANY, _(L("Bed Shape")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {} - ~BedShapeDialog() {} - void build_dialog(ConfigOptionPoints* default_pt); - std::vector GetValue() { return m_panel->GetValue(); } + void build_dialog(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model); + + const std::vector& get_shape() const { return m_panel->get_shape(); } + const std::string& get_custom_texture() const { return m_panel->get_custom_texture(); } + const std::string& get_custom_model() const { return m_panel->get_custom_model(); } protected: void on_dpi_changed(const wxRect &suggested_rect) override; diff --git a/src/slic3r/GUI/BonjourDialog.cpp b/src/slic3r/GUI/BonjourDialog.cpp index ec6b2f0c95..0e05a517c6 100644 --- a/src/slic3r/GUI/BonjourDialog.cpp +++ b/src/slic3r/GUI/BonjourDialog.cpp @@ -52,7 +52,7 @@ struct LifetimeGuard }; BonjourDialog::BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology tech) - : wxDialog(parent, wxID_ANY, _(L("Network lookup")), wxDefaultPosition, wxDefaultSize, wxRESIZE_BORDER) + : wxDialog(parent, wxID_ANY, _(L("Network lookup")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) , list(new wxListView(this, wxID_ANY)) , replies(new ReplySet) , label(new wxStaticText(this, wxID_ANY, "")) @@ -171,7 +171,7 @@ void BonjourDialog::on_reply(BonjourReplyEvent &e) // Filter replies based on selected technology const auto model = e.reply.txt_data.find("model"); const bool sl1 = model != e.reply.txt_data.end() && model->second == "SL1"; - if (tech == ptFFF && sl1 || tech == ptSLA && !sl1) { + if ((tech == ptFFF && sl1) || (tech == ptSLA && !sl1)) { return; } diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index dd6cbefe19..8e3a6d1f12 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -2,6 +2,8 @@ #include "Camera.hpp" #include "3DScene.hpp" +#include "GUI_App.hpp" +#include "AppConfig.hpp" #include @@ -19,32 +21,68 @@ static const float VIEW_REAR[2] = { 180.0f, 90.0f }; namespace Slic3r { namespace GUI { +const double Camera::DefaultDistance = 1000.0; +double Camera::FrustrumMinZRange = 50.0; +double Camera::FrustrumMinNearZ = 100.0; +double Camera::FrustrumZMargin = 10.0; +double Camera::MaxFovDeg = 60.0; + Camera::Camera() - : type(Ortho) - , zoom(1.0f) - , phi(45.0f) -// , distance(0.0f) + : phi(45.0f) , requires_zoom_to_bed(false) , inverted_phi(false) - , m_theta(45.0f) + , m_type(Perspective) , m_target(Vec3d::Zero()) + , m_theta(45.0f) + , m_zoom(1.0) + , m_distance(DefaultDistance) + , m_gui_scale(1.0) + , m_view_matrix(Transform3d::Identity()) + , m_projection_matrix(Transform3d::Identity()) { } std::string Camera::get_type_as_string() const { - switch (type) + switch (m_type) { - default: case Unknown: return "unknown"; -// case Perspective: -// return "perspective"; + case Perspective: + return "perspective"; + default: case Ortho: - return "ortho"; + return "orthographic"; }; } +void Camera::set_type(EType type) +{ + if (m_type != type) + { + m_type = type; + wxGetApp().app_config->set("use_perspective_camera", (m_type == Perspective) ? "1" : "0"); + wxGetApp().app_config->save(); + } +} + +void Camera::set_type(const std::string& type) +{ + if (type == "1") + set_type(Perspective); + else + set_type(Ortho); +} + +void Camera::select_next_type() +{ + unsigned char next = (unsigned char)m_type + 1; + if (next == (unsigned char)Num_types) + next = 1; + + set_type((EType)next); +} + void Camera::set_target(const Vec3d& target) { m_target = target; @@ -65,9 +103,20 @@ void Camera::set_theta(float theta, bool apply_limit) } } -void Camera::set_scene_box(const BoundingBoxf3& box) +void Camera::set_zoom(double zoom, const BoundingBoxf3& max_box, int canvas_w, int canvas_h) { - m_scene_box = box; + zoom = std::max(std::min(zoom, 4.0), -4.0) / 10.0; + zoom = m_zoom / (1.0 - zoom); + + // Don't allow to zoom too far outside the scene. + double zoom_min = calc_zoom_to_bounding_box_factor(max_box, canvas_w, canvas_h); + if (zoom_min > 0.0) + zoom = std::max(zoom, zoom_min * 0.7); + + // Don't allow to zoom too close to the scene. + zoom = std::min(zoom, 100.0); + + m_zoom = zoom; } bool Camera::select_view(const std::string& direction) @@ -99,6 +148,18 @@ bool Camera::select_view(const std::string& direction) return false; } +double Camera::get_fov() const +{ + switch (m_type) + { + case Perspective: + return 2.0 * Geometry::rad2deg(std::atan(1.0 / m_projection_matrix.matrix()(1, 1))); + default: + case Ortho: + return 0.0; + }; +} + void Camera::apply_viewport(int x, int y, unsigned int w, unsigned int h) const { glsafe(::glViewport(0, 0, w, h)); @@ -107,27 +168,275 @@ void Camera::apply_viewport(int x, int y, unsigned int w, unsigned int h) const void Camera::apply_view_matrix() const { + double theta_rad = Geometry::deg2rad(-(double)m_theta); + double phi_rad = Geometry::deg2rad((double)phi); + double sin_theta = ::sin(theta_rad); + Vec3d camera_pos = m_target + m_distance * Vec3d(sin_theta * ::sin(phi_rad), sin_theta * ::cos(phi_rad), ::cos(theta_rad)); + glsafe(::glMatrixMode(GL_MODELVIEW)); glsafe(::glLoadIdentity()); glsafe(::glRotatef(-m_theta, 1.0f, 0.0f, 0.0f)); // pitch - glsafe(::glRotatef(phi, 0.0f, 0.0f, 1.0f)); // yaw - glsafe(::glTranslated(-m_target(0), -m_target(1), -m_target(2))); + glsafe(::glRotatef(phi, 0.0f, 0.0f, 1.0f)); // yaw + + glsafe(::glTranslated(-camera_pos(0), -camera_pos(1), -camera_pos(2))); glsafe(::glGetDoublev(GL_MODELVIEW_MATRIX, m_view_matrix.data())); } -void Camera::apply_ortho_projection(float x_min, float x_max, float y_min, float y_max, float z_min, float z_max) const +void Camera::apply_projection(const BoundingBoxf3& box) const { + set_distance(DefaultDistance); + + double w = 0.0; + double h = 0.0; + + while (true) + { + m_frustrum_zs = calc_tight_frustrum_zs_around(box); + + w = 0.5 * (double)m_viewport[2]; + h = 0.5 * (double)m_viewport[3]; + + if (m_zoom != 0.0) + { + double inv_zoom = 1.0 / m_zoom; + w *= inv_zoom; + h *= inv_zoom; + } + + switch (m_type) + { + default: + case Ortho: + { + m_gui_scale = 1.0; + break; + } + case Perspective: + { + // scale near plane to keep w and h constant on the plane at z = m_distance + double scale = m_frustrum_zs.first / m_distance; + w *= scale; + h *= scale; + m_gui_scale = scale; + break; + } + } + + if (m_type == Perspective) + { + double fov_deg = Geometry::rad2deg(2.0 * std::atan(h / m_frustrum_zs.first)); + + // adjust camera distance to keep fov in a limited range + if (fov_deg > MaxFovDeg) + { + double delta_z = h / ::tan(0.5 * Geometry::deg2rad(MaxFovDeg)) - m_frustrum_zs.first; + if (delta_z > 0.001) + set_distance(m_distance + delta_z); + else + break; + } + else + break; + } + else + break; + } + glsafe(::glMatrixMode(GL_PROJECTION)); glsafe(::glLoadIdentity()); - glsafe(::glOrtho(x_min, x_max, y_min, y_max, z_min, z_max)); - glsafe(::glGetDoublev(GL_PROJECTION_MATRIX, m_projection_matrix.data())); + switch (m_type) + { + default: + case Ortho: + { + glsafe(::glOrtho(-w, w, -h, h, m_frustrum_zs.first, m_frustrum_zs.second)); + break; + } + case Perspective: + { + glsafe(::glFrustum(-w, w, -h, h, m_frustrum_zs.first, m_frustrum_zs.second)); + break; + } + } + glsafe(::glGetDoublev(GL_PROJECTION_MATRIX, m_projection_matrix.data())); glsafe(::glMatrixMode(GL_MODELVIEW)); } +void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h) +{ + // Calculate the zoom factor needed to adjust the view around the given box. + double zoom = calc_zoom_to_bounding_box_factor(box, canvas_w, canvas_h); + if (zoom > 0.0) + { + m_zoom = zoom; + // center view around box center + m_target = box.center(); + } +} + +#if ENABLE_CAMERA_STATISTICS +void Camera::debug_render() const +{ + ImGuiWrapper& imgui = *wxGetApp().imgui(); + imgui.set_next_window_bg_alpha(0.5f); + imgui.begin(std::string("Camera statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + std::string type = get_type_as_string(); + Vec3f position = get_position().cast(); + Vec3f target = m_target.cast(); + float distance = (float)get_distance(); + Vec3f forward = get_dir_forward().cast(); + Vec3f right = get_dir_right().cast(); + Vec3f up = get_dir_up().cast(); + float nearZ = (float)m_frustrum_zs.first; + float farZ = (float)m_frustrum_zs.second; + float deltaZ = farZ - nearZ; + float zoom = (float)m_zoom; + float fov = (float)get_fov(); + float gui_scale = (float)get_gui_scale(); + + ImGui::InputText("Type", const_cast(type.data()), type.length(), ImGuiInputTextFlags_ReadOnly); + ImGui::Separator(); + ImGui::InputFloat3("Position", position.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::InputFloat3("Target", target.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::InputFloat("Distance", &distance, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::Separator(); + ImGui::InputFloat3("Forward", forward.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::InputFloat3("Right", right.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::InputFloat3("Up", up.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::Separator(); + ImGui::InputFloat("Near Z", &nearZ, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::InputFloat("Far Z", &farZ, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::InputFloat("Delta Z", &deltaZ, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::Separator(); + ImGui::InputFloat("Zoom", &zoom, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::InputFloat("Fov", &fov, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::Separator(); + ImGui::InputFloat("GUI scale", &gui_scale, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); + imgui.end(); +} +#endif // ENABLE_CAMERA_STATISTICS + +std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const +{ + std::pair ret; + + while (true) + { + ret = std::make_pair(DBL_MAX, -DBL_MAX); + + // box vertices in world space + std::vector vertices; + vertices.reserve(8); + vertices.push_back(box.min); + vertices.emplace_back(box.max(0), box.min(1), box.min(2)); + vertices.emplace_back(box.max(0), box.max(1), box.min(2)); + vertices.emplace_back(box.min(0), box.max(1), box.min(2)); + vertices.emplace_back(box.min(0), box.min(1), box.max(2)); + vertices.emplace_back(box.max(0), box.min(1), box.max(2)); + vertices.push_back(box.max); + vertices.emplace_back(box.min(0), box.max(1), box.max(2)); + + // set the Z range in eye coordinates (negative Zs are in front of the camera) + for (const Vec3d& v : vertices) + { + double z = -(m_view_matrix * v)(2); + ret.first = std::min(ret.first, z); + ret.second = std::max(ret.second, z); + } + + // apply margin + ret.first -= FrustrumZMargin; + ret.second += FrustrumZMargin; + + // ensure min size + if (ret.second - ret.first < FrustrumMinZRange) + { + double mid_z = 0.5 * (ret.first + ret.second); + double half_size = 0.5 * FrustrumMinZRange; + ret.first = mid_z - half_size; + ret.second = mid_z + half_size; + } + + if (ret.first >= FrustrumMinNearZ) + break; + + // ensure min Near Z + set_distance(m_distance + FrustrumMinNearZ - ret.first); + } + + return ret; +} + +double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h) const +{ + double max_bb_size = box.max_size(); + if (max_bb_size == 0.0) + return -1.0; + + // project the box vertices on a plane perpendicular to the camera forward axis + // then calculates the vertices coordinate on this plane along the camera xy axes + + // ensure that the view matrix is updated + apply_view_matrix(); + + Vec3d right = get_dir_right(); + Vec3d up = get_dir_up(); + Vec3d forward = get_dir_forward(); + + Vec3d bb_center = box.center(); + + // box vertices in world space + std::vector vertices; + vertices.reserve(8); + vertices.push_back(box.min); + vertices.emplace_back(box.max(0), box.min(1), box.min(2)); + vertices.emplace_back(box.max(0), box.max(1), box.min(2)); + vertices.emplace_back(box.min(0), box.max(1), box.min(2)); + vertices.emplace_back(box.min(0), box.min(1), box.max(2)); + vertices.emplace_back(box.max(0), box.min(1), box.max(2)); + vertices.push_back(box.max); + vertices.emplace_back(box.min(0), box.max(1), box.max(2)); + + double max_x = 0.0; + double max_y = 0.0; + + // margin factor to give some empty space around the box + double margin_factor = 1.25; + + for (const Vec3d& v : vertices) + { + // project vertex on the plane perpendicular to camera forward axis + Vec3d pos(v(0) - bb_center(0), v(1) - bb_center(1), v(2) - bb_center(2)); + Vec3d proj_on_plane = pos - pos.dot(forward) * forward; + + // calculates vertex coordinate along camera xy axes + double x_on_plane = proj_on_plane.dot(right); + double y_on_plane = proj_on_plane.dot(up); + + max_x = std::max(max_x, std::abs(x_on_plane)); + max_y = std::max(max_y, std::abs(y_on_plane)); + } + + if ((max_x == 0.0) || (max_y == 0.0)) + return -1.0f; + + max_x *= margin_factor; + max_y *= margin_factor; + + return std::min((double)canvas_w / (2.0 * max_x), (double)canvas_h / (2.0 * max_y)); +} + +void Camera::set_distance(double distance) const +{ + m_distance = distance; + apply_view_matrix(); +} + } // GUI } // Slic3r diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index 6e1b539ab6..839d0d6cf1 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -9,44 +9,65 @@ namespace GUI { struct Camera { + static const double DefaultDistance; + static double FrustrumMinZRange; + static double FrustrumMinNearZ; + static double FrustrumZMargin; + static double MaxFovDeg; + enum EType : unsigned char { Unknown, -// Perspective, Ortho, + Perspective, Num_types }; - EType type; - float zoom; float phi; -// float distance; bool requires_zoom_to_bed; bool inverted_phi; private: + EType m_type; Vec3d m_target; float m_theta; + double m_zoom; + // Distance between camera position and camera target measured along the camera Z axis + mutable double m_distance; + mutable double m_gui_scale; mutable std::array m_viewport; mutable Transform3d m_view_matrix; mutable Transform3d m_projection_matrix; + mutable std::pair m_frustrum_zs; BoundingBoxf3 m_scene_box; public: Camera(); + EType get_type() const { return m_type; } std::string get_type_as_string() const; + void set_type(EType type); + // valid values for type: "0" -> ortho, "1" -> perspective + void set_type(const std::string& type); + void select_next_type(); const Vec3d& get_target() const { return m_target; } void set_target(const Vec3d& target); + double get_distance() const { return m_distance; } + double get_gui_scale() const { return m_gui_scale; } + float get_theta() const { return m_theta; } void set_theta(float theta, bool apply_limit); + double get_zoom() const { return m_zoom; } + void set_zoom(double zoom, const BoundingBoxf3& max_box, int canvas_w, int canvas_h); + void set_zoom(double zoom) { m_zoom = zoom; } + const BoundingBoxf3& get_scene_box() const { return m_scene_box; } - void set_scene_box(const BoundingBoxf3& box); + void set_scene_box(const BoundingBoxf3& box) { m_scene_box = box; } bool select_view(const std::string& direction); @@ -56,13 +77,31 @@ public: Vec3d get_dir_right() const { return m_view_matrix.matrix().block(0, 0, 3, 3).row(0); } Vec3d get_dir_up() const { return m_view_matrix.matrix().block(0, 0, 3, 3).row(1); } - Vec3d get_dir_forward() const { return m_view_matrix.matrix().block(0, 0, 3, 3).row(2); } + Vec3d get_dir_forward() const { return -m_view_matrix.matrix().block(0, 0, 3, 3).row(2); } - Vec3d get_position() const { return m_view_matrix.matrix().block(0, 3, 3, 1); } + Vec3d get_position() const { return m_view_matrix.matrix().inverse().block(0, 3, 3, 1); } + + double get_near_z() const { return m_frustrum_zs.first; } + double get_far_z() const { return m_frustrum_zs.second; } + + double get_fov() const; void apply_viewport(int x, int y, unsigned int w, unsigned int h) const; void apply_view_matrix() const; - void apply_ortho_projection(float x_min, float x_max, float y_min, float y_max, float z_min, float z_max) const; + void apply_projection(const BoundingBoxf3& box) const; + + void zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h); + +#if ENABLE_CAMERA_STATISTICS + void debug_render() const; +#endif // ENABLE_CAMERA_STATISTICS + +private: + // returns tight values for nearZ and farZ plane around the given bounding box + // the camera MUST be outside of the bounding box in eye coordinate of the given box + std::pair calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const; + double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h) const; + void set_distance(double distance) const; }; } // GUI diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index aacbfdc52e..0115ff5f9c 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -25,6 +25,7 @@ #include "PresetBundle.hpp" #include "GUI.hpp" #include "GUI_Utils.hpp" +#include "slic3r/Config/Snapshot.hpp" #include "slic3r/Utils/PresetUpdater.hpp" @@ -32,6 +33,10 @@ namespace Slic3r { namespace GUI { +using Config::Snapshot; +using Config::SnapshotDB; + + // Printer model picker GUI control struct PrinterPickerEvent : public wxEvent @@ -330,8 +335,8 @@ PagePrinters::PagePrinters(ConfigWizard *parent, wxString title, wxString shortn const auto families = vendor.families(); for (const auto &family : families) { const auto filter = [&](const VendorProfile::PrinterModel &model) { - return (model.technology == ptFFF && technology & T_FFF - || model.technology == ptSLA && technology & T_SLA) + return ((model.technology == ptFFF && technology & T_FFF) + || (model.technology == ptSLA && technology & T_SLA)) && model.family == family; }; @@ -532,15 +537,21 @@ PageBedShape::PageBedShape(ConfigWizard *parent) { append_text(_(L("Set the shape of your printer's bed."))); - shape_panel->build_panel(wizard_p()->custom_config->option("bed_shape")); + shape_panel->build_panel(*wizard_p()->custom_config->option("bed_shape"), + *wizard_p()->custom_config->option("bed_custom_texture"), + *wizard_p()->custom_config->option("bed_custom_model")); + append(shape_panel); } void PageBedShape::apply_custom_config(DynamicPrintConfig &config) { - const auto points(shape_panel->GetValue()); - auto *opt = new ConfigOptionPoints(points); - config.set_key_value("bed_shape", opt); + const std::vector& points = shape_panel->get_shape(); + const std::string& custom_texture = shape_panel->get_custom_texture(); + const std::string& custom_model = shape_panel->get_custom_model(); + config.set_key_value("bed_shape", new ConfigOptionPoints(points)); + config.set_key_value("bed_custom_texture", new ConfigOptionString(custom_texture)); + config.set_key_value("bed_custom_model", new ConfigOptionString(custom_model)); } PageDiameters::PageDiameters(ConfigWizard *parent) @@ -810,7 +821,7 @@ void ConfigWizardIndex::on_paint(wxPaintEvent & evt) const Item& item = items[i]; unsigned x = em_w/2 + item.indent * em_w; - if (i == item_active || item_hover >= 0 && i == (size_t)item_hover) { + if (i == item_active || (item_hover >= 0 && i == (size_t)item_hover)) { dc.DrawBitmap(bullet_blue.bmp(), x, y + yoff_icon, false); } else if (i < item_active) { dc.DrawBitmap(bullet_black.bmp(), x, y + yoff_icon, false); } @@ -1019,15 +1030,33 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese // Decide whether to create snapshot based on run_reason and the reset profile checkbox bool snapshot = true; + Snapshot::Reason snapshot_reason = Snapshot::SNAPSHOT_UPGRADE; switch (run_reason) { - case ConfigWizard::RR_DATA_EMPTY: snapshot = false; break; - case ConfigWizard::RR_DATA_LEGACY: snapshot = true; break; - case ConfigWizard::RR_DATA_INCOMPAT: snapshot = false; break; // In this case snapshot is done by PresetUpdater with the appropriate reason - case ConfigWizard::RR_USER: snapshot = page_welcome->reset_user_profile(); break; + case ConfigWizard::RR_DATA_EMPTY: + snapshot = false; + break; + case ConfigWizard::RR_DATA_LEGACY: + snapshot = true; + break; + case ConfigWizard::RR_DATA_INCOMPAT: + // In this case snapshot has already been taken by + // PresetUpdater with the appropriate reason + snapshot = false; + break; + case ConfigWizard::RR_USER: + snapshot = page_welcome->reset_user_profile(); + snapshot_reason = Snapshot::SNAPSHOT_USER; + break; } + + if (snapshot) { + SnapshotDB::singleton().take_snapshot(*app_config, snapshot_reason); + } + if (install_bundles.size() > 0) { // Install bundles from resources. - updater->install_bundles_rsrc(std::move(install_bundles), snapshot); + // Don't create snapshot - we've already done that above if applicable. + updater->install_bundles_rsrc(std::move(install_bundles), false); } else { BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources"; } @@ -1086,7 +1115,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent, RunReason reason) p->load_vendors(); p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({ - "gcode_flavor", "bed_shape", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature", + "gcode_flavor", "bed_shape", "bed_custom_texture", "bed_custom_model", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature", })); p->index = new ConfigWizardIndex(this); diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 7f42db4d79..2bee0018c9 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -113,11 +113,19 @@ wxString Field::get_tooltip_text(const wxString& default_string) wxString tooltip_text(""); wxString tooltip = _(m_opt.tooltip); edit_tooltip(tooltip); + + std::string opt_id = m_opt_id; + auto hash_pos = opt_id.find("#"); + if (hash_pos != std::string::npos) { + opt_id.replace(hash_pos, 1,"["); + opt_id += "]"; + } + if (tooltip.length() > 0) tooltip_text = tooltip + "\n" + _(L("default value")) + "\t: " + - (boost::iends_with(m_opt_id, "_gcode") ? "\n" : "") + default_string + - (boost::iends_with(m_opt_id, "_gcode") ? "" : "\n") + - _(L("parameter name")) + "\t: " + m_opt_id; + (boost::iends_with(opt_id, "_gcode") ? "\n" : "") + default_string + + (boost::iends_with(opt_id, "_gcode") ? "" : "\n") + + _(L("parameter name")) + "\t: " + opt_id; return tooltip_text; } @@ -128,6 +136,8 @@ bool Field::is_matched(const std::string& string, const std::string& pattern) return std::regex_match(string, regex_pattern); } +static wxString na_value() { return _(L("N/A")); } + void Field::get_value_by_opt_type(wxString& str) { switch (m_opt.type) { @@ -157,7 +167,9 @@ void Field::get_value_by_opt_type(wxString& str) val = 0.0; else { - if (!str.ToCDouble(&val)) + if (m_opt.nullable && str == na_value()) + val = ConfigOptionFloatsNullable::nil_value(); + else if (!str.ToCDouble(&val)) { show_error(m_parent, _(L("Invalid numeric input."))); set_value(double_to_string(val), true); @@ -185,17 +197,18 @@ void Field::get_value_by_opt_type(wxString& str) show_error(m_parent, _(L("Invalid numeric input."))); set_value(double_to_string(val), true); } - else if (m_opt.sidetext.rfind("mm/s") != std::string::npos && val > m_opt.max || - m_opt.sidetext.rfind("mm ") != std::string::npos && val > 1) + else if ((m_opt.sidetext.rfind("mm/s") != std::string::npos && val > m_opt.max || + m_opt.sidetext.rfind("mm ") != std::string::npos && val > 1) && + (m_value.empty() || std::string(str.ToUTF8().data()) != boost::any_cast(m_value))) { - std::string sidetext = m_opt.sidetext.rfind("mm/s") != std::string::npos ? "mm/s" : "mm"; - const int nVal = int(val); - wxString msg_text = wxString::Format(_(L("Do you mean %d%% instead of %d %s?\n" - "Select YES if you want to change this value to %d%%, \n" - "or NO if you are sure that %d %s is a correct value.")), nVal, nVal, sidetext, nVal, nVal, sidetext); - auto dialog = new wxMessageDialog(m_parent, msg_text, _(L("Parameter validation")), wxICON_WARNING | wxYES | wxNO); - if (dialog->ShowModal() == wxID_YES) { - set_value(wxString::Format("%s%%", str), true); + const std::string sidetext = m_opt.sidetext.rfind("mm/s") != std::string::npos ? "mm/s" : "mm"; + const wxString stVal = double_to_string(val, 2); + const wxString msg_text = wxString::Format(_(L("Do you mean %s%% instead of %s %s?\n" + "Select YES if you want to change this value to %s%%, \n" + "or NO if you are sure that %s %s is a correct value.")), stVal, stVal, sidetext, stVal, stVal, sidetext); + wxMessageDialog dialog(m_parent, msg_text, _(L("Parameter validation")), wxICON_WARNING | wxYES | wxNO); + if (dialog.ShowModal() == wxID_YES) { + set_value(wxString::Format("%s%%", stVal), false/*true*/); str += "%%"; } } @@ -247,6 +260,7 @@ void TextCtrl::BUILD() { m_opt.default_value->getFloat() : m_opt.get_default_value()->get_at(m_opt_idx); text_value = double_to_string(val); + m_last_meaningful_value = text_value; break; } case coString: @@ -316,24 +330,7 @@ void TextCtrl::BUILD() { } propagate_value(); }), temp->GetId()); - /* - temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent& evt) - { -#ifdef __WXGTK__ - if (bChangedValueEvent) -#endif //__WXGTK__ - if(is_defined_input_value()) - on_change_field(); - }), temp->GetId()); -#ifdef __WXGTK__ - // to correct value updating on GTK we should: - // call on_change_field() on wxEVT_KEY_UP instead of wxEVT_TEXT - // and prevent value updating on wxEVT_KEY_DOWN - temp->Bind(wxEVT_KEY_DOWN, &TextCtrl::change_field_value, this); - temp->Bind(wxEVT_KEY_UP, &TextCtrl::change_field_value, this); -#endif //__WXGTK__ -*/ // select all text using Ctrl+A temp->Bind(wxEVT_CHAR, ([temp](wxKeyEvent& event) { @@ -346,18 +343,80 @@ void TextCtrl::BUILD() { window = dynamic_cast(temp); } +bool TextCtrl::value_was_changed() +{ + if (m_value.empty()) + return true; + + boost::any val = m_value; + wxString ret_str = static_cast(window)->GetValue(); + // update m_value! + get_value_by_opt_type(ret_str); + + switch (m_opt.type) { + case coInt: + return boost::any_cast(m_value) != boost::any_cast(val); + case coPercent: + case coPercents: + case coFloats: + case coFloat: { + if (m_opt.nullable && std::isnan(boost::any_cast(m_value)) && + std::isnan(boost::any_cast(val))) + return false; + return boost::any_cast(m_value) != boost::any_cast(val); + } + case coString: + case coStrings: + case coFloatOrPercent: + return boost::any_cast(m_value) != boost::any_cast(val); + default: + return true; + } +} + void TextCtrl::propagate_value() { - if (is_defined_input_value(window, m_opt.type)) + if (is_defined_input_value(window, m_opt.type) && value_was_changed()) on_change_field(); else on_kill_focus(); } +void TextCtrl::set_value(const boost::any& value, bool change_event/* = false*/) { + m_disable_change_event = !change_event; + if (m_opt.nullable) { + const bool m_is_na_val = boost::any_cast(value) == na_value(); + if (!m_is_na_val) + m_last_meaningful_value = value; + dynamic_cast(window)->SetValue(m_is_na_val ? na_value() : boost::any_cast(value)); + } + else + dynamic_cast(window)->SetValue(boost::any_cast(value)); + m_disable_change_event = false; + + if (!change_event) { + wxString ret_str = static_cast(window)->GetValue(); + // update m_value to correct work of next value_was_changed() + get_value_by_opt_type(ret_str); + } +} + +void TextCtrl::set_last_meaningful_value() +{ + dynamic_cast(window)->SetValue(boost::any_cast(m_last_meaningful_value)); + propagate_value(); +} + +void TextCtrl::set_na_value() +{ + dynamic_cast(window)->SetValue(na_value()); + propagate_value(); +} + boost::any& TextCtrl::get_value() { wxString ret_str = static_cast(window)->GetValue(); - // modifies ret_string! + // update m_value get_value_by_opt_type(ret_str); return m_value; @@ -400,6 +459,8 @@ void CheckBox::BUILD() { m_opt.get_default_value()->get_at(m_opt_idx) : false; + m_last_meaningful_value = static_cast(check_value); + // Set Label as a string of at least one space simbol to correct system scaling of a CheckBox auto temp = new wxCheckBox(m_parent, wxID_ANY, wxString(" "), wxDefaultPosition, size); temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); @@ -407,7 +468,10 @@ void CheckBox::BUILD() { temp->SetValue(check_value); if (m_opt.readonly) temp->Disable(); - temp->Bind(wxEVT_CHECKBOX, ([this](wxCommandEvent e) { on_change_field(); }), temp->GetId()); + temp->Bind(wxEVT_CHECKBOX, ([this](wxCommandEvent e) { + m_is_na_val = false; + on_change_field(); + }), temp->GetId()); temp->SetToolTip(get_tooltip_text(check_value ? "true" : "false")); @@ -415,6 +479,38 @@ void CheckBox::BUILD() { window = dynamic_cast(temp); } +void CheckBox::set_value(const boost::any& value, bool change_event) +{ + m_disable_change_event = !change_event; + if (m_opt.nullable) { + m_is_na_val = boost::any_cast(value) == ConfigOptionBoolsNullable::nil_value(); + if (!m_is_na_val) + m_last_meaningful_value = value; + dynamic_cast(window)->SetValue(m_is_na_val ? false : boost::any_cast(value) != 0); + } + else + dynamic_cast(window)->SetValue(boost::any_cast(value)); + m_disable_change_event = false; +} + +void CheckBox::set_last_meaningful_value() +{ + if (m_opt.nullable) { + m_is_na_val = false; + dynamic_cast(window)->SetValue(boost::any_cast(m_last_meaningful_value) != 0); + on_change_field(); + } +} + +void CheckBox::set_na_value() +{ + if (m_opt.nullable) { + m_is_na_val = true; + dynamic_cast(window)->SetValue(false); + on_change_field(); + } +} + boost::any& CheckBox::get_value() { // boost::any m_value; @@ -422,7 +518,7 @@ boost::any& CheckBox::get_value() if (m_opt.type == coBool) m_value = static_cast(value); else - m_value = static_cast(value); + m_value = m_is_na_val ? ConfigOptionBoolsNullable::nil_value() : static_cast(value); return m_value; } @@ -463,7 +559,16 @@ void SpinCtrl::BUILD() { break; } - const int min_val = m_opt.min == INT_MIN ? 0: m_opt.min; + const int min_val = m_opt.min == INT_MIN +#ifdef __WXOSX__ + // We will forcibly set the input value for SpinControl, since the value + // inserted from the keyboard is not updated under OSX. + // So, we can't set min control value bigger then 0. + // Otherwise, it couldn't be possible to input from keyboard value + // less then min_val. + || m_opt.min > 0 +#endif + ? 0 : m_opt.min; const int max_val = m_opt.max < 2147483647 ? m_opt.max : 2147483647; auto temp = new wxSpinCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, @@ -535,6 +640,14 @@ void SpinCtrl::propagate_value() if (tmp_value == UNDEF_VALUE) { on_kill_focus(); } else { +#ifdef __WXOSX__ + // check input value for minimum + if (m_opt.min > 0 && tmp_value < m_opt.min) { + wxSpinCtrl* spin = static_cast(window); + spin->SetValue(m_opt.min); + spin->GetText()->SetInsertionPointEnd(); + } +#endif on_change_field(); } } @@ -815,7 +928,7 @@ boost::any& Choice::get_value() wxString ret_str = field->GetValue(); // options from right panel - std::vector right_panel_options{ "support", "scale_unit" }; + std::vector right_panel_options{ "support", "pad", "scale_unit" }; for (auto rp_option: right_panel_options) if (m_opt_id == rp_option) return m_value = boost::any(ret_str); @@ -920,11 +1033,12 @@ void ColourPicker::BUILD() // Validate the color wxString clr_str(m_opt.get_default_value()->get_at(m_opt_idx)); wxColour clr(clr_str); - if (! clr.IsOk()) { + if (clr_str.IsEmpty() || !clr.IsOk()) { clr = wxTransparentColour; } auto temp = new wxColourPickerCtrl(m_parent, wxID_ANY, clr, wxDefaultPosition, size); + temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); temp->SetBackgroundStyle(wxBG_STYLE_PAINT); // // recast as a wxWindow to fit the calling convention @@ -935,17 +1049,59 @@ void ColourPicker::BUILD() temp->SetToolTip(get_tooltip_text(clr_str)); } +void ColourPicker::set_undef_value(wxColourPickerCtrl* field) +{ + field->SetColour(wxTransparentColour); + + wxButton* btn = dynamic_cast(field->GetPickerCtrl()); + wxBitmap bmp = btn->GetBitmap(); + wxMemoryDC dc(bmp); + dc.SetTextForeground(*wxWHITE); + dc.SetFont(wxGetApp().normal_font()); + + const wxRect rect = wxRect(0, 0, bmp.GetWidth(), bmp.GetHeight()); + dc.DrawLabel("undef", rect, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL); + + dc.SelectObject(wxNullBitmap); + btn->SetBitmapLabel(bmp); +} + +void ColourPicker::set_value(const boost::any& value, bool change_event) +{ + m_disable_change_event = !change_event; + const wxString clr_str(boost::any_cast(value)); + auto field = dynamic_cast(window); + + wxColour clr(clr_str); + if (clr_str.IsEmpty() || !clr.IsOk()) + set_undef_value(field); + else + field->SetColour(clr); + + m_disable_change_event = false; +} + boost::any& ColourPicker::get_value() { -// boost::any m_value; - auto colour = static_cast(window)->GetColour(); - auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), colour.Red(), colour.Green(), colour.Blue()); - m_value = clr_str.ToStdString(); - + if (colour == wxTransparentColour) + m_value = std::string(""); + else { + auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), colour.Red(), colour.Green(), colour.Blue()); + m_value = clr_str.ToStdString(); + } return m_value; } +void ColourPicker::msw_rescale() +{ + Field::msw_rescale(); + + wxColourPickerCtrl* field = dynamic_cast(window); + if (field->GetColour() == wxTransparentColour) + set_undef_value(field); +} + void PointCtrl::BUILD() { auto temp = new wxBoxSizer(wxHORIZONTAL); diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index 990c40e6f9..761b99ed83 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -123,6 +123,8 @@ public: /// subclasses should overload with a specific version /// Postcondition: Method does not fire the on_change event. virtual void set_value(const boost::any& value, bool change_event) = 0; + virtual void set_last_meaningful_value() {} + virtual void set_na_value() {} /// Gets a boost::any representing this control. /// subclasses should overload with a specific version @@ -247,6 +249,8 @@ protected: // current value boost::any m_value; + // last maeningful value + boost::any m_last_meaningful_value; int m_em_unit; @@ -277,6 +281,7 @@ public: ~TextCtrl() {} void BUILD(); + bool value_was_changed(); // Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER void propagate_value(); wxWindow* window {nullptr}; @@ -286,11 +291,9 @@ public: dynamic_cast(window)->SetValue(wxString(value)); m_disable_change_event = false; } - virtual void set_value(const boost::any& value, bool change_event = false) { - m_disable_change_event = !change_event; - dynamic_cast(window)->SetValue(boost::any_cast(value)); - m_disable_change_event = false; - } + virtual void set_value(const boost::any& value, bool change_event = false) override; + virtual void set_last_meaningful_value() override; + virtual void set_na_value() override; boost::any& get_value() override; @@ -303,6 +306,7 @@ public: class CheckBox : public Field { using Field::Field; + bool m_is_na_val {false}; public: CheckBox(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {} CheckBox(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {} @@ -316,11 +320,9 @@ public: dynamic_cast(window)->SetValue(value); m_disable_change_event = false; } - void set_value(const boost::any& value, bool change_event = false) { - m_disable_change_event = !change_event; - dynamic_cast(window)->SetValue(boost::any_cast(value)); - m_disable_change_event = false; - } + void set_value(const boost::any& value, bool change_event = false) override; + void set_last_meaningful_value() override; + void set_na_value() override; boost::any& get_value() override; void msw_rescale() override; @@ -402,6 +404,8 @@ public: class ColourPicker : public Field { using Field::Field; + + void set_undef_value(wxColourPickerCtrl* field); public: ColourPicker(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {} ColourPicker(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {} @@ -415,13 +419,9 @@ public: dynamic_cast(window)->SetColour(value); m_disable_change_event = false; } - void set_value(const boost::any& value, bool change_event = false) { - m_disable_change_event = !change_event; - dynamic_cast(window)->SetColour(boost::any_cast(value)); - m_disable_change_event = false; - } - + void set_value(const boost::any& value, bool change_event = false) override; boost::any& get_value() override; + void msw_rescale() override; void enable() override { dynamic_cast(window)->Enable(); }; void disable() override{ dynamic_cast(window)->Disable(); }; diff --git a/src/slic3r/GUI/FirmwareDialog.cpp b/src/slic3r/GUI/FirmwareDialog.cpp index b400e27eae..d1f2da040c 100644 --- a/src/slic3r/GUI/FirmwareDialog.cpp +++ b/src/slic3r/GUI/FirmwareDialog.cpp @@ -5,11 +5,14 @@ #include #include #include -#include -#include +#include #include #include +#if _WIN32 + #include +#endif + #include "libslic3r/Utils.hpp" #include "avrdude/avrdude-slic3r.hpp" #include "GUI.hpp" @@ -104,7 +107,7 @@ struct FirmwareDialog::priv // GUI elements wxComboBox *port_picker; - wxStaticText *port_autodetect; + wxStaticText *txt_port_autodetect; wxFilePickerCtrl *hex_picker; wxStaticText *txt_status; wxGauge *progressbar; @@ -131,6 +134,7 @@ struct FirmwareDialog::priv // Data std::vector ports; optional port; + bool port_autodetect; HexFile hex_file; // This is a shared pointer holding the background AvrDude task @@ -147,6 +151,7 @@ struct FirmwareDialog::priv btn_flash_label_flashing(_(L("Cancel"))), label_status_flashing(_(L("Flashing in progress. Please do not disconnect the printer!"))), timer_pulse(q), + port_autodetect(false), progress_tasks_done(0), progress_tasks_bar(0), user_cancelled(false), @@ -158,7 +163,8 @@ struct FirmwareDialog::priv void set_txt_status(const wxString &label); void flashing_start(unsigned tasks); void flashing_done(AvrDudeComplete complete); - void enable_port_picker(bool enable); + void set_autodetect(bool autodetect); + void update_flash_enabled(); void load_hex_file(const wxString &path); void queue_event(AvrdudeEvent aevt, wxString message); @@ -171,6 +177,7 @@ struct FirmwareDialog::priv void prepare_mk2(); void prepare_mk3(); void prepare_avr109(Avr109Pid usb_pid); + bool get_serial_port(); void perform_upload(); void user_cancel(); @@ -211,8 +218,10 @@ void FirmwareDialog::priv::find_serial_ports() idx = i; break; } - if (idx != -1) + if (idx != -1) { port_picker->SetSelection(idx); + update_flash_enabled(); + } } } } @@ -277,20 +286,30 @@ void FirmwareDialog::priv::flashing_done(AvrDudeComplete complete) } } -void FirmwareDialog::priv::enable_port_picker(bool enable) +void FirmwareDialog::priv::set_autodetect(bool autodetect) { - port_picker->Show(enable); - btn_rescan->Show(enable); - port_autodetect->Show(! enable); + port_autodetect = autodetect; + + port_picker->Show(!autodetect); + btn_rescan->Show(!autodetect); + txt_port_autodetect->Show(autodetect); q->Layout(); fit_no_shrink(); } +void FirmwareDialog::priv::update_flash_enabled() +{ + const bool hex_exists = wxFileExists(hex_picker->GetPath()); + const bool port_valid = port_autodetect || get_serial_port(); + + btn_flash->Enable(hex_exists && port_valid); +} + void FirmwareDialog::priv::load_hex_file(const wxString &path) { hex_file = HexFile(path.wx_str()); - const bool auto_lookup = hex_file.device == HexFile::DEV_MM_CONTROL || hex_file.device == HexFile::DEV_CW1; - enable_port_picker(! auto_lookup); + const bool autodetect = hex_file.device == HexFile::DEV_MM_CONTROL || hex_file.device == HexFile::DEV_CW1; + set_autodetect(autodetect); } void FirmwareDialog::priv::queue_event(AvrdudeEvent aevt, wxString message) @@ -335,7 +354,7 @@ bool FirmwareDialog::priv::check_model_id() // Therefore, regretably, so far the check cannot be used and we just return true here. // TODO: Rewrite Serial using more platform-native code. return true; - + // if (hex_file.model_id.empty()) { // // No data to check against, assume it's ok // return true; @@ -423,8 +442,7 @@ void FirmwareDialog::priv::avr109_lookup_port(Avr109Pid usb_pid) auto ports = Utils::scan_serial_ports_extended(); ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) { return port.id_vendor != USB_VID_PRUSA || - port.id_product != usb_pid.boot && - port.id_product != usb_pid.app; + (port.id_product != usb_pid.boot && port.id_product != usb_pid.app); }), ports.end()); if (ports.size() == 0) { @@ -553,6 +571,31 @@ void FirmwareDialog::priv::prepare_avr109(Avr109Pid usb_pid) } +bool FirmwareDialog::priv::get_serial_port() +{ + const int selection = port_picker->GetSelection(); + if (selection != wxNOT_FOUND) { + port = this->ports[selection]; + } else { + // User has supplied a custom filename + + std::string path_u8 = GUI::into_u8(port_picker->GetValue()); +#ifdef _WIN32 + static const std::regex com_pattern("COM[0-9]+", std::regex::icase); + std::smatch matches; + if (std::regex_match(path_u8, matches, com_pattern)) { +#else + if (fs::is_other(fs::path(path_u8))) { +#endif + port = SerialPortInfo(std::move(path_u8)); + } else { + port = boost::none; + } + } + + return !!port; +} + void FirmwareDialog::priv::perform_upload() { auto filename = hex_picker->GetPath(); @@ -560,14 +603,8 @@ void FirmwareDialog::priv::perform_upload() load_hex_file(filename); // Might already be loaded, but we want to make sure it's fresh - int selection = port_picker->GetSelection(); - if (selection != wxNOT_FOUND) { - port = this->ports[selection]; - - // Verify whether the combo box list selection equals to the combo box edit value. - if (wxString::FromUTF8(port->friendly_name.data()) != port_picker->GetValue()) { - return; - } + if (!port_autodetect && !get_serial_port()) { + return; } const bool extra_verbose = false; // For debugging @@ -769,13 +806,13 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : auto *label_port_picker = new wxStaticText(panel, wxID_ANY, _(L("Serial port:"))); p->port_picker = new wxComboBox(panel, wxID_ANY); - p->port_autodetect = new wxStaticText(panel, wxID_ANY, _(L("Autodetected"))); + p->txt_port_autodetect = new wxStaticText(panel, wxID_ANY, _(L("Autodetected"))); p->btn_rescan = new wxButton(panel, wxID_ANY, _(L("Rescan"))); auto *port_sizer = new wxBoxSizer(wxHORIZONTAL); port_sizer->Add(p->port_picker, 1, wxEXPAND | wxRIGHT, SPACING); port_sizer->Add(p->btn_rescan, 0); - port_sizer->Add(p->port_autodetect, 1, wxEXPAND); - p->enable_port_picker(true); + port_sizer->Add(p->txt_port_autodetect, 1, wxEXPAND); + p->set_autodetect(false); auto *label_progress = new wxStaticText(panel, wxID_ANY, _(L("Progress:"))); p->progressbar = new wxGauge(panel, wxID_ANY, 1, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL | wxGA_SMOOTH); @@ -836,10 +873,13 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : p->hex_picker->Bind(wxEVT_FILEPICKER_CHANGED, [this](wxFileDirPickerEvent& evt) { if (wxFileExists(evt.GetPath())) { this->p->load_hex_file(evt.GetPath()); - this->p->btn_flash->Enable(); } + p->update_flash_enabled(); }); + p->port_picker->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) { p->update_flash_enabled(); }); + p->port_picker->Bind(wxEVT_TEXT, [this](wxCommandEvent &) { p->update_flash_enabled(); }); + p->spoiler->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, [=](wxCollapsiblePaneEvent &evt) { if (evt.GetCollapsed()) { this->SetMinSize(wxSize(p->min_width, p->min_height)); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index b8ca905d27..8c15d0fb62 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -17,6 +17,7 @@ #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/PresetBundle.hpp" #include "slic3r/GUI/Tab.hpp" +#include "slic3r/GUI/GUI_Preview.hpp" #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" #include "GUI_ObjectManipulation.hpp" @@ -63,11 +64,6 @@ static const float GROUND_Z = -0.02f; static const float GIZMO_RESET_BUTTON_HEIGHT = 22.0f; static const float GIZMO_RESET_BUTTON_WIDTH = 70.f; -static const float UNIT_MATRIX[] = { 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f }; - static const float DEFAULT_BG_DARK_COLOR[3] = { 0.478f, 0.478f, 0.478f }; static const float DEFAULT_BG_LIGHT_COLOR[3] = { 0.753f, 0.753f, 0.753f }; static const float ERROR_BG_DARK_COLOR[3] = { 0.478f, 0.192f, 0.039f }; @@ -120,90 +116,8 @@ void Size::set_scale_factor(int scale_factor) m_scale_factor = scale_factor; } -#if !ENABLE_TEXTURES_FROM_SVG -GLCanvas3D::Shader::Shader() - : m_shader(nullptr) -{ -} - -GLCanvas3D::Shader::~Shader() -{ - _reset(); -} - -bool GLCanvas3D::Shader::init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename) -{ - if (is_initialized()) - return true; - - m_shader = new GLShader(); - if (m_shader != nullptr) - { - if (!m_shader->load_from_file(fragment_shader_filename.c_str(), vertex_shader_filename.c_str())) - { - std::cout << "Compilaton of shader failed:" << std::endl; - std::cout << m_shader->last_error << std::endl; - _reset(); - return false; - } - } - - return true; -} - -bool GLCanvas3D::Shader::is_initialized() const -{ - return (m_shader != nullptr); -} - -bool GLCanvas3D::Shader::start_using() const -{ - if (is_initialized()) - { - m_shader->enable(); - return true; - } - else - return false; -} - -void GLCanvas3D::Shader::stop_using() const -{ - if (m_shader != nullptr) - m_shader->disable(); -} - -void GLCanvas3D::Shader::set_uniform(const std::string& name, float value) const -{ - if (m_shader != nullptr) - m_shader->set_uniform(name.c_str(), value); -} - -void GLCanvas3D::Shader::set_uniform(const std::string& name, const float* matrix) const -{ - if (m_shader != nullptr) - m_shader->set_uniform(name.c_str(), matrix); -} - -const GLShader* GLCanvas3D::Shader::get_shader() const -{ - return m_shader; -} - -void GLCanvas3D::Shader::_reset() -{ - if (m_shader != nullptr) - { - m_shader->release(); - delete m_shader; - m_shader = nullptr; - } -} -#endif // !ENABLE_TEXTURES_FROM_SVG - GLCanvas3D::LayersEditing::LayersEditing() - : m_use_legacy_opengl(false) - , m_enabled(false) + : m_enabled(false) , m_z_texture_id(0) , m_model_object(nullptr) , m_object_max_z(0.f) @@ -278,12 +192,7 @@ void GLCanvas3D::LayersEditing::select_object(const Model &model, int object_id) bool GLCanvas3D::LayersEditing::is_allowed() const { - return !m_use_legacy_opengl && m_shader.is_initialized() && m_shader.get_shader()->shader_program_id > 0 && m_z_texture_id > 0; -} - -void GLCanvas3D::LayersEditing::set_use_legacy_opengl(bool use_legacy_opengl) -{ - m_use_legacy_opengl = use_legacy_opengl; + return m_shader.is_initialized() && m_shader.get_shader()->shader_program_id > 0 && m_z_texture_id > 0; } bool GLCanvas3D::LayersEditing::is_enabled() const @@ -304,22 +213,10 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const const Rect& bar_rect = get_bar_rect_viewport(canvas); const Rect& reset_rect = get_reset_rect_viewport(canvas); - glsafe(::glDisable(GL_DEPTH_TEST)); - - // The viewport and camera are set to complete view and glOrtho(-$x / 2, $x / 2, -$y / 2, $y / 2, -$depth, $depth), - // where x, y is the window size divided by $self->_zoom. - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); - _render_tooltip_texture(canvas, bar_rect, reset_rect); _render_reset_texture(reset_rect); _render_active_object_annotations(canvas, bar_rect); _render_profile(bar_rect); - - // Revert the matrices. - glsafe(::glPopMatrix()); - - glsafe(::glEnable(GL_DEPTH_TEST)); } float GLCanvas3D::LayersEditing::get_cursor_z_relative(const GLCanvas3D& canvas) @@ -374,7 +271,7 @@ Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas) float half_w = 0.5f * (float)cnv_size.get_width(); float half_h = 0.5f * (float)cnv_size.get_height(); - float zoom = canvas.get_camera().zoom; + float zoom = (float)canvas.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; return Rect((half_w - thickness_bar_width(canvas)) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, (-half_h + reset_button_height(canvas)) * inv_zoom); @@ -386,7 +283,7 @@ Rect GLCanvas3D::LayersEditing::get_reset_rect_viewport(const GLCanvas3D& canvas float half_w = 0.5f * (float)cnv_size.get_width(); float half_h = 0.5f * (float)cnv_size.get_height(); - float zoom = canvas.get_camera().zoom; + float zoom = (float)canvas.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; return Rect((half_w - thickness_bar_width(canvas)) * inv_zoom, (-half_h + reset_button_height(canvas)) * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom); @@ -405,7 +302,7 @@ void GLCanvas3D::LayersEditing::_render_tooltip_texture(const GLCanvas3D& canvas if (m_tooltip_texture.get_id() == 0) { std::string filename = resources_dir() + "/icons/variable_layer_height_tooltip.png"; - if (!m_tooltip_texture.load_from_file(filename, false)) + if (!m_tooltip_texture.load_from_file(filename, false, GLTexture::SingleThreaded, false)) return; } @@ -417,7 +314,7 @@ void GLCanvas3D::LayersEditing::_render_tooltip_texture(const GLCanvas3D& canvas const float width = (float)m_tooltip_texture.get_width() * scale; const float height = (float)m_tooltip_texture.get_height() * scale; - float zoom = canvas.get_camera().zoom; + float zoom = (float)canvas.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; float gap = 10.0f * inv_zoom; @@ -437,7 +334,7 @@ void GLCanvas3D::LayersEditing::_render_reset_texture(const Rect& reset_rect) co if (m_reset_texture.get_id() == 0) { std::string filename = resources_dir() + "/icons/variable_layer_height_reset.png"; - if (!m_reset_texture.load_from_file(filename, false)) + if (!m_reset_texture.load_from_file(filename, false, GLTexture::SingleThreaded, false)) return; } @@ -452,8 +349,7 @@ void GLCanvas3D::LayersEditing::_render_active_object_annotations(const GLCanvas m_shader.set_uniform("z_texture_row_to_normalized", 1.0f / (float)m_layers_texture.height); m_shader.set_uniform("z_cursor", m_object_max_z * this->get_cursor_z_relative(canvas)); m_shader.set_uniform("z_cursor_band_width", band_width); - // The shader requires the original model coordinates when rendering to the texture, so we pass it the unit matrix - m_shader.set_uniform("volume_world_matrix", UNIT_MATRIX); + m_shader.set_uniform("object_max_z", m_object_max_z); glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); @@ -466,10 +362,10 @@ void GLCanvas3D::LayersEditing::_render_active_object_annotations(const GLCanvas ::glBegin(GL_QUADS); ::glNormal3f(0.0f, 0.0f, 1.0f); - ::glVertex3f(l, b, 0.0f); - ::glVertex3f(r, b, 0.0f); - ::glVertex3f(r, t, m_object_max_z); - ::glVertex3f(l, t, m_object_max_z); + ::glTexCoord2f(0.0f, 0.0f); ::glVertex2f(l, b); + ::glTexCoord2f(1.0f, 0.0f); ::glVertex2f(r, b); + ::glTexCoord2f(1.0f, 1.0f); ::glVertex2f(r, t); + ::glTexCoord2f(0.0f, 1.0f); ::glVertex2f(l, t); glsafe(::glEnd()); glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); @@ -480,8 +376,10 @@ void GLCanvas3D::LayersEditing::_render_profile(const Rect& bar_rect) const { //FIXME show some kind of legend. + if (!m_slicing_parameters) + return; + // Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region. - assert(m_slicing_parameters != nullptr); float scale_x = bar_rect.get_width() / (float)(1.12 * m_slicing_parameters->max_layer_height); float scale_y = bar_rect.get_height() / m_object_max_z; float x = bar_rect.get_left() + (float)m_slicing_parameters->layer_height * scale_x; @@ -522,6 +420,7 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G GLint z_cursor_id = ::glGetUniformLocation(shader_id, "z_cursor"); GLint z_cursor_band_width_id = ::glGetUniformLocation(shader_id, "z_cursor_band_width"); GLint world_matrix_id = ::glGetUniformLocation(shader_id, "volume_world_matrix"); + GLint object_max_z_id = ::glGetUniformLocation(shader_id, "object_max_z"); glcheck(); if (z_to_texture_row_id != -1 && z_texture_row_to_normalized_id != -1 && z_cursor_id != -1 && z_cursor_band_width_id != -1 && world_matrix_id != -1) @@ -544,11 +443,14 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G glsafe(::glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, half_w, half_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data())); glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, half_w, half_h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data() + m_layers_texture.width * m_layers_texture.height * 4)); - for (const GLVolume *glvolume : volumes.volumes) { + for (const GLVolume* glvolume : volumes.volumes) { // Render the object using the layer editing shader and texture. if (! glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier) continue; - glsafe(::glUniformMatrix4fv(world_matrix_id, 1, GL_FALSE, (const GLfloat*)glvolume->world_matrix().cast().data())); + if (world_matrix_id != -1) + glsafe(::glUniformMatrix4fv(world_matrix_id, 1, GL_FALSE, (const GLfloat*)glvolume->world_matrix().cast().data())); + if (object_max_z_id != -1) + glsafe(::glUniform1f(object_max_z_id, GLfloat(0))); glvolume->render(); } // Revert back to the previous shader. @@ -560,8 +462,8 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G { // Something went wrong. Just render the object. assert(false); - for (const GLVolume *glvolume : volumes.volumes) { - // Render the object using the layer editing shader and texture. + for (const GLVolume* glvolume : volumes.volumes) { + // Render the object using the layer editing shader and texture. if (!glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier) continue; glsafe(::glUniformMatrix4fv(world_matrix_id, 1, GL_FALSE, (const GLfloat*)glvolume->world_matrix().cast().data())); @@ -621,6 +523,7 @@ void GLCanvas3D::LayersEditing::accept_changes(GLCanvas3D& canvas) { if (last_object_id >= 0) { if (m_layer_height_profile_modified) { + wxGetApp().plater()->take_snapshot(_(L("Layers heights"))); const_cast(m_model_object)->layer_height_profile = m_layer_height_profile; canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } @@ -729,7 +632,7 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool } } - _generate(text, canvas, red_colored); // GUI::GLTexture::reset() is called at the beginning of generate(...) + generate(text, canvas, true, red_colored); // GUI::GLTexture::reset() is called at the beginning of generate(...) // save information for rescaling m_msg_text = text; @@ -790,7 +693,7 @@ static void msw_disable_cleartype(wxFont &font) } #endif /* __WXMSW__ */ -bool GLCanvas3D::WarningTexture::_generate(const std::string& msg_utf8, const GLCanvas3D& canvas, const bool red_colored/* = false*/) +bool GLCanvas3D::WarningTexture::generate(const std::string& msg_utf8, const GLCanvas3D& canvas, bool compress, bool red_colored/* = false*/) { reset(); @@ -864,7 +767,10 @@ bool GLCanvas3D::WarningTexture::_generate(const std::string& msg_utf8, const GL glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); glsafe(::glGenTextures(1, &m_id)); glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)m_id)); - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); + if (compress && GLEW_EXT_texture_compression_s3tc) + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); + else + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); @@ -880,12 +786,8 @@ void GLCanvas3D::WarningTexture::render(const GLCanvas3D& canvas) const if ((m_id > 0) && (m_original_width > 0) && (m_original_height > 0) && (m_width > 0) && (m_height > 0)) { - glsafe(::glDisable(GL_DEPTH_TEST)); - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); - const Size& cnv_size = canvas.get_canvas_size(); - float zoom = canvas.get_camera().zoom; + float zoom = (float)canvas.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; float left = (-0.5f * (float)m_original_width) * inv_zoom; float top = (-0.5f * (float)cnv_size.get_height() + (float)m_original_height + 2.0f) * inv_zoom; @@ -904,9 +806,6 @@ void GLCanvas3D::WarningTexture::render(const GLCanvas3D& canvas) const uvs.right_top = { uv_right, uv_top }; GLTexture::render_sub_texture(m_id, left, right, bottom, top, uvs); - - glsafe(::glPopMatrix()); - glsafe(::glEnable(GL_DEPTH_TEST)); } } @@ -915,7 +814,7 @@ void GLCanvas3D::WarningTexture::msw_rescale(const GLCanvas3D& canvas) if (m_msg_text.empty()) return; - _generate(m_msg_text, canvas, m_is_colored_red); + generate(m_msg_text, canvas, true, m_is_colored_red); } const unsigned char GLCanvas3D::LegendTexture::Squares_Border_Color[3] = { 64, 64, 64 }; @@ -933,7 +832,8 @@ GLCanvas3D::LegendTexture::LegendTexture() void GLCanvas3D::LegendTexture::fill_color_print_legend_values(const GCodePreviewData& preview_data, const GLCanvas3D& canvas, std::vector>& cp_legend_values) { - if (preview_data.extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint) + if (preview_data.extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint && + wxGetApp().extruders_edited_cnt() == 1) // show color change legend only for single-material presets { auto& config = wxGetApp().preset_bundle->project_config; const std::vector& color_print_values = config.option("colorprint_heights")->values; @@ -958,7 +858,7 @@ void GLCanvas3D::LegendTexture::fill_color_print_legend_values(const GCodePrevie } } -bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, const std::vector& tool_colors, const GLCanvas3D& canvas) +bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, const std::vector& tool_colors, const GLCanvas3D& canvas, bool compress) { reset(); @@ -1147,7 +1047,10 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); glsafe(::glGenTextures(1, &m_id)); glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)m_id)); - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); + if (compress && GLEW_EXT_texture_compression_s3tc) + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); + else + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); @@ -1160,12 +1063,8 @@ void GLCanvas3D::LegendTexture::render(const GLCanvas3D& canvas) const { if ((m_id > 0) && (m_original_width > 0) && (m_original_height > 0) && (m_width > 0) && (m_height > 0)) { - glsafe(::glDisable(GL_DEPTH_TEST)); - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); - const Size& cnv_size = canvas.get_canvas_size(); - float zoom = canvas.get_camera().zoom; + float zoom = (float)canvas.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; float left = (-0.5f * (float)cnv_size.get_width()) * inv_zoom; float top = (0.5f * (float)cnv_size.get_height()) * inv_zoom; @@ -1184,9 +1083,6 @@ void GLCanvas3D::LegendTexture::render(const GLCanvas3D& canvas) const uvs.right_top = { uv_right, uv_top }; GLTexture::render_sub_texture(m_id, left, right, bottom, top, uvs); - - glsafe(::glPopMatrix()); - glsafe(::glEnable(GL_DEPTH_TEST)); } } @@ -1210,6 +1106,10 @@ wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar) : m_canvas(canvas) @@ -1221,31 +1121,31 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar , m_bed(bed) , m_camera(camera) , m_view_toolbar(view_toolbar) -#if ENABLE_SVG_ICONS - , m_toolbar(GLToolbar::Normal, "Top") -#else - , m_toolbar(GLToolbar::Normal) -#endif // ENABLE_SVG_ICONS + , m_main_toolbar(GLToolbar::Normal, "Top") + , m_undoredo_toolbar(GLToolbar::Normal, "Top") + , m_gizmos(*this) , m_use_clipping_planes(false) , m_sidebar_field("") + , m_keep_dirty(false) , m_config(nullptr) , m_process(nullptr) , m_model(nullptr) , m_dirty(true) , m_initialized(false) - , m_use_VBOs(false) , m_apply_zoom_to_volumes_filter(false) , m_legend_texture_enabled(false) , m_picking_enabled(false) , m_moving_enabled(false) , m_dynamic_background_enabled(false) , m_multisample_allowed(false) - , m_regenerate_volumes(true) , m_moving(false) , m_tab_down(false) , m_cursor_type(Standard) , m_color_by("volume") , m_reload_delayed(false) +#if ENABLE_RENDER_PICKING_PASS + , m_show_picking_texture(false) +#endif // ENABLE_RENDER_PICKING_PASS , m_render_sla_auxiliaries(true) { if (m_canvas != nullptr) { @@ -1271,7 +1171,7 @@ void GLCanvas3D::post_event(wxEvent &&event) wxPostEvent(m_canvas, event); } -bool GLCanvas3D::init(bool useVBOs, bool use_legacy_opengl) +bool GLCanvas3D::init() { if (m_initialized) return true; @@ -1322,31 +1222,29 @@ bool GLCanvas3D::init(bool useVBOs, bool use_legacy_opengl) if (m_multisample_allowed) glsafe(::glEnable(GL_MULTISAMPLE)); - if (useVBOs && !m_shader.init("gouraud.vs", "gouraud.fs")) + if (!m_shader.init("gouraud.vs", "gouraud.fs")) + { + std::cout << "Unable to initialize gouraud shader: please, check that the files gouraud.vs and gouraud.fs are available" << std::endl; return false; + } - if (m_toolbar.is_enabled() && useVBOs && !m_layers_editing.init("variable_layer_height.vs", "variable_layer_height.fs")) + if (m_main_toolbar.is_enabled() && !m_layers_editing.init("variable_layer_height.vs", "variable_layer_height.fs")) + { + std::cout << "Unable to initialize variable_layer_height shader: please, check that the files variable_layer_height.vs and variable_layer_height.fs are available" << std::endl; return false; - - m_use_VBOs = useVBOs; - m_layers_editing.set_use_legacy_opengl(use_legacy_opengl); + } // on linux the gl context is not valid until the canvas is not shown on screen // we defer the geometry finalization of volumes until the first call to render() - if (!m_volumes.empty()) - m_volumes.finalize_geometry(m_use_VBOs); + m_volumes.finalize_geometry(true); - if (m_gizmos.is_enabled()) { - if (! m_gizmos.init(*this)) { - std::cout << "Unable to initialize gizmos: please, check that all the required textures are available" << std::endl; - return false; - } - } + if (m_gizmos.is_enabled() && !m_gizmos.init()) + std::cout << "Unable to initialize gizmos: please, check that all the required textures are available" << std::endl; - if (!_init_toolbar()) + if (!_init_toolbars()) return false; - if (m_selection.is_enabled() && !m_selection.init(m_use_VBOs)) + if (m_selection.is_enabled() && !m_selection.init()) return false; post_event(SimpleEvent(EVT_GLCANVAS_INIT)); @@ -1376,7 +1274,6 @@ void GLCanvas3D::reset_volumes() if (!m_volumes.empty()) { m_selection.clear(); - m_volumes.release_geometry(); m_volumes.clear(); m_dirty = true; } @@ -1422,6 +1319,26 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject _set_warning_texture(WarningTexture::SomethingNotShown, false); } +void GLCanvas3D::update_instance_printable_state_for_object(const size_t obj_idx) +{ + ModelObject* model_object = m_model->objects[obj_idx]; + for (int inst_idx = 0; inst_idx < model_object->instances.size(); inst_idx++) + { + ModelInstance* instance = model_object->instances[inst_idx]; + + for (GLVolume* volume : m_volumes.volumes) + { + if ((volume->object_idx() == obj_idx) && (volume->instance_idx() == inst_idx)) + volume->printable = instance->printable; + } + } +} + +void GLCanvas3D::update_instance_printable_state_for_objects(std::vector& object_idxs) +{ + for (size_t obj_idx : object_idxs) + update_instance_printable_state_for_object(obj_idx); +} void GLCanvas3D::set_config(const DynamicPrintConfig* config) { @@ -1445,6 +1362,8 @@ void GLCanvas3D::bed_shape_changed() m_camera.set_scene_box(scene_bounding_box()); m_camera.requires_zoom_to_bed = true; m_dirty = true; + if (m_bed.is_prusa()) + start_keeping_dirty(); } void GLCanvas3D::set_color_by(const std::string& value) @@ -1466,7 +1385,7 @@ BoundingBoxf3 GLCanvas3D::volumes_bounding_box() const BoundingBoxf3 GLCanvas3D::scene_bounding_box() const { BoundingBoxf3 bb = volumes_bounding_box(); - bb.merge(m_bed.get_bounding_box()); + bb.merge(m_bed.get_bounding_box(false)); if (m_config != nullptr) { @@ -1474,6 +1393,7 @@ BoundingBoxf3 GLCanvas3D::scene_bounding_box() const bb.min(2) = std::min(bb.min(2), -h); bb.max(2) = std::max(bb.max(2), h); } + return bb; } @@ -1532,9 +1452,14 @@ void GLCanvas3D::enable_selection(bool enable) m_selection.set_enabled(enable); } -void GLCanvas3D::enable_toolbar(bool enable) +void GLCanvas3D::enable_main_toolbar(bool enable) { - m_toolbar.set_enabled(enable); + m_main_toolbar.set_enabled(enable); +} + +void GLCanvas3D::enable_undoredo_toolbar(bool enable) +{ + m_undoredo_toolbar.set_enabled(enable); } void GLCanvas3D::enable_dynamic_background(bool enable) @@ -1549,20 +1474,20 @@ void GLCanvas3D::allow_multisample(bool allow) void GLCanvas3D::zoom_to_bed() { - _zoom_to_bounding_box(m_bed.get_bounding_box()); + _zoom_to_box(m_bed.get_bounding_box(false)); } void GLCanvas3D::zoom_to_volumes() { m_apply_zoom_to_volumes_filter = true; - _zoom_to_bounding_box(volumes_bounding_box()); + _zoom_to_box(volumes_bounding_box()); m_apply_zoom_to_volumes_filter = false; } void GLCanvas3D::zoom_to_selection() { if (!m_selection.is_empty()) - _zoom_to_bounding_box(m_selection.get_bounding_box()); + _zoom_to_box(m_selection.get_bounding_box()); } void GLCanvas3D::select_view(const std::string& direction) @@ -1579,7 +1504,13 @@ void GLCanvas3D::update_volumes_colors_by_extruder() void GLCanvas3D::render() { - wxCHECK_RET(!m_in_render, "GLCanvas3D::render() called recursively"); + if (m_in_render) + { + // if called recursively, return + m_dirty = true; + return; + } + m_in_render = true; Slic3r::ScopeGuard in_render_guard([this]() { m_in_render = false; }); (void)in_render_guard; @@ -1617,6 +1548,7 @@ void GLCanvas3D::render() } m_camera.apply_view_matrix(); + m_camera.apply_projection(_max_bounding_box(true, true)); GLfloat position_cam[4] = { 1.0f, 0.0f, 1.0f, 0.0f }; glsafe(::glLightfv(GL_LIGHT1, GL_POSITION, position_cam)); @@ -1640,23 +1572,18 @@ void GLCanvas3D::render() _picking_pass(); } +#if ENABLE_RENDER_PICKING_PASS + if (!m_picking_enabled || !m_show_picking_texture) + { +#endif // ENABLE_RENDER_PICKING_PASS // draw scene glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); _render_background(); - // textured bed needs to be rendered after objects if the texture is transparent - bool early_bed_render = m_bed.is_custom() || (theta <= 90.0f); - if (early_bed_render) - _render_bed(theta); - _render_objects(); _render_sla_slices(); _render_selection(); - - _render_axes(); - - if (!early_bed_render) - _render_bed(theta); + _render_bed(theta); #if ENABLE_RENDER_SELECTION_CENTER _render_selection_center(); @@ -1669,6 +1596,9 @@ void GLCanvas3D::render() _render_current_gizmo(); _render_selection_sidebar_hints(); +#if ENABLE_RENDER_PICKING_PASS + } +#endif // ENABLE_RENDER_PICKING_PASS #if ENABLE_SHOW_CAMERA_TARGET _render_camera_target(); @@ -1678,29 +1608,31 @@ void GLCanvas3D::render() m_rectangle_selection.render(*this); // draw overlays - _render_gizmos_overlay(); - _render_warning_texture(); - _render_legend_texture(); -#if !ENABLE_SVG_ICONS - _resize_toolbars(); -#endif // !ENABLE_SVG_ICONS - _render_toolbar(); - _render_view_toolbar(); - if ((m_layers_editing.last_object_id >= 0) && (m_layers_editing.object_max_z() > 0.0f)) - m_layers_editing.render_overlay(*this); + _render_overlays(); #if ENABLE_RENDER_STATISTICS ImGuiWrapper& imgui = *wxGetApp().imgui(); imgui.set_next_window_bg_alpha(0.5f); imgui.begin(std::string("Render statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - imgui.text(_(L("Last frame")) +": "); + imgui.text("Last frame: "); ImGui::SameLine(); imgui.text(std::to_string(m_render_stats.last_frame)); ImGui::SameLine(); - imgui.text(" "+_(L("ms"))); + imgui.text(" ms"); + ImGui::Separator(); + imgui.text("Compressed textures: "); + ImGui::SameLine(); + imgui.text(GLCanvas3DManager::are_compressed_textures_supported() ? "supported" : "not supported"); + imgui.text("Max texture size: "); + ImGui::SameLine(); + imgui.text(std::to_string(GLCanvas3DManager::get_gl_info().get_max_tex_size())); imgui.end(); #endif // ENABLE_RENDER_STATISTICS +#if ENABLE_CAMERA_STATISTICS + m_camera.debug_render(); +#endif // ENABLE_CAMERA_STATISTICS + wxGetApp().imgui()->render(); m_canvas->SwapBuffers(); @@ -1719,11 +1651,10 @@ void GLCanvas3D::select_all() void GLCanvas3D::deselect_all() { - m_selection.clear(); - m_selection.set_mode(Selection::Instance); + m_selection.remove_all(); wxGetApp().obj_manipul()->set_dirty(); m_gizmos.reset_all_states(); - m_gizmos.update_data(*this); + m_gizmos.update_data(); post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); } @@ -1779,7 +1710,7 @@ std::vector GLCanvas3D::load_object(const ModelObject& model_object, int ob instance_idxs.push_back(i); } } - return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_color_by, m_use_VBOs && m_initialized); + return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_color_by, m_initialized); } std::vector GLCanvas3D::load_object(const Model& model, int obj_idx) @@ -1797,7 +1728,7 @@ std::vector GLCanvas3D::load_object(const Model& model, int obj_idx) void GLCanvas3D::mirror_selection(Axis axis) { m_selection.mirror(axis); - do_mirror(); + do_mirror(L("Mirror Object")); wxGetApp().obj_manipul()->set_dirty(); } @@ -1818,14 +1749,14 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re struct ModelVolumeState { ModelVolumeState(const GLVolume *volume) : model_volume(nullptr), geometry_id(volume->geometry_id), volume_idx(-1) {} - ModelVolumeState(const ModelVolume *model_volume, const ModelID &instance_id, const GLVolume::CompositeID &composite_id) : + ModelVolumeState(const ModelVolume *model_volume, const ObjectID &instance_id, const GLVolume::CompositeID &composite_id) : model_volume(model_volume), geometry_id(std::make_pair(model_volume->id().id, instance_id.id)), composite_id(composite_id), volume_idx(-1) {} - ModelVolumeState(const ModelID &volume_id, const ModelID &instance_id) : + ModelVolumeState(const ObjectID &volume_id, const ObjectID &instance_id) : model_volume(nullptr), geometry_id(std::make_pair(volume_id.id, instance_id.id)), volume_idx(-1) {} bool new_geometry() const { return this->volume_idx == size_t(-1); } const ModelVolume *model_volume; - // ModelID of ModelVolume + ModelID of ModelInstance - // or timestamp of an SLAPrintObjectStep + ModelID of ModelInstance + // ObjectID of ModelVolume + ObjectID of ModelInstance + // or timestamp of an SLAPrintObjectStep + ObjectID of ModelInstance std::pair geometry_id; GLVolume::CompositeID composite_id; // Volume index in the new GLVolume vector. @@ -1854,240 +1785,247 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re PrinterTechnology printer_technology = m_process->current_printer_technology(); int volume_idx_wipe_tower_old = -1; - if (m_regenerate_volumes) - { - // Release invalidated volumes to conserve GPU memory in case of delayed refresh (see m_reload_delayed). - // First initialize model_volumes_new_sorted & model_instances_new_sorted. - for (int object_idx = 0; object_idx < (int)m_model->objects.size(); ++ object_idx) { - const ModelObject *model_object = m_model->objects[object_idx]; - for (int instance_idx = 0; instance_idx < (int)model_object->instances.size(); ++ instance_idx) { - const ModelInstance *model_instance = model_object->instances[instance_idx]; - for (int volume_idx = 0; volume_idx < (int)model_object->volumes.size(); ++ volume_idx) { - const ModelVolume *model_volume = model_object->volumes[volume_idx]; - model_volume_state.emplace_back(model_volume, model_instance->id(), GLVolume::CompositeID(object_idx, volume_idx, instance_idx)); - } + // Release invalidated volumes to conserve GPU memory in case of delayed refresh (see m_reload_delayed). + // First initialize model_volumes_new_sorted & model_instances_new_sorted. + for (int object_idx = 0; object_idx < (int)m_model->objects.size(); ++ object_idx) { + const ModelObject *model_object = m_model->objects[object_idx]; + for (int instance_idx = 0; instance_idx < (int)model_object->instances.size(); ++ instance_idx) { + const ModelInstance *model_instance = model_object->instances[instance_idx]; + for (int volume_idx = 0; volume_idx < (int)model_object->volumes.size(); ++ volume_idx) { + const ModelVolume *model_volume = model_object->volumes[volume_idx]; + model_volume_state.emplace_back(model_volume, model_instance->id(), GLVolume::CompositeID(object_idx, volume_idx, instance_idx)); } } - if (printer_technology == ptSLA) { - const SLAPrint *sla_print = this->sla_print(); - #ifndef NDEBUG - // Verify that the SLAPrint object is synchronized with m_model. - check_model_ids_equal(*m_model, sla_print->model()); - #endif /* NDEBUG */ - sla_support_state.reserve(sla_print->objects().size()); - for (const SLAPrintObject *print_object : sla_print->objects()) { - SLASupportState state; - for (size_t istep = 0; istep < sla_steps.size(); ++ istep) { - state.step[istep] = print_object->step_state_with_timestamp(sla_steps[istep]); - if (state.step[istep].state == PrintStateBase::DONE) { - if (! print_object->has_mesh(sla_steps[istep])) - // Consider the DONE step without a valid mesh as invalid for the purpose - // of mesh visualization. - state.step[istep].state = PrintStateBase::INVALID; - else - for (const ModelInstance *model_instance : print_object->model_object()->instances) - aux_volume_state.emplace_back(state.step[istep].timestamp, model_instance->id()); - } - } - sla_support_state.emplace_back(state); - } - } - std::sort(model_volume_state.begin(), model_volume_state.end(), model_volume_state_lower); - std::sort(aux_volume_state .begin(), aux_volume_state .end(), model_volume_state_lower); - // Release all ModelVolume based GLVolumes not found in the current Model. - for (size_t volume_id = 0; volume_id < m_volumes.volumes.size(); ++ volume_id) { - GLVolume *volume = m_volumes.volumes[volume_id]; - ModelVolumeState key(volume); - ModelVolumeState *mvs = nullptr; - if (volume->volume_idx() < 0) { - auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); - if (it != aux_volume_state.end() && it->geometry_id == key.geometry_id) - mvs = &(*it); - } else { - auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); - if (it != model_volume_state.end() && it->geometry_id == key.geometry_id) - mvs = &(*it); - } - // Emplace instance ID of the volume. Both the aux volumes and model volumes share the same instance ID. - // The wipe tower has its own wipe_tower_instance_id(). - if (m_selection.contains_volume(volume_id)) - instance_ids_selected.emplace_back(volume->geometry_id.second); - if (mvs == nullptr || force_full_scene_refresh) { - // This GLVolume will be released. - if (volume->is_wipe_tower) { - // There is only one wipe tower. - assert(volume_idx_wipe_tower_old == -1); - volume_idx_wipe_tower_old = (int)volume_id; - } - volume->release_geometry(); - if (! m_reload_delayed) - delete volume; - } else { - // This GLVolume will be reused. - volume->set_sla_shift_z(0.0); - map_glvolume_old_to_new[volume_id] = glvolumes_new.size(); - mvs->volume_idx = glvolumes_new.size(); - glvolumes_new.emplace_back(volume); - // Update color of the volume based on the current extruder. - if (mvs->model_volume != nullptr) { - int extruder_id = mvs->model_volume->extruder_id(); - if (extruder_id != -1) - volume->extruder_id = extruder_id; - - volume->is_modifier = !mvs->model_volume->is_model_part(); - volume->set_color_from_model_volume(mvs->model_volume); - - // updates volumes transformations - volume->set_instance_transformation(mvs->model_volume->get_object()->instances[mvs->composite_id.instance_id]->get_transformation()); - volume->set_volume_transformation(mvs->model_volume->get_transformation()); - } - } - } - sort_remove_duplicates(instance_ids_selected); } + if (printer_technology == ptSLA) { + const SLAPrint *sla_print = this->sla_print(); + #ifndef NDEBUG + // Verify that the SLAPrint object is synchronized with m_model. + check_model_ids_equal(*m_model, sla_print->model()); + #endif /* NDEBUG */ + sla_support_state.reserve(sla_print->objects().size()); + for (const SLAPrintObject *print_object : sla_print->objects()) { + SLASupportState state; + for (size_t istep = 0; istep < sla_steps.size(); ++ istep) { + state.step[istep] = print_object->step_state_with_timestamp(sla_steps[istep]); + if (state.step[istep].state == PrintStateBase::DONE) { + if (! print_object->has_mesh(sla_steps[istep])) + // Consider the DONE step without a valid mesh as invalid for the purpose + // of mesh visualization. + state.step[istep].state = PrintStateBase::INVALID; + else + for (const ModelInstance *model_instance : print_object->model_object()->instances) + // Only the instances, which are currently printable, will have the SLA support structures kept. + // The instances outside the print bed will have the GLVolumes of their support structures released. + if (model_instance->is_printable()) + aux_volume_state.emplace_back(state.step[istep].timestamp, model_instance->id()); + } + } + sla_support_state.emplace_back(state); + } + } + std::sort(model_volume_state.begin(), model_volume_state.end(), model_volume_state_lower); + std::sort(aux_volume_state .begin(), aux_volume_state .end(), model_volume_state_lower); + // Release all ModelVolume based GLVolumes not found in the current Model. + for (size_t volume_id = 0; volume_id < m_volumes.volumes.size(); ++ volume_id) { + GLVolume *volume = m_volumes.volumes[volume_id]; + ModelVolumeState key(volume); + ModelVolumeState *mvs = nullptr; + if (volume->volume_idx() < 0) { + auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); + if (it != aux_volume_state.end() && it->geometry_id == key.geometry_id) + // This can be an SLA support structure that should not be rendered (in case someone used undo + // to revert to before it was generated). We only reuse the volume if that's not the case. + if (m_model->objects[volume->composite_id.object_id]->sla_points_status != sla::PointsStatus::NoPoints) + mvs = &(*it); + } else { + auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); + if (it != model_volume_state.end() && it->geometry_id == key.geometry_id) + mvs = &(*it); + } + // Emplace instance ID of the volume. Both the aux volumes and model volumes share the same instance ID. + // The wipe tower has its own wipe_tower_instance_id(). + if (m_selection.contains_volume(volume_id)) + instance_ids_selected.emplace_back(volume->geometry_id.second); + if (mvs == nullptr || force_full_scene_refresh) { + // This GLVolume will be released. + if (volume->is_wipe_tower) { + // There is only one wipe tower. + assert(volume_idx_wipe_tower_old == -1); + volume_idx_wipe_tower_old = (int)volume_id; + } + if (! m_reload_delayed) + delete volume; + } else { + // This GLVolume will be reused. + volume->set_sla_shift_z(0.0); + map_glvolume_old_to_new[volume_id] = glvolumes_new.size(); + mvs->volume_idx = glvolumes_new.size(); + glvolumes_new.emplace_back(volume); + // Update color of the volume based on the current extruder. + if (mvs->model_volume != nullptr) { + int extruder_id = mvs->model_volume->extruder_id(); + if (extruder_id != -1) + volume->extruder_id = extruder_id; + + volume->is_modifier = !mvs->model_volume->is_model_part(); + volume->set_color_from_model_volume(mvs->model_volume); + + // updates volumes transformations + volume->set_instance_transformation(mvs->model_volume->get_object()->instances[mvs->composite_id.instance_id]->get_transformation()); + volume->set_volume_transformation(mvs->model_volume->get_transformation()); + } + } + } + sort_remove_duplicates(instance_ids_selected); if (m_reload_delayed) return; bool update_object_list = false; - if (m_regenerate_volumes) - { - if (m_volumes.volumes != glvolumes_new) - update_object_list = true; - m_volumes.volumes = std::move(glvolumes_new); - for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++ obj_idx) { - const ModelObject &model_object = *m_model->objects[obj_idx]; - for (int volume_idx = 0; volume_idx < (int)model_object.volumes.size(); ++ volume_idx) { - const ModelVolume &model_volume = *model_object.volumes[volume_idx]; - for (int instance_idx = 0; instance_idx < (int)model_object.instances.size(); ++ instance_idx) { - const ModelInstance &model_instance = *model_object.instances[instance_idx]; - ModelVolumeState key(model_volume.id(), model_instance.id()); - auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); - assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id); - if (it->new_geometry()) { - // New volume. - m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_color_by, m_use_VBOs && m_initialized); - m_volumes.volumes.back()->geometry_id = key.geometry_id; + if (m_volumes.volumes != glvolumes_new) + update_object_list = true; + m_volumes.volumes = std::move(glvolumes_new); + for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++ obj_idx) { + const ModelObject &model_object = *m_model->objects[obj_idx]; + for (int volume_idx = 0; volume_idx < (int)model_object.volumes.size(); ++ volume_idx) { + const ModelVolume &model_volume = *model_object.volumes[volume_idx]; + for (int instance_idx = 0; instance_idx < (int)model_object.instances.size(); ++ instance_idx) { + const ModelInstance &model_instance = *model_object.instances[instance_idx]; + ModelVolumeState key(model_volume.id(), model_instance.id()); + auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); + assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id); + if (it->new_geometry()) { + // New volume. + m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_color_by, m_initialized); + m_volumes.volumes.back()->geometry_id = key.geometry_id; + update_object_list = true; + } else { + // Recycling an old GLVolume. + GLVolume &existing_volume = *m_volumes.volumes[it->volume_idx]; + assert(existing_volume.geometry_id == key.geometry_id); + // Update the Object/Volume/Instance indices into the current Model. + if (existing_volume.composite_id != it->composite_id) { + existing_volume.composite_id = it->composite_id; update_object_list = true; - } else { - // Recycling an old GLVolume. - GLVolume &existing_volume = *m_volumes.volumes[it->volume_idx]; - assert(existing_volume.geometry_id == key.geometry_id); - // Update the Object/Volume/Instance indices into the current Model. - if (existing_volume.composite_id != it->composite_id) { - existing_volume.composite_id = it->composite_id; - update_object_list = true; - } - } + } } } } - if (printer_technology == ptSLA) { - size_t idx = 0; - const SLAPrint *sla_print = this->sla_print(); - std::vector shift_zs(m_model->objects.size(), 0); - double relative_correction_z = sla_print->relative_correction().z(); - if (relative_correction_z <= EPSILON) - relative_correction_z = 1.; - for (const SLAPrintObject *print_object : sla_print->objects()) { - SLASupportState &state = sla_support_state[idx ++]; - const ModelObject *model_object = print_object->model_object(); - // Find an index of the ModelObject - int object_idx; - if (std::all_of(state.step.begin(), state.step.end(), [](const PrintStateBase::StateWithTimeStamp &state){ return state.state != PrintStateBase::DONE; })) - continue; - // There may be new SLA volumes added to the scene for this print_object. - // Find the object index of this print_object in the Model::objects list. - auto it = std::find(sla_print->model().objects.begin(), sla_print->model().objects.end(), model_object); - assert(it != sla_print->model().objects.end()); - object_idx = it - sla_print->model().objects.begin(); - // Cache the Z offset to be applied to all volumes with this object_idx. - shift_zs[object_idx] = print_object->get_current_elevation() / relative_correction_z; - // Collect indices of this print_object's instances, for which the SLA support meshes are to be added to the scene. - // pairs of - std::vector> instances[std::tuple_size::value]; - for (size_t print_instance_idx = 0; print_instance_idx < print_object->instances().size(); ++ print_instance_idx) { - const SLAPrintObject::Instance &instance = print_object->instances()[print_instance_idx]; - // Find index of ModelInstance corresponding to this SLAPrintObject::Instance. - auto it = std::find_if(model_object->instances.begin(), model_object->instances.end(), - [&instance](const ModelInstance *mi) { return mi->id() == instance.instance_id; }); - assert(it != model_object->instances.end()); - int instance_idx = it - model_object->instances.begin(); - for (size_t istep = 0; istep < sla_steps.size(); ++ istep) - if (state.step[istep].state == PrintStateBase::DONE) { - ModelVolumeState key(state.step[istep].timestamp, instance.instance_id.id); - auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); - assert(it != aux_volume_state.end() && it->geometry_id == key.geometry_id); - if (it->new_geometry()) + } + if (printer_technology == ptSLA) { + size_t idx = 0; + const SLAPrint *sla_print = this->sla_print(); + std::vector shift_zs(m_model->objects.size(), 0); + double relative_correction_z = sla_print->relative_correction().z(); + if (relative_correction_z <= EPSILON) + relative_correction_z = 1.; + for (const SLAPrintObject *print_object : sla_print->objects()) { + SLASupportState &state = sla_support_state[idx ++]; + const ModelObject *model_object = print_object->model_object(); + // Find an index of the ModelObject + int object_idx; + if (std::all_of(state.step.begin(), state.step.end(), [](const PrintStateBase::StateWithTimeStamp &state){ return state.state != PrintStateBase::DONE; })) + continue; + // There may be new SLA volumes added to the scene for this print_object. + // Find the object index of this print_object in the Model::objects list. + auto it = std::find(sla_print->model().objects.begin(), sla_print->model().objects.end(), model_object); + assert(it != sla_print->model().objects.end()); + object_idx = it - sla_print->model().objects.begin(); + // Cache the Z offset to be applied to all volumes with this object_idx. + shift_zs[object_idx] = print_object->get_current_elevation() / relative_correction_z; + // Collect indices of this print_object's instances, for which the SLA support meshes are to be added to the scene. + // pairs of + std::vector> instances[std::tuple_size::value]; + for (size_t print_instance_idx = 0; print_instance_idx < print_object->instances().size(); ++ print_instance_idx) { + const SLAPrintObject::Instance &instance = print_object->instances()[print_instance_idx]; + // Find index of ModelInstance corresponding to this SLAPrintObject::Instance. + auto it = std::find_if(model_object->instances.begin(), model_object->instances.end(), + [&instance](const ModelInstance *mi) { return mi->id() == instance.instance_id; }); + assert(it != model_object->instances.end()); + int instance_idx = it - model_object->instances.begin(); + for (size_t istep = 0; istep < sla_steps.size(); ++ istep) + if (state.step[istep].state == PrintStateBase::DONE) { + ModelVolumeState key(state.step[istep].timestamp, instance.instance_id.id); + auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); + assert(it != aux_volume_state.end() && it->geometry_id == key.geometry_id); + if (it->new_geometry()) { + // This can be an SLA support structure that should not be rendered (in case someone used undo + // to revert to before it was generated). If that's the case, we should not generate anything. + if (model_object->sla_points_status != sla::PointsStatus::NoPoints) instances[istep].emplace_back(std::pair(instance_idx, print_instance_idx)); else - // Recycling an old GLVolume. Update the Object/Instance indices into the current Model. - m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx); + shift_zs[object_idx] = 0.; } - } - - // stores the current volumes count - size_t volumes_count = m_volumes.volumes.size(); - - for (size_t istep = 0; istep < sla_steps.size(); ++istep) - if (!instances[istep].empty()) - m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp, m_use_VBOs && m_initialized); + else { + // Recycling an old GLVolume. Update the Object/Instance indices into the current Model. + m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx); + m_volumes.volumes[it->volume_idx]->set_instance_transformation(model_object->instances[instance_idx]->get_transformation()); + } + } } - // Shift-up all volumes of the object so that it has the right elevation with respect to the print bed - for (GLVolume* volume : m_volumes.volumes) + // stores the current volumes count + size_t volumes_count = m_volumes.volumes.size(); + + for (size_t istep = 0; istep < sla_steps.size(); ++istep) + if (!instances[istep].empty()) + m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp, m_initialized); + } + + // Shift-up all volumes of the object so that it has the right elevation with respect to the print bed + for (GLVolume* volume : m_volumes.volumes) + if (volume->object_idx() < m_model->objects.size() && m_model->objects[volume->object_idx()]->instances[volume->instance_idx()]->is_printable()) volume->set_sla_shift_z(shift_zs[volume->object_idx()]); - } + } - if (printer_technology == ptFFF && m_config->has("nozzle_diameter")) + if (printer_technology == ptFFF && m_config->has("nozzle_diameter")) + { + // Should the wipe tower be visualized ? + unsigned int extruders_count = (unsigned int)dynamic_cast(m_config->option("nozzle_diameter"))->values.size(); + + bool wt = dynamic_cast(m_config->option("wipe_tower"))->value; + bool co = dynamic_cast(m_config->option("complete_objects"))->value; + + if ((extruders_count > 1) && wt && !co) { - // Should the wipe tower be visualized ? - unsigned int extruders_count = (unsigned int)dynamic_cast(m_config->option("nozzle_diameter"))->values.size(); + // Height of a print (Show at least a slab) + double height = std::max(m_model->bounding_box().max(2), 10.0); - bool semm = dynamic_cast(m_config->option("single_extruder_multi_material"))->value; - bool wt = dynamic_cast(m_config->option("wipe_tower"))->value; - bool co = dynamic_cast(m_config->option("complete_objects"))->value; + float x = dynamic_cast(m_config->option("wipe_tower_x"))->value; + float y = dynamic_cast(m_config->option("wipe_tower_y"))->value; + float w = dynamic_cast(m_config->option("wipe_tower_width"))->value; + float a = dynamic_cast(m_config->option("wipe_tower_rotation_angle"))->value; - if ((extruders_count > 1) && semm && wt && !co) - { - // Height of a print (Show at least a slab) - double height = std::max(m_model->bounding_box().max(2), 10.0); + const Print *print = m_process->fff_print(); + float depth = print->get_wipe_tower_depth(); - float x = dynamic_cast(m_config->option("wipe_tower_x"))->value; - float y = dynamic_cast(m_config->option("wipe_tower_y"))->value; - float w = dynamic_cast(m_config->option("wipe_tower_width"))->value; - float a = dynamic_cast(m_config->option("wipe_tower_rotation_angle"))->value; + // Calculate wipe tower brim spacing. + const DynamicPrintConfig &print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; + double layer_height = print_config.opt_float("layer_height"); + double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height); + float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4); - const Print *print = m_process->fff_print(); - float depth = print->get_wipe_tower_depth(); - - // Calculate wipe tower brim spacing. - const DynamicPrintConfig &print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; - double layer_height = print_config.opt_float("layer_height"); - double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height); - float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4); - - if (!print->is_step_done(psWipeTower)) - depth = (900.f/w) * (float)(extruders_count - 1) ; - int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( - 1000, x, y, w, depth, (float)height, a, m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower), - brim_spacing * 4.5f); - if (volume_idx_wipe_tower_old != -1) - map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new; - } + if (!print->is_step_done(psWipeTower)) + depth = (900.f/w) * (float)(extruders_count - 1); + int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( + 1000, x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), + brim_spacing * 4.5f, m_initialized); + if (volume_idx_wipe_tower_old != -1) + map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new; } + } - update_volumes_colors_by_extruder(); - // Update selection indices based on the old/new GLVolumeCollection. - if (m_selection.get_mode() == Selection::Instance) - m_selection.instances_changed(instance_ids_selected); - else - m_selection.volumes_changed(map_glvolume_old_to_new); - } + update_volumes_colors_by_extruder(); + // Update selection indices based on the old/new GLVolumeCollection. + if (m_selection.get_mode() == Selection::Instance) + m_selection.instances_changed(instance_ids_selected); + else + m_selection.volumes_changed(map_glvolume_old_to_new); - m_gizmos.update_data(*this); - m_gizmos.refresh_on_off_state(m_selection); + m_gizmos.update_data(); + m_gizmos.refresh_on_off_state(); // Update the toolbar if (update_object_list) @@ -2127,9 +2065,6 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, false)); } - // restore to default value - m_regenerate_volumes = true; - m_camera.set_scene_box(scene_bounding_box()); if (m_selection.is_empty()) @@ -2297,13 +2232,17 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt) if (!m_initialized) return; - m_dirty |= m_toolbar.update_items_state(); + m_dirty |= m_main_toolbar.update_items_state(); + m_dirty |= m_undoredo_toolbar.update_items_state(); m_dirty |= m_view_toolbar.update_items_state(); if (!m_dirty) return; _refresh_if_shown_on_screen(); + + if (m_keep_dirty) + m_dirty = true; } void GLCanvas3D::on_char(wxKeyEvent& evt) @@ -2321,9 +2260,17 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) return; } - if (m_gizmos.on_char(evt, *this)) + if ((keyCode == WXK_ESCAPE) && _deactivate_undo_redo_toolbar_items()) return; + if (m_gizmos.on_char(evt)) + { + // FIXME: Without the following call to render(), the gimgui dialogs are not shown the first time the user tries to open them using the keyboard shortcuts + // (it looks like as if 2 render calls are needed before they show up) + render(); + return; + } + //#ifdef __APPLE__ // ctrlMask |= wxMOD_RAW_CONTROL; //#endif /* __APPLE__ */ @@ -2353,12 +2300,28 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) #endif /* __APPLE__ */ post_event(SimpleEvent(EVT_GLTOOLBAR_PASTE)); break; + + #ifdef __APPLE__ - case WXK_BACK: // the low cost Apple solutions are not equipped with a Delete key, use Backspace instead. + case 'y': + case 'Y': #else /* __APPLE__ */ - case WXK_DELETE: + case WXK_CONTROL_Y: #endif /* __APPLE__ */ - post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); break; + post_event(SimpleEvent(EVT_GLCANVAS_REDO)); + break; +#ifdef __APPLE__ + case 'z': + case 'Z': +#else /* __APPLE__ */ + case WXK_CONTROL_Z: +#endif /* __APPLE__ */ + post_event(SimpleEvent(EVT_GLCANVAS_UNDO)); + break; + + case WXK_BACK: + case WXK_DELETE: + post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); break; default: evt.Skip(); } } else if (evt.HasModifiers()) { @@ -2366,14 +2329,10 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) } else { switch (keyCode) { -#ifdef __APPLE__ - case WXK_BACK: // the low cost Apple solutions are not equipped with a Delete key, use Backspace instead. -#else /* __APPLE__ */ + case WXK_BACK: case WXK_DELETE: -#endif /* __APPLE__ */ post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); break; - case WXK_ESCAPE: { deselect_all(); break; } case '0': { select_view("iso"); break; } case '1': { select_view("top"); break; } @@ -2382,17 +2341,37 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) case '4': { select_view("rear"); break; } case '5': { select_view("left"); break; } case '6': { select_view("right"); break; } - case '+': { post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, +1)); break; } - case '-': { post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, -1)); break; } + case '+': { + if (dynamic_cast(m_canvas->GetParent()) != nullptr) + post_event(wxKeyEvent(EVT_GLCANVAS_EDIT_COLOR_CHANGE, evt)); + else + post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, +1)); + break; } + case '-': { + if (dynamic_cast(m_canvas->GetParent()) != nullptr) + post_event(wxKeyEvent(EVT_GLCANVAS_EDIT_COLOR_CHANGE, evt)); + else + post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, -1)); + break; } case '?': { post_event(SimpleEvent(EVT_GLCANVAS_QUESTION_MARK)); break; } case 'A': case 'a': { post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE)); break; } case 'B': case 'b': { zoom_to_bed(); break; } case 'I': - case 'i': { set_camera_zoom(1.0f); break; } + case 'i': { set_camera_zoom(1.0); break; } + case 'K': + case 'k': { m_camera.select_next_type(); m_dirty = true; break; } case 'O': - case 'o': { set_camera_zoom(-1.0f); break; } + case 'o': { set_camera_zoom(-1.0); break; } +#if ENABLE_RENDER_PICKING_PASS + case 'T': + case 't': { + m_show_picking_texture = !m_show_picking_texture; + m_dirty = true; + break; + } +#endif // ENABLE_RENDER_PICKING_PASS case 'Z': case 'z': { m_selection.is_empty() ? zoom_to_volumes() : zoom_to_selection(); break; } default: { evt.Skip(); break; } @@ -2410,7 +2389,7 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) } else { - if (!m_gizmos.on_key(evt, *this)) + if (!m_gizmos.on_key(evt)) { if (evt.GetEventType() == wxEVT_KEY_UP) { if (m_tab_down && keyCode == WXK_TAB && !evt.HasAnyModifiers()) { @@ -2463,6 +2442,15 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) } else if (keyCode == WXK_CONTROL) m_dirty = true; + // DoubleSlider navigation in Preview + else if (keyCode == WXK_LEFT || + keyCode == WXK_RIGHT || + keyCode == WXK_UP || + keyCode == WXK_DOWN ) + { + if (dynamic_cast(m_canvas->GetParent()) != nullptr) + post_event(wxKeyEvent(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, evt)); + } } } } @@ -2491,6 +2479,13 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) evt.SetY(evt.GetY() * scale); #endif +#ifdef __WXMSW__ + // For some reason the Idle event is not being generated after the mouse scroll event in case of scrolling with the two fingers on the touch pad, + // if the event is not allowed to be passed further. + // https://github.com/prusa3d/PrusaSlicer/issues/2750 + evt.Skip(); +#endif /* __WXMSW__ */ + // Performs layers editing updates, if enabled if (is_layers_editing_enabled()) { @@ -2504,19 +2499,18 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) m_layers_editing.band_width = std::max(std::min(m_layers_editing.band_width * (1.0f + 0.1f * (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta()), 10.0f), 1.5f); if (m_canvas != nullptr) m_canvas->Refresh(); - + return; } } } // Inform gizmos about the event so they have the opportunity to react. - if (m_gizmos.on_mouse_wheel(evt, *this)) + if (m_gizmos.on_mouse_wheel(evt)) return; // Calculate the zoom delta and apply it to the current zoom factor - float zoom = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta(); - set_camera_zoom(zoom); + set_camera_zoom((double)evt.GetWheelRotation() / (double)evt.GetWheelDelta()); } void GLCanvas3D::on_timer(wxTimerEvent& evt) @@ -2624,7 +2618,15 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) #endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ } - if (m_toolbar.on_mouse(evt, *this)) + if (m_main_toolbar.on_mouse(evt, *this)) + { + if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) + mouse_up_cleanup(); + m_mouse.set_start_position_3D_as_invalid(); + return; + } + + if (m_undoredo_toolbar.on_mouse(evt, *this)) { if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) mouse_up_cleanup(); @@ -2640,7 +2642,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) return; } - if (m_gizmos.on_mouse(evt, *this)) + if (m_gizmos.on_mouse(evt)) { if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) mouse_up_cleanup(); @@ -2692,12 +2694,17 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } else if (evt.Leaving()) { + _deactivate_undo_redo_toolbar_items(); + // to remove hover on objects when the mouse goes out of this canvas m_mouse.position = Vec2d(-1.0, -1.0); m_dirty = true; } - else if (evt.LeftDown() || evt.RightDown()) + else if (evt.LeftDown() || evt.RightDown() || evt.MiddleDown()) { + if (_deactivate_undo_redo_toolbar_items()) + return; + // If user pressed left or right button we first check whether this happened // on a volume or not. m_layers_editing.state = LayersEditing::Unknown; @@ -2762,9 +2769,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (m_selection.is_empty()) m_gizmos.reset_all_states(); else - m_gizmos.refresh_on_off_state(m_selection); + m_gizmos.refresh_on_off_state(); - m_gizmos.update_data(*this); + m_gizmos.update_data(); post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); m_dirty = true; } @@ -2834,7 +2841,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } } - m_regenerate_volumes = false; m_selection.translate(cur_pos - m_mouse.drag.start_position_3D); wxGetApp().obj_manipul()->set_dirty(); m_dirty = true; @@ -2894,10 +2900,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } else if ((m_mouse.drag.move_volume_idx != -1) && m_mouse.dragging) { - m_regenerate_volumes = false; - do_move(); + do_move(L("Move Object")); wxGetApp().obj_manipul()->set_dirty(); - // Let the platter know that the dragging finished, so a delayed refresh + // Let the plater know that the dragging finished, so a delayed refresh // of the scene with the background processing data should be performed. post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); } @@ -2931,10 +2936,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) && m_gizmos.get_current_type() != GLGizmosManager::SlaSupports) // disable context menu when the gizmo is open { // forces the selection of the volume - m_selection.add(volume_idx); - m_gizmos.refresh_on_off_state(m_selection); + /* m_selection.add(volume_idx); // #et_FIXME_if_needed + * To avoid extra "Add-Selection" snapshots, + * call add() with check_for_already_contained=true + * */ + m_selection.add(volume_idx, true, true); + m_gizmos.refresh_on_off_state(); post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); - m_gizmos.update_data(*this); + m_gizmos.update_data(); wxGetApp().obj_manipul()->set_dirty(); // forces a frame render to update the view before the context menu is shown render(); @@ -2960,7 +2969,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) tooltip = m_gizmos.get_tooltip(); if (tooltip.empty()) - tooltip = m_toolbar.get_tooltip(); + tooltip = m_main_toolbar.get_tooltip(); + + if (tooltip.empty()) + tooltip = m_undoredo_toolbar.get_tooltip(); if (tooltip.empty()) tooltip = m_view_toolbar.get_tooltip(); @@ -3054,11 +3066,14 @@ void GLCanvas3D::set_tooltip(const std::string& tooltip) const } -void GLCanvas3D::do_move() +void GLCanvas3D::do_move(const std::string& snapshot_type) { if (m_model == nullptr) return; + if (!snapshot_type.empty()) + wxGetApp().plater()->take_snapshot(_(snapshot_type)); + std::set> done; // keeps track of modified instances bool object_moved = false; Vec3d wipe_tower_origin = Vec3d::Zero(); @@ -3109,13 +3124,18 @@ void GLCanvas3D::do_move() if (wipe_tower_origin != Vec3d::Zero()) post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_MOVED, std::move(wipe_tower_origin))); + + m_dirty = true; } -void GLCanvas3D::do_rotate() +void GLCanvas3D::do_rotate(const std::string& snapshot_type) { if (m_model == nullptr) return; + if (!snapshot_type.empty()) + wxGetApp().plater()->take_snapshot(_(snapshot_type)); + std::set> done; // keeps track of modified instances Selection::EMode selection_mode = m_selection.get_mode(); @@ -3164,13 +3184,18 @@ void GLCanvas3D::do_rotate() if (!done.empty()) post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED)); + + m_dirty = true; } -void GLCanvas3D::do_scale() +void GLCanvas3D::do_scale(const std::string& snapshot_type) { if (m_model == nullptr) return; + if (!snapshot_type.empty()) + wxGetApp().plater()->take_snapshot(_(snapshot_type)); + std::set> done; // keeps track of modified instances Selection::EMode selection_mode = m_selection.get_mode(); @@ -3216,18 +3241,27 @@ void GLCanvas3D::do_scale() if (!done.empty()) post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED)); + + m_dirty = true; } -void GLCanvas3D::do_flatten() +void GLCanvas3D::do_flatten(const Vec3d& normal, const std::string& snapshot_type) { - do_rotate(); + if (!snapshot_type.empty()) + wxGetApp().plater()->take_snapshot(_(snapshot_type)); + + m_selection.flattening_rotate(normal); + do_rotate(""); // avoid taking another snapshot } -void GLCanvas3D::do_mirror() +void GLCanvas3D::do_mirror(const std::string& snapshot_type) { if (m_model == nullptr) return; + if (!snapshot_type.empty()) + wxGetApp().plater()->take_snapshot(_(snapshot_type)); + std::set> done; // keeps track of modified instances Selection::EMode selection_mode = m_selection.get_mode(); @@ -3266,30 +3300,22 @@ void GLCanvas3D::do_mirror() } post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + + m_dirty = true; } -void GLCanvas3D::set_camera_zoom(float zoom) +void GLCanvas3D::set_camera_zoom(double zoom) { - zoom = std::max(std::min(zoom, 4.0f), -4.0f) / 10.0f; - zoom = m_camera.zoom / (1.0f - zoom); - - // Don't allow to zoom too far outside the scene. - float zoom_min = _get_zoom_to_bounding_box_factor(_max_bounding_box()); - if (zoom_min > 0.0f) - zoom = std::max(zoom, zoom_min * 0.7f); - - // Don't allow to zoom too close to the scene. - zoom = std::min(zoom, 100.0f); - - m_camera.zoom = zoom; - _refresh_if_shown_on_screen(); + const Size& cnv_size = get_canvas_size(); + m_camera.set_zoom(zoom, _max_bounding_box(false, false), cnv_size.get_width(), cnv_size.get_height()); + m_dirty = true; } void GLCanvas3D::update_gizmos_on_off_state() { set_as_dirty(); - m_gizmos.update_data(*this); - m_gizmos.refresh_on_off_state(get_selection()); + m_gizmos.update_data(); + m_gizmos.refresh_on_off_state(); } void GLCanvas3D::handle_sidebar_focus_event(const std::string& opt_key, bool focus_on) @@ -3303,8 +3329,17 @@ void GLCanvas3D::handle_sidebar_focus_event(const std::string& opt_key, bool foc } } +void GLCanvas3D::handle_layers_data_focus_event(const t_layer_height_range range, const EditorType type) +{ + std::string field = "layer_" + std::to_string(type) + "_" + std::to_string(range.first) + "_" + std::to_string(range.second); + handle_sidebar_focus_event(field, true); +} + void GLCanvas3D::update_ui_from_settings() { + m_camera.set_type(wxGetApp().app_config->get("use_perspective_camera")); + m_dirty = true; + #if ENABLE_RETINA_GL const float orig_scaling = m_retina_helper->get_scale_factor(); @@ -3316,8 +3351,7 @@ void GLCanvas3D::update_ui_from_settings() if (new_scaling != orig_scaling) { BOOST_LOG_TRIVIAL(debug) << "GLCanvas3D: Scaling factor: " << new_scaling; - m_camera.zoom /= orig_scaling; - m_camera.zoom *= new_scaling; + m_camera.set_zoom(m_camera.get_zoom() * new_scaling / orig_scaling); _refresh_if_shown_on_screen(); } #endif @@ -3325,36 +3359,24 @@ void GLCanvas3D::update_ui_from_settings() -arr::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const +GLCanvas3D::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const { - arr::WipeTowerInfo wti; + WipeTowerInfo wti; + for (const GLVolume* vol : m_volumes.volumes) { if (vol->is_wipe_tower) { - wti.is_wipe_tower = true; - wti.pos = Vec2d(m_config->opt_float("wipe_tower_x"), + wti.m_pos = Vec2d(m_config->opt_float("wipe_tower_x"), m_config->opt_float("wipe_tower_y")); - wti.rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle"); - const BoundingBoxf3& bb = vol->bounding_box; - wti.bb_size = Vec2d(bb.size()(0), bb.size()(1)); + wti.m_rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle"); + const BoundingBoxf3& bb = vol->bounding_box(); + wti.m_bb_size = Vec2d(bb.size().x(), bb.size().y()); break; } } + return wti; } - -void GLCanvas3D::arrange_wipe_tower(const arr::WipeTowerInfo& wti) const -{ - if (wti.is_wipe_tower) { - DynamicPrintConfig cfg; - cfg.opt("wipe_tower_x", true)->value = wti.pos(0); - cfg.opt("wipe_tower_y", true)->value = wti.pos(1); - cfg.opt("wipe_tower_rotation_angle", true)->value = (180./M_PI) * wti.rotation; - wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); - } -} - - Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos) { float z0 = 0.0f; @@ -3364,7 +3386,7 @@ Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos) double GLCanvas3D::get_size_proportional_to_max_bed_size(double factor) const { - return factor * m_bed.get_bounding_box().max_size(); + return factor * m_bed.get_bounding_box(false).max_size(); } void GLCanvas3D::set_cursor(ECursorType type) @@ -3386,21 +3408,73 @@ void GLCanvas3D::msw_rescale() m_warning_texture.msw_rescale(*this); } +bool GLCanvas3D::has_toolpaths_to_export() const +{ + return m_volumes.has_toolpaths_to_export(); +} + +void GLCanvas3D::export_toolpaths_to_obj(const char* filename) const +{ + m_volumes.export_toolpaths_to_obj(filename); +} + bool GLCanvas3D::_is_shown_on_screen() const { return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false; } -bool GLCanvas3D::_init_toolbar() +// Getter for the const char*[] +static bool string_getter(const bool is_undo, int idx, const char** out_text) { - if (!m_toolbar.is_enabled()) - return true; + return wxGetApp().plater()->undo_redo_string_getter(is_undo, idx, out_text); +} -#if !ENABLE_SVG_ICONS - ItemsIconsTexture::Metadata icons_data; - icons_data.filename = "toolbar.png"; - icons_data.icon_size = 37; -#endif // !ENABLE_SVG_ICONS +void GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) +{ + const wxString& stack_name = _(is_undo ? L("Undo") : L("Redo")); + ImGuiWrapper* imgui = wxGetApp().imgui(); + + const float x = pos_x * (float)get_camera().get_zoom() + 0.5f * (float)get_canvas_size().get_width(); + imgui->set_next_window_pos(x, m_undoredo_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); + imgui->set_next_window_bg_alpha(0.5f); + imgui->begin(wxString::Format(_(L("%s Stack")), stack_name), + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + int hovered = m_imgui_undo_redo_hovered_pos; + int selected = -1; + float em = static_cast(wxGetApp().em_unit()); +#if ENABLE_RETINA_GL + em *= m_retina_helper->get_scale_factor(); +#endif + + if (imgui->undo_redo_list(ImVec2(18 * em, 26 * em), is_undo, &string_getter, hovered, selected)) + m_imgui_undo_redo_hovered_pos = hovered; + else + m_imgui_undo_redo_hovered_pos = -1; + + if (selected >= 0) + is_undo ? wxGetApp().plater()->undo_to(selected) : wxGetApp().plater()->redo_to(selected); + + imgui->text(wxString::Format(_(L("%s %d Action")), stack_name, hovered + 1)); + + imgui->end(); +} + +bool GLCanvas3D::_init_toolbars() +{ + if (!_init_main_toolbar()) + return false; + + if (!_init_undoredo_toolbar()) + return false; + + return true; +} + +bool GLCanvas3D::_init_main_toolbar() +{ + if (!m_main_toolbar.is_enabled()) + return true; BackgroundTexture::Metadata background_data; background_data.filename = "toolbar_background.png"; @@ -3409,162 +3483,237 @@ bool GLCanvas3D::_init_toolbar() background_data.right = 16; background_data.bottom = 16; -#if ENABLE_SVG_ICONS - if (!m_toolbar.init(background_data)) -#else - if (!m_toolbar.init(icons_data, background_data)) -#endif // ENABLE_SVG_ICONS + if (!m_main_toolbar.init(background_data)) { // unable to init the toolbar texture, disable it - m_toolbar.set_enabled(false); + m_main_toolbar.set_enabled(false); return true; } -// m_toolbar.set_layout_type(GLToolbar::Layout::Vertical); - m_toolbar.set_layout_type(GLToolbar::Layout::Horizontal); - m_toolbar.set_layout_orientation(GLToolbar::Layout::Top); - m_toolbar.set_border(5.0f); - m_toolbar.set_separator_size(5); - m_toolbar.set_gap_size(2); +// m_main_toolbar.set_layout_type(GLToolbar::Layout::Vertical); + m_main_toolbar.set_layout_type(GLToolbar::Layout::Horizontal); + m_main_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Right); + m_main_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top); + m_main_toolbar.set_border(5.0f); + m_main_toolbar.set_separator_size(5); + m_main_toolbar.set_gap_size(2); GLToolbarItem::Data item; item.name = "add"; -#if ENABLE_SVG_ICONS item.icon_filename = "add.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Add...")) + " [" + GUI::shortkey_ctrl_prefix() + "I]"; item.sprite_id = 0; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD)); }; - if (!m_toolbar.add_item(item)) + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD)); }; + if (!m_main_toolbar.add_item(item)) return false; item.name = "delete"; -#if ENABLE_SVG_ICONS item.icon_filename = "remove.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Delete")) + " [Del]"; item.sprite_id = 1; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE)); }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_delete(); }; - if (!m_toolbar.add_item(item)) + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE)); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_delete(); }; + if (!m_main_toolbar.add_item(item)) return false; item.name = "deleteall"; -#if ENABLE_SVG_ICONS item.icon_filename = "delete_all.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Delete all")) + " [" + GUI::shortkey_ctrl_prefix() + "Del]"; item.sprite_id = 2; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_delete_all(); }; - if (!m_toolbar.add_item(item)) + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_delete_all(); }; + if (!m_main_toolbar.add_item(item)) return false; item.name = "arrange"; -#if ENABLE_SVG_ICONS item.icon_filename = "arrange.svg"; -#endif // ENABLE_SVG_ICONS - item.tooltip = _utf8(L("Arrange")) + " [A]"; + item.tooltip = _utf8(L("Arrange")) + " [A]\n" + _utf8(L("Arrange selection")) + " [Shift+A]"; item.sprite_id = 3; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_arrange(); }; - if (!m_toolbar.add_item(item)) + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); }; + if (!m_main_toolbar.add_item(item)) return false; - if (!m_toolbar.add_separator()) + if (!m_main_toolbar.add_separator()) return false; item.name = "copy"; -#if ENABLE_SVG_ICONS item.icon_filename = "copy.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Copy")) + " [" + GUI::shortkey_ctrl_prefix() + "C]"; item.sprite_id = 4; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_copy(); }; - if (!m_toolbar.add_item(item)) + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_copy_to_clipboard(); }; + if (!m_main_toolbar.add_item(item)) return false; item.name = "paste"; -#if ENABLE_SVG_ICONS item.icon_filename = "paste.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Paste")) + " [" + GUI::shortkey_ctrl_prefix() + "V]"; item.sprite_id = 5; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_paste(); }; - if (!m_toolbar.add_item(item)) + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_paste_from_clipboard(); }; + if (!m_main_toolbar.add_item(item)) return false; - if (!m_toolbar.add_separator()) + if (!m_main_toolbar.add_separator()) return false; item.name = "more"; -#if ENABLE_SVG_ICONS item.icon_filename = "instance_add.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Add instance")) + " [+]"; item.sprite_id = 6; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_MORE)); }; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_MORE)); }; item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_increase_instances(); }; - if (!m_toolbar.add_item(item)) + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_increase_instances(); }; + + if (!m_main_toolbar.add_item(item)) return false; item.name = "fewer"; -#if ENABLE_SVG_ICONS item.icon_filename = "instance_remove.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Remove instance")) + " [-]"; item.sprite_id = 7; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); }; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); }; item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_decrease_instances(); }; - if (!m_toolbar.add_item(item)) + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_decrease_instances(); }; + if (!m_main_toolbar.add_item(item)) return false; - if (!m_toolbar.add_separator()) + if (!m_main_toolbar.add_separator()) return false; item.name = "splitobjects"; -#if ENABLE_SVG_ICONS item.icon_filename = "split_objects.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Split to objects")); item.sprite_id = 8; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS)); }; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS)); }; item.visibility_callback = GLToolbarItem::Default_Visibility_Callback; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_split_to_objects(); }; - if (!m_toolbar.add_item(item)) + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_objects(); }; + if (!m_main_toolbar.add_item(item)) return false; item.name = "splitvolumes"; -#if ENABLE_SVG_ICONS item.icon_filename = "split_parts.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Split to parts")); item.sprite_id = 9; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES)); }; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES)); }; item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_split_to_volumes(); }; - if (!m_toolbar.add_item(item)) + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_volumes(); }; + if (!m_main_toolbar.add_item(item)) return false; - if (!m_toolbar.add_separator()) + if (!m_main_toolbar.add_separator()) return false; item.name = "layersediting"; -#if ENABLE_SVG_ICONS item.icon_filename = "layers_white.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Layers editing")); item.sprite_id = 10; - item.is_toggable = true; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; - item.visibility_callback = [this]()->bool { return m_process->current_printer_technology() == ptFFF; }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); }; - if (!m_toolbar.add_item(item)) + item.left.toggable = true; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; + item.visibility_callback = [this]()->bool + { + bool res = m_process->current_printer_technology() == ptFFF; + // turns off if changing printer technology + if (!res && m_main_toolbar.is_item_visible("layersediting") && m_main_toolbar.is_item_pressed("layersediting")) + force_main_toolbar_left_action(get_main_toolbar_item_id("layersediting")); + + return res; + }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); }; + if (!m_main_toolbar.add_item(item)) + return false; + + return true; +} + +bool GLCanvas3D::_init_undoredo_toolbar() +{ + if (!m_undoredo_toolbar.is_enabled()) + return true; + + BackgroundTexture::Metadata background_data; + background_data.filename = "toolbar_background.png"; + background_data.left = 16; + background_data.top = 16; + background_data.right = 16; + background_data.bottom = 16; + + if (!m_undoredo_toolbar.init(background_data)) + { + // unable to init the toolbar texture, disable it + m_undoredo_toolbar.set_enabled(false); + return true; + } + +// m_undoredo_toolbar.set_layout_type(GLToolbar::Layout::Vertical); + m_undoredo_toolbar.set_layout_type(GLToolbar::Layout::Horizontal); + m_undoredo_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Left); + m_undoredo_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top); + m_undoredo_toolbar.set_border(5.0f); + m_undoredo_toolbar.set_separator_size(5); + m_undoredo_toolbar.set_gap_size(2); + + GLToolbarItem::Data item; + + item.name = "undo"; + item.icon_filename = "undo_toolbar.svg"; + item.tooltip = _utf8(L("Undo")) + " [" + GUI::shortkey_ctrl_prefix() + "Z]"; + item.sprite_id = 0; + item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_UNDO)); }; + item.right.toggable = true; + item.right.action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; }; + item.right.render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(true, 0.5f * (left + right)); }; + item.enabling_callback = [this]()->bool { + bool can_undo = wxGetApp().plater()->can_undo(); + unsigned int id = m_undoredo_toolbar.get_item_id("undo"); + + std::string curr_additional_tooltip; + m_undoredo_toolbar.get_additional_tooltip(id, curr_additional_tooltip); + + std::string new_additional_tooltip = ""; + if (can_undo) + wxGetApp().plater()->undo_redo_topmost_string_getter(true, new_additional_tooltip); + + if (new_additional_tooltip != curr_additional_tooltip) + { + m_undoredo_toolbar.set_additional_tooltip(id, new_additional_tooltip); + set_tooltip(""); + } + return can_undo; + }; + + if (!m_undoredo_toolbar.add_item(item)) + return false; + + item.name = "redo"; + item.icon_filename = "redo_toolbar.svg"; + item.tooltip = _utf8(L("Redo")) + " [" + GUI::shortkey_ctrl_prefix() + "Y]"; + item.sprite_id = 1; + item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_REDO)); }; + item.right.action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; }; + item.right.render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(false, 0.5f * (left + right)); }; + item.enabling_callback = [this]()->bool { + bool can_redo = wxGetApp().plater()->can_redo(); + unsigned int id = m_undoredo_toolbar.get_item_id("redo"); + + std::string curr_additional_tooltip; + m_undoredo_toolbar.get_additional_tooltip(id, curr_additional_tooltip); + + std::string new_additional_tooltip = ""; + if (can_redo) + wxGetApp().plater()->undo_redo_topmost_string_getter(false, new_additional_tooltip); + + if (new_additional_tooltip != curr_additional_tooltip) + { + m_undoredo_toolbar.set_additional_tooltip(id, new_additional_tooltip); + set_tooltip(""); + } + return can_redo; + }; + + if (!m_undoredo_toolbar.add_item(item)) return false; return true; @@ -3595,143 +3744,36 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) // ensures that this canvas is current _set_current(); + + // updates camera m_camera.apply_viewport(0, 0, w, h); - const BoundingBoxf3& bbox = _max_bounding_box(); - - switch (m_camera.type) - { - case Camera::Ortho: - { - float w2 = w; - float h2 = h; - float two_zoom = 2.0f * m_camera.zoom; - if (two_zoom != 0.0f) - { - float inv_two_zoom = 1.0f / two_zoom; - w2 *= inv_two_zoom; - h2 *= inv_two_zoom; - } - - // FIXME: calculate a tighter value for depth will improve z-fighting - // Set at least some minimum depth in case the bounding box is empty to avoid an OpenGL driver error. - float depth = std::max(1.f, 5.0f * (float)bbox.max_size()); - m_camera.apply_ortho_projection(-w2, w2, -h2, h2, -depth, depth); - - break; - } -// case Camera::Perspective: -// { -// float bbox_r = (float)bbox.radius(); -// float fov = PI * 45.0f / 180.0f; -// float fov_tan = tan(0.5f * fov); -// float cam_distance = 0.5f * bbox_r / fov_tan; -// m_camera.distance = cam_distance; -// -// float nr = cam_distance - bbox_r * 1.1f; -// float fr = cam_distance + bbox_r * 1.1f; -// if (nr < 1.0f) -// nr = 1.0f; -// -// if (fr < nr + 1.0f) -// fr = nr + 1.0f; -// -// float h2 = fov_tan * nr; -// float w2 = h2 * w / h; -// ::glFrustum(-w2, w2, -h2, h2, nr, fr); -// -// break; -// } - default: - { - throw std::runtime_error("Invalid camera type."); - break; - } - } - m_dirty = false; } -BoundingBoxf3 GLCanvas3D::_max_bounding_box() const +BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_bed_model) const { BoundingBoxf3 bb = volumes_bounding_box(); - bb.merge(m_bed.get_bounding_box()); + + // The following is a workaround for gizmos not being taken in account when calculating the tight camera frustrum + // A better solution would ask the gizmo manager for the bounding box of the current active gizmo, if any + if (include_gizmos && m_gizmos.is_running()) + { + BoundingBoxf3 sel_bb = m_selection.get_bounding_box(); + Vec3d sel_bb_center = sel_bb.center(); + Vec3d extend_by = sel_bb.max_size() * Vec3d::Ones(); + bb.merge(BoundingBoxf3(sel_bb_center - extend_by, sel_bb_center + extend_by)); + } + + bb.merge(m_bed.get_bounding_box(include_bed_model)); return bb; } -void GLCanvas3D::_zoom_to_bounding_box(const BoundingBoxf3& bbox) +void GLCanvas3D::_zoom_to_box(const BoundingBoxf3& box) { - // Calculate the zoom factor needed to adjust viewport to bounding box. - float zoom = _get_zoom_to_bounding_box_factor(bbox); - if (zoom > 0.0f) - { - m_camera.zoom = zoom; - // center view around bounding box center - m_camera.set_target(bbox.center()); - m_dirty = true; - } -} - -float GLCanvas3D::_get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) const -{ - float max_bb_size = bbox.max_size(); - if (max_bb_size == 0.0f) - return -1.0f; - - // project the bbox vertices on a plane perpendicular to the camera forward axis - // then calculates the vertices coordinate on this plane along the camera xy axes - - // we need the view matrix, we let opengl calculate it (same as done in render()) - m_camera.apply_view_matrix(); - - Vec3d right = m_camera.get_dir_right(); - Vec3d up = m_camera.get_dir_up(); - Vec3d forward = m_camera.get_dir_forward(); - - Vec3d bb_min = bbox.min; - Vec3d bb_max = bbox.max; - Vec3d bb_center = bbox.center(); - - // bbox vertices in world space - std::vector vertices; - vertices.reserve(8); - vertices.push_back(bb_min); - vertices.emplace_back(bb_max(0), bb_min(1), bb_min(2)); - vertices.emplace_back(bb_max(0), bb_max(1), bb_min(2)); - vertices.emplace_back(bb_min(0), bb_max(1), bb_min(2)); - vertices.emplace_back(bb_min(0), bb_min(1), bb_max(2)); - vertices.emplace_back(bb_max(0), bb_min(1), bb_max(2)); - vertices.push_back(bb_max); - vertices.emplace_back(bb_min(0), bb_max(1), bb_max(2)); - - double max_x = 0.0; - double max_y = 0.0; - - // margin factor to give some empty space around the bbox - double margin_factor = 1.25; - - for (const Vec3d& v : vertices) - { - // project vertex on the plane perpendicular to camera forward axis - Vec3d pos(v(0) - bb_center(0), v(1) - bb_center(1), v(2) - bb_center(2)); - Vec3d proj_on_plane = pos - pos.dot(forward) * forward; - - // calculates vertex coordinate along camera xy axes - double x_on_plane = proj_on_plane.dot(right); - double y_on_plane = proj_on_plane.dot(up); - - max_x = std::max(max_x, margin_factor * std::abs(x_on_plane)); - max_y = std::max(max_y, margin_factor * std::abs(y_on_plane)); - } - - if ((max_x == 0.0) || (max_y == 0.0)) - return -1.0f; - - max_x *= 2.0; - max_y *= 2.0; - const Size& cnv_size = get_canvas_size(); - return (float)std::min((double)cnv_size.get_width() / max_x, (double)cnv_size.get_height() / max_y); + m_camera.zoom_to_box(box, cnv_size.get_width(), cnv_size.get_height()); + m_dirty = true; } void GLCanvas3D::_refresh_if_shown_on_screen() @@ -3758,6 +3800,7 @@ void GLCanvas3D::_picking_pass() const // Better to use software ray - casting on a bounding - box hierarchy. if (m_multisample_allowed) + // This flag is often ignored by NVIDIA drivers if rendering into a screen buffer. glsafe(::glDisable(GL_MULTISAMPLE)); glsafe(::glDisable(GL_BLEND)); @@ -3774,7 +3817,7 @@ void GLCanvas3D::_picking_pass() const if (m_camera_clipping_plane.is_active()) ::glDisable(GL_CLIP_PLANE0); - m_gizmos.render_current_gizmo_for_picking_pass(m_selection); + m_gizmos.render_current_gizmo_for_picking_pass(); if (m_multisample_allowed) glsafe(::glEnable(GL_MULTISAMPLE)); @@ -3787,7 +3830,9 @@ void GLCanvas3D::_picking_pass() const if (inside) { glsafe(::glReadPixels(m_mouse.position(0), cnv_size.get_height() - m_mouse.position(1) - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color)); - volume_id = color[0] + (color[1] << 8) + (color[2] << 16); + if (picking_checksum_alpha_channel(color[0], color[1], color[2]) == color[3]) + // Only non-interpolated colors are valid, those have their lowest three bits zeroed. + volume_id = color[0] + (color[1] << 8) + (color[2] << 16); } if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size())) { @@ -3810,6 +3855,7 @@ void GLCanvas3D::_rectangular_selection_picking_pass() const if (m_picking_enabled) { if (m_multisample_allowed) + // This flag is often ignored by NVIDIA drivers if rendering into a screen buffer. glsafe(::glDisable(GL_MULTISAMPLE)); glsafe(::glDisable(GL_BLEND)); @@ -3835,6 +3881,8 @@ void GLCanvas3D::_rectangular_selection_picking_pass() const struct Pixel { std::array data; + // Only non-interpolated colors are valid, those have their lowest three bits zeroed. + bool valid() const { return picking_checksum_alpha_channel(data[0], data[1], data[2]) == data[3]; } int id() const { return data[0] + (data[1] << 8) + (data[2] << 16); } }; @@ -3845,17 +3893,15 @@ void GLCanvas3D::_rectangular_selection_picking_pass() const tbb::parallel_for(tbb::blocked_range(0, frame.size(), (size_t)width), [this, &frame, &idxs, &mutex](const tbb::blocked_range& range) { for (size_t i = range.begin(); i < range.end(); ++i) - { - int volume_id = frame[i].id(); - if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size())) - { - mutex.lock(); - idxs.insert(volume_id); - mutex.unlock(); - } - } - } - ); + if (frame[i].valid()) { + int volume_id = frame[i].id(); + if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size())) { + mutex.lock(); + idxs.insert(volume_id); + mutex.unlock(); + } + } + }); #else std::vector frame(4 * px_count); glsafe(::glReadPixels(left, top, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (void*)frame.data())); @@ -3917,16 +3963,9 @@ void GLCanvas3D::_render_bed(float theta) const #if ENABLE_RETINA_GL scale_factor = m_retina_helper->get_scale_factor(); #endif // ENABLE_RETINA_GL - m_bed.render(theta, m_use_VBOs, scale_factor); + m_bed.render(const_cast(*this), theta, scale_factor); } -void GLCanvas3D::_render_axes() const -{ - m_bed.render_axes(); -} - - - void GLCanvas3D::_render_objects() const { if (m_volumes.empty()) @@ -3937,75 +3976,44 @@ void GLCanvas3D::_render_objects() const m_camera_clipping_plane = m_gizmos.get_sla_clipping_plane(); - if (m_use_VBOs) + if (m_picking_enabled) { - if (m_picking_enabled) + // Update the layer editing selection to the first object selected, update the current object maximum Z. + const_cast(m_layers_editing).select_object(*m_model, this->is_layers_editing_enabled() ? m_selection.get_object_idx() : -1); + + if (m_config != nullptr) { - // Update the layer editing selection to the first object selected, update the current object maximum Z. - const_cast(m_layers_editing).select_object(*m_model, this->is_layers_editing_enabled() ? m_selection.get_object_idx() : -1); - - if (m_config != nullptr) - { - const BoundingBoxf3& bed_bb = m_bed.get_bounding_box(); - m_volumes.set_print_box((float)bed_bb.min(0), (float)bed_bb.min(1), 0.0f, (float)bed_bb.max(0), (float)bed_bb.max(1), (float)m_config->opt_float("max_print_height")); - m_volumes.check_outside_state(m_config, nullptr); - } + const BoundingBoxf3& bed_bb = m_bed.get_bounding_box(false); + m_volumes.set_print_box((float)bed_bb.min(0), (float)bed_bb.min(1), 0.0f, (float)bed_bb.max(0), (float)bed_bb.max(1), (float)m_config->opt_float("max_print_height")); + m_volumes.check_outside_state(m_config, nullptr); } - - if (m_use_clipping_planes) - m_volumes.set_z_range(-m_clipping_planes[0].get_data()[3], m_clipping_planes[1].get_data()[3]); - else - m_volumes.set_z_range(-FLT_MAX, FLT_MAX); - - m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data()); - - m_shader.start_using(); - if (m_picking_enabled && !m_gizmos.is_dragging() && m_layers_editing.is_enabled() && (m_layers_editing.last_object_id != -1) && (m_layers_editing.object_max_z() > 0.0f)) { - int object_id = m_layers_editing.last_object_id; - m_volumes.render_VBOs(GLVolumeCollection::Opaque, false, m_camera.get_view_matrix(), [object_id](const GLVolume &volume) { - // Which volume to paint without the layer height profile shader? - return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id); - }); - // Let LayersEditing handle rendering of the active object using the layer height profile shader. - m_layers_editing.render_volumes(*this, this->m_volumes); - } else { - // do not cull backfaces to show broken geometry, if any - m_volumes.render_VBOs(GLVolumeCollection::Opaque, m_picking_enabled, m_camera.get_view_matrix(), [this](const GLVolume& volume) { - return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); - }); - } - m_volumes.render_VBOs(GLVolumeCollection::Transparent, false, m_camera.get_view_matrix()); - m_shader.stop_using(); } + + if (m_use_clipping_planes) + m_volumes.set_z_range(-m_clipping_planes[0].get_data()[3], m_clipping_planes[1].get_data()[3]); else - { - ::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data()); - ::glEnable(GL_CLIP_PLANE0); + m_volumes.set_z_range(-FLT_MAX, FLT_MAX); - if (m_use_clipping_planes) - { - glsafe(::glClipPlane(GL_CLIP_PLANE1, (GLdouble*)m_clipping_planes[0].get_data())); - glsafe(::glEnable(GL_CLIP_PLANE1)); - glsafe(::glClipPlane(GL_CLIP_PLANE2, (GLdouble*)m_clipping_planes[1].get_data())); - glsafe(::glEnable(GL_CLIP_PLANE2)); - } - + m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data()); + m_shader.start_using(); + if (m_picking_enabled && !m_gizmos.is_dragging() && m_layers_editing.is_enabled() && (m_layers_editing.last_object_id != -1) && (m_layers_editing.object_max_z() > 0.0f)) { + int object_id = m_layers_editing.last_object_id; + m_volumes.render(GLVolumeCollection::Opaque, false, m_camera.get_view_matrix(), [object_id](const GLVolume& volume) { + // Which volume to paint without the layer height profile shader? + return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id); + }); + // Let LayersEditing handle rendering of the active object using the layer height profile shader. + m_layers_editing.render_volumes(*this, this->m_volumes); + } else { // do not cull backfaces to show broken geometry, if any - m_volumes.render_legacy(GLVolumeCollection::Opaque, m_picking_enabled, m_camera.get_view_matrix(), [this](const GLVolume& volume) { + m_volumes.render(GLVolumeCollection::Opaque, m_picking_enabled, m_camera.get_view_matrix(), [this](const GLVolume& volume) { return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); }); - m_volumes.render_legacy(GLVolumeCollection::Transparent, false, m_camera.get_view_matrix()); - - ::glDisable(GL_CLIP_PLANE0); - - if (m_use_clipping_planes) - { - glsafe(::glDisable(GL_CLIP_PLANE1)); - glsafe(::glDisable(GL_CLIP_PLANE2)); - } } - + m_volumes.render(GLVolumeCollection::Transparent, false, m_camera.get_view_matrix()); + m_shader.stop_using(); + m_camera_clipping_plane = ClippingPlane::ClipsNothing(); glsafe(::glDisable(GL_LIGHTING)); } @@ -4028,6 +4036,30 @@ void GLCanvas3D::_render_selection_center() const } #endif // ENABLE_RENDER_SELECTION_CENTER +void GLCanvas3D::_render_overlays() const +{ + glsafe(::glDisable(GL_DEPTH_TEST)); + glsafe(::glPushMatrix()); + glsafe(::glLoadIdentity()); + // ensure that the textures are renderered inside the frustrum + glsafe(::glTranslated(0.0, 0.0, -(m_camera.get_near_z() + 0.005))); + // ensure that the overlay fits the frustrum near z plane + double gui_scale = m_camera.get_gui_scale(); + glsafe(::glScaled(gui_scale, gui_scale, 1.0)); + + _render_gizmos_overlay(); + _render_warning_texture(); + _render_legend_texture(); + _render_main_toolbar(); + _render_undoredo_toolbar(); + _render_view_toolbar(); + + if ((m_layers_editing.last_object_id >= 0) && (m_layers_editing.object_max_z() > 0.0f)) + m_layers_editing.render_overlay(*this); + + glsafe(::glPopMatrix()); +} + void GLCanvas3D::_render_warning_texture() const { m_warning_texture.render(*this); @@ -4048,49 +4080,34 @@ void GLCanvas3D::_render_volumes_for_picking() const // do not cull backfaces to show broken geometry, if any glsafe(::glDisable(GL_CULL_FACE)); - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); const Transform3d& view_matrix = m_camera.get_view_matrix(); - GLVolumeWithIdAndZList to_render = volumes_to_render(m_volumes.volumes, GLVolumeCollection::Opaque, view_matrix); - for (const GLVolumeWithIdAndZ& volume : to_render) - { - // Object picking mode. Render the object with a color encoding the object index. - unsigned int r = (volume.second.first & 0x000000FF) >> 0; - unsigned int g = (volume.second.first & 0x0000FF00) >> 8; - unsigned int b = (volume.second.first & 0x00FF0000) >> 16; - glsafe(::glColor3f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255)); - - if (!volume.first->disabled && ((volume.first->composite_id.volume_id >= 0) || m_render_sla_auxiliaries)) - volume.first->render(); - } - - to_render = volumes_to_render(m_volumes.volumes, GLVolumeCollection::Transparent, view_matrix); - for (const GLVolumeWithIdAndZ& volume : to_render) - { - // Object picking mode. Render the object with a color encoding the object index. - unsigned int r = (volume.second.first & 0x000000FF) >> 0; - unsigned int g = (volume.second.first & 0x0000FF00) >> 8; - unsigned int b = (volume.second.first & 0x00FF0000) >> 16; - glsafe(::glColor3f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255)); - - if (!volume.first->disabled && ((volume.first->composite_id.volume_id >= 0) || m_render_sla_auxiliaries)) - volume.first->render(); - } + for (size_t type = 0; type < 2; ++ type) { + GLVolumeWithIdAndZList to_render = volumes_to_render(m_volumes.volumes, (type == 0) ? GLVolumeCollection::Opaque : GLVolumeCollection::Transparent, view_matrix); + for (const GLVolumeWithIdAndZ& volume : to_render) + if (!volume.first->disabled && ((volume.first->composite_id.volume_id >= 0) || m_render_sla_auxiliaries)) { + // Object picking mode. Render the object with a color encoding the object index. + unsigned int id = volume.second.first; + unsigned int r = (id & (0x000000FF << 0)) << 0; + unsigned int g = (id & (0x000000FF << 8)) >> 8; + unsigned int b = (id & (0x000000FF << 16)) >> 16; + unsigned int a = picking_checksum_alpha_channel(r, g, b); + glsafe(::glColor4f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255, (GLfloat)a * INV_255)); + volume.first->render(); + } + } glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glDisable(GL_BLEND)); glsafe(::glEnable(GL_CULL_FACE)); } void GLCanvas3D::_render_current_gizmo() const { - m_gizmos.render_current_gizmo(m_selection); + m_gizmos.render_current_gizmo(); } void GLCanvas3D::_render_gizmos_overlay() const @@ -4106,80 +4123,64 @@ void GLCanvas3D::_render_gizmos_overlay() const m_gizmos.set_overlay_icon_size(size); //! #ys_FIXME_experiment #endif /* __WXMSW__ */ - m_gizmos.render_overlay(*this, m_selection); + m_gizmos.render_overlay(); } -void GLCanvas3D::_render_toolbar() const +void GLCanvas3D::_render_main_toolbar() const { -#if ENABLE_SVG_ICONS + if (!m_main_toolbar.is_enabled()) + return; + #if ENABLE_RETINA_GL -// m_toolbar.set_scale(m_retina_helper->get_scale_factor()); +// m_main_toolbar.set_scale(m_retina_helper->get_scale_factor()); const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(true); - m_toolbar.set_scale(scale); //! #ys_FIXME_experiment + m_main_toolbar.set_scale(scale); //! #ys_FIXME_experiment #else -// m_toolbar.set_scale(m_canvas->GetContentScaleFactor()); -// m_toolbar.set_scale(wxGetApp().em_unit()*0.1f); +// m_main_toolbar.set_scale(m_canvas->GetContentScaleFactor()); +// m_main_toolbar.set_scale(wxGetApp().em_unit()*0.1f); const float size = int(GLToolbar::Default_Icons_Size * wxGetApp().toolbar_icon_scale(true)); - m_toolbar.set_icons_size(size); //! #ys_FIXME_experiment + m_main_toolbar.set_icons_size(size); //! #ys_FIXME_experiment #endif // ENABLE_RETINA_GL Size cnv_size = get_canvas_size(); - float zoom = m_camera.zoom; + float zoom = (float)m_camera.get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; - GLToolbar::Layout::EOrientation orientation = m_toolbar.get_layout_orientation(); + float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; + float left = -0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width()) * inv_zoom; + + m_main_toolbar.set_position(top, left); + m_main_toolbar.render(*this); +} + +void GLCanvas3D::_render_undoredo_toolbar() const +{ + if (!m_undoredo_toolbar.is_enabled()) + return; - float top = 0.0f; - float left = 0.0f; - switch (m_toolbar.get_layout_type()) - { - default: - case GLToolbar::Layout::Horizontal: - { - // centers the toolbar on the top edge of the 3d scene - if (orientation == GLToolbar::Layout::Top) - { - top = 0.5f * (float)cnv_size.get_height() * inv_zoom; - left = -0.5f * m_toolbar.get_width() * inv_zoom; - } - else - { - top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; - left = -0.5f * m_toolbar.get_width() * inv_zoom; - } - break; - } - case GLToolbar::Layout::Vertical: - { - // centers the toolbar on the right edge of the 3d scene - if (orientation == GLToolbar::Layout::Left) - { - top = 0.5f * m_toolbar.get_height() * inv_zoom; - left = (-0.5f * (float)cnv_size.get_width()) * inv_zoom; - } - else - { - top = 0.5f * m_toolbar.get_height() * inv_zoom; - left = (0.5f * (float)cnv_size.get_width() - m_toolbar.get_width()) * inv_zoom; - } - break; - } - } - m_toolbar.set_position(top, left); -#else #if ENABLE_RETINA_GL - m_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); +// m_undoredo_toolbar.set_scale(m_retina_helper->get_scale_factor()); + const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(true); + m_undoredo_toolbar.set_scale(scale); //! #ys_FIXME_experiment #else - m_toolbar.set_icons_scale(m_canvas->GetContentScaleFactor()); -#endif /* __WXMSW__ */ -#endif // ENABLE_SVG_ICONS +// m_undoredo_toolbar.set_scale(m_canvas->GetContentScaleFactor()); +// m_undoredo_toolbar.set_scale(wxGetApp().em_unit()*0.1f); + const float size = int(GLToolbar::Default_Icons_Size * wxGetApp().toolbar_icon_scale(true)); + m_undoredo_toolbar.set_icons_size(size); //! #ys_FIXME_experiment +#endif // ENABLE_RETINA_GL - m_toolbar.render(*this); + Size cnv_size = get_canvas_size(); + float zoom = (float)m_camera.get_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + + float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; + float left = (m_main_toolbar.get_width() - 0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width())) * inv_zoom; + m_undoredo_toolbar.set_position(top, left); + m_undoredo_toolbar.render(*this); } void GLCanvas3D::_render_view_toolbar() const { -#if ENABLE_SVG_ICONS #if ENABLE_RETINA_GL // m_view_toolbar.set_scale(m_retina_helper->get_scale_factor()); const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(); @@ -4192,20 +4193,13 @@ void GLCanvas3D::_render_view_toolbar() const #endif // ENABLE_RETINA_GL Size cnv_size = get_canvas_size(); - float zoom = m_camera.zoom; + float zoom = (float)m_camera.get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; // places the toolbar on the bottom-left corner of the 3d scene float top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; float left = -0.5f * (float)cnv_size.get_width() * inv_zoom; m_view_toolbar.set_position(top, left); -#else -#if ENABLE_RETINA_GL - m_view_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); -#else - m_view_toolbar.set_icons_scale(m_canvas->GetContentScaleFactor()); -#endif /* __WXMSW__ */ -#endif // ENABLE_SVG_ICONS m_view_toolbar.render(*this); } @@ -4358,16 +4352,9 @@ void GLCanvas3D::_render_sla_slices() const void GLCanvas3D::_render_selection_sidebar_hints() const { - if (m_use_VBOs) - m_shader.start_using(); - - m_selection.render_sidebar_hints(m_sidebar_field); - - if (m_use_VBOs) - m_shader.stop_using(); + m_selection.render_sidebar_hints(m_sidebar_field, m_shader); } - void GLCanvas3D::_update_volumes_hover_state() const { for (GLVolume* v : m_volumes.volumes) @@ -4576,8 +4563,7 @@ void GLCanvas3D::_load_print_toolpaths() _3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), volume); } - volume.bounding_box = volume.indexed_vertex_array.bounding_box(); - volume.indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); + volume.indexed_vertex_array.finalize_geometry(m_initialized); } void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const std::vector& str_tool_colors, const std::vector& color_print_values) @@ -4608,17 +4594,12 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c bool color_by_tool() const { return tool_colors != nullptr; } size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() / 4 : 0; } const float* color_tool(size_t tool) const { return tool_colors->data() + tool * 4; } - int volume_idx(int extruder, int feature) const - { - return this->color_by_color_print() ? 0 : this->color_by_tool() ? std::min(this->number_tools() - 1, std::max(extruder - 1, 0)) : feature; - } // For coloring by a color_print(M600), return a parsed color. bool color_by_color_print() const { return color_print_values!=nullptr; } - const float* color_print_by_layer_idx(const size_t layer_idx) const - { + const size_t color_print_color_idx_by_layer_idx(const size_t layer_idx) const { auto it = std::lower_bound(color_print_values->begin(), color_print_values->end(), layers[layer_idx]->print_z + EPSILON); - return color_tool((it - color_print_values->begin()) % number_tools()); + return (it - color_print_values->begin()) % number_tools(); } } ctxt; @@ -4648,10 +4629,10 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c std::sort(ctxt.layers.begin(), ctxt.layers.end(), [](const Layer *l1, const Layer *l2) { return l1->print_z < l2->print_z; }); // Maximum size of an allocation block: 32MB / sizeof(float) - BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - start"; + BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - start" << m_volumes.log_memory_info() << log_memory_info(); //FIXME Improve the heuristics for a grain size. - size_t grain_size = ctxt.color_by_color_print() ? size_t(1) : std::max(ctxt.layers.size() / 16, size_t(1)); + size_t grain_size = std::max(ctxt.layers.size() / 16, size_t(1)); tbb::spin_mutex new_volume_mutex; auto new_volume = [this, &new_volume_mutex](const float *color) -> GLVolume* { auto *volume = new GLVolume(color); @@ -4660,14 +4641,34 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c new_volume_mutex.unlock(); return volume; }; - const size_t volumes_cnt_initial = m_volumes.volumes.size(); - std::vector volumes_per_thread(ctxt.layers.size()); + const size_t volumes_cnt_initial = m_volumes.volumes.size(); tbb::parallel_for( tbb::blocked_range(0, ctxt.layers.size(), grain_size), [&ctxt, &new_volume](const tbb::blocked_range& range) { - GLVolumePtrs vols; - if (ctxt.color_by_color_print()) - vols.emplace_back(new_volume(ctxt.color_print_by_layer_idx(range.begin()))); + GLVolumePtrs vols; + std::vector color_print_layer_to_glvolume; + auto volume = [&ctxt, &vols, &color_print_layer_to_glvolume, &range](size_t layer_idx, int extruder, int feature) -> GLVolume& { + return *vols[ctxt.color_by_color_print() ? + color_print_layer_to_glvolume[layer_idx - range.begin()] : + ctxt.color_by_tool() ? + std::min(ctxt.number_tools() - 1, std::max(extruder - 1, 0)) : + feature + ]; + }; + if (ctxt.color_by_color_print()) { + // Create a map from the layer index to a GLVolume, which is initialized with the correct layer span color. + std::vector color_print_tool_to_glvolume(ctxt.number_tools(), -1); + color_print_layer_to_glvolume.reserve(range.end() - range.begin()); + vols.reserve(ctxt.number_tools()); + for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { + int idx_tool = (int)ctxt.color_print_color_idx_by_layer_idx(idx_layer); + if (color_print_tool_to_glvolume[idx_tool] == -1) { + color_print_tool_to_glvolume[idx_tool] = (int)vols.size(); + vols.emplace_back(new_volume(ctxt.color_tool(idx_tool))); + } + color_print_layer_to_glvolume.emplace_back(color_print_tool_to_glvolume[idx_tool]); + } + } else if (ctxt.color_by_tool()) { for (size_t i = 0; i < ctxt.number_tools(); ++i) vols.emplace_back(new_volume(ctxt.color_tool(i))); @@ -4675,33 +4676,31 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c else vols = { new_volume(ctxt.color_perimeters()), new_volume(ctxt.color_infill()), new_volume(ctxt.color_support()) }; for (GLVolume *vol : vols) - vol->indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); - for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { + vol->indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); + for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { const Layer *layer = ctxt.layers[idx_layer]; - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume &vol = *vols[i]; - if (vol.print_zs.empty() || vol.print_zs.back() != layer->print_z) { - vol.print_zs.push_back(layer->print_z); - vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size()); - vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size()); + for (GLVolume *vol : vols) + if (vol->print_zs.empty() || vol->print_zs.back() != layer->print_z) { + vol->print_zs.push_back(layer->print_z); + vol->offsets.push_back(vol->indexed_vertex_array.quad_indices.size()); + vol->offsets.push_back(vol->indexed_vertex_array.triangle_indices.size()); } - } for (const Point © : *ctxt.shifted_copies) { for (const LayerRegion *layerm : layer->regions()) { if (ctxt.has_perimeters) _3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, - *vols[ctxt.volume_idx(layerm->region()->config().perimeter_extruder.value, 0)]); + volume(idx_layer, layerm->region()->config().perimeter_extruder.value, 0)); if (ctxt.has_infill) { for (const ExtrusionEntity *ee : layerm->fills.entities) { // fill represents infill extrusions of a single island. const auto *fill = dynamic_cast(ee); - if (!fill->entities.empty()) + if (! fill->entities.empty()) _3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy, - *vols[ctxt.volume_idx( - is_solid_infill(fill->entities.front()->role()) ? - layerm->region()->config().solid_infill_extruder : - layerm->region()->config().infill_extruder, - 1)]); + volume(idx_layer, + is_solid_infill(fill->entities.front()->role()) ? + layerm->region()->config().solid_infill_extruder : + layerm->region()->config().infill_extruder, + 1)); } } } @@ -4710,49 +4709,48 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c if (support_layer) { for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities) _3DScene::extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy, - *vols[ctxt.volume_idx( - (extrusion_entity->role() == erSupportMaterial) ? - support_layer->object()->config().support_material_extruder : - support_layer->object()->config().support_material_interface_extruder, - 2)]); + volume(idx_layer, + (extrusion_entity->role() == erSupportMaterial) ? + support_layer->object()->config().support_material_extruder : + support_layer->object()->config().support_material_interface_extruder, + 2)); } } } - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume &vol = *vols[i]; - if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) { - // Store the vertex arrays and restart their containers, - vols[i] = new_volume(vol.color); - GLVolume &vol_new = *vols[i]; - // Assign the large pre-allocated buffers to the new GLVolume. - vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); - // Copy the content back to the old GLVolume. - vol.indexed_vertex_array = vol_new.indexed_vertex_array; - // Finalize a bounding box of the old GLVolume. - vol.bounding_box = vol.indexed_vertex_array.bounding_box(); - // Clear the buffers, but keep them pre-allocated. - vol_new.indexed_vertex_array.clear(); - // Just make sure that clear did not clear the reserved memory. - vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); - } - } + // Ensure that no volume grows over the limits. If the volume is too large, allocate a new one. + for (size_t i = 0; i < vols.size(); ++i) { + GLVolume &vol = *vols[i]; + if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) { + // Store the vertex arrays and restart their containers, + vols[i] = new_volume(vol.color); + GLVolume &vol_new = *vols[i]; + // Assign the large pre-allocated buffers to the new GLVolume. + vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); + // Copy the content back to the old GLVolume. + vol.indexed_vertex_array = vol_new.indexed_vertex_array; + // Clear the buffers, but keep them pre-allocated. + vol_new.indexed_vertex_array.clear(); + // Just make sure that clear did not clear the reserved memory. + vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); + } + } } - for (GLVolume *vol : vols) { - vol->bounding_box = vol->indexed_vertex_array.bounding_box(); + for (GLVolume *vol : vols) + // Ideally one would call vol->indexed_vertex_array.finalize() here to move the buffers to the OpenGL driver, + // but this code runs in parallel and the OpenGL driver is not thread safe. vol->indexed_vertex_array.shrink_to_fit(); - } }); - BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results"; + BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results" << m_volumes.log_memory_info() << log_memory_info(); // Remove empty volumes from the newly added volumes. m_volumes.volumes.erase( std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), [](const GLVolume *volume) { return volume->empty(); }), m_volumes.volumes.end()); for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) - m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); + m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_initialized); - BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end"; + BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); } void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_tool_colors) @@ -4770,7 +4768,7 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ { const Print *print; const std::vector *tool_colors; - WipeTower::xy wipe_tower_pos; + Vec2f wipe_tower_pos; float wipe_tower_angle; // Number of vertices (each vertex is 6x4=24 bytes long) @@ -4801,14 +4799,15 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ ctxt.print = print; ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; if (print->wipe_tower_data().priming && print->config().single_extruder_multi_material_priming) - ctxt.priming.emplace_back(*print->wipe_tower_data().priming.get()); + for (int i=0; iwipe_tower_data().priming.get()->size(); ++i) + ctxt.priming.emplace_back(print->wipe_tower_data().priming.get()->at(i)); if (print->wipe_tower_data().final_purge) ctxt.final.emplace_back(*print->wipe_tower_data().final_purge.get()); ctxt.wipe_tower_angle = ctxt.print->config().wipe_tower_rotation_angle.value/180.f * PI; - ctxt.wipe_tower_pos = WipeTower::xy(ctxt.print->config().wipe_tower_x.value, ctxt.print->config().wipe_tower_y.value); + ctxt.wipe_tower_pos = Vec2f(ctxt.print->config().wipe_tower_x.value, ctxt.print->config().wipe_tower_y.value); - BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start"; + BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start" << m_volumes.log_memory_info() << log_memory_info(); //FIXME Improve the heuristics for a grain size. size_t n_items = print->wipe_tower_data().tool_changes.size() + (ctxt.priming.empty() ? 0 : 1); @@ -4868,19 +4867,19 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ WipeTower::Extrusion e_prev = extrusions.extrusions[i-1]; if (!extrusions.priming) { // wipe tower extrusions describe the wipe tower at the origin with no rotation - e_prev.pos.rotate(ctxt.wipe_tower_angle); - e_prev.pos.translate(ctxt.wipe_tower_pos); + e_prev.pos = Eigen::Rotation2Df(ctxt.wipe_tower_angle) * e_prev.pos; + e_prev.pos += ctxt.wipe_tower_pos; } for (; i < j; ++i) { WipeTower::Extrusion e = extrusions.extrusions[i]; assert(e.width > 0.f); if (!extrusions.priming) { - e.pos.rotate(ctxt.wipe_tower_angle); - e.pos.translate(ctxt.wipe_tower_pos); + e.pos = Eigen::Rotation2Df(ctxt.wipe_tower_angle) * e.pos; + e.pos += ctxt.wipe_tower_pos; } - lines.emplace_back(Point::new_scale(e_prev.pos.x, e_prev.pos.y), Point::new_scale(e.pos.x, e.pos.y)); + lines.emplace_back(Point::new_scale(e_prev.pos.x(), e_prev.pos.y()), Point::new_scale(e.pos.x(), e.pos.y())); widths.emplace_back(e.width); e_prev = e; @@ -4900,30 +4899,26 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); // Copy the content back to the old GLVolume. vol.indexed_vertex_array = vol_new.indexed_vertex_array; - // Finalize a bounding box of the old GLVolume. - vol.bounding_box = vol.indexed_vertex_array.bounding_box(); // Clear the buffers, but keep them pre-allocated. vol_new.indexed_vertex_array.clear(); // Just make sure that clear did not clear the reserved memory. vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); } } - for (GLVolume *vol : vols) { - vol->bounding_box = vol->indexed_vertex_array.bounding_box(); + for (GLVolume *vol : vols) vol->indexed_vertex_array.shrink_to_fit(); - } }); - BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results"; + BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results" << m_volumes.log_memory_info() << log_memory_info(); // Remove empty volumes from the newly added volumes. m_volumes.volumes.erase( std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), [](const GLVolume *volume) { return volume->empty(); }), m_volumes.volumes.end()); for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) - m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); + m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_initialized); - BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end"; + BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); } static inline int hex_digit_to_int(const char c) @@ -4936,6 +4931,8 @@ static inline int hex_digit_to_int(const char c) void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_data, const std::vector& tool_colors) { + BOOST_LOG_TRIVIAL(debug) << "Loading G-code extrusion paths - start" << m_volumes.log_memory_info() << log_memory_info(); + // helper functions to select data in dependence of the extrusion view type struct Helper { @@ -5051,6 +5048,8 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat if (filters.empty()) return; + BOOST_LOG_TRIVIAL(debug) << "Loading G-code extrusion paths - create volumes" << m_volumes.log_memory_info() << log_memory_info(); + // creates a new volume for each filter for (Filter& filter : filters) { @@ -5081,6 +5080,8 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat } } + BOOST_LOG_TRIVIAL(debug) << "Loading G-code extrusion paths - populate volumes" << m_volumes.log_memory_info() << log_memory_info(); + // populates volumes for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers) { @@ -5100,15 +5101,10 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat } // finalize volumes and sends geometry to gpu - if (m_volumes.volumes.size() > initial_volumes_count) - { - for (size_t i = initial_volumes_count; i < m_volumes.volumes.size(); ++i) - { - GLVolume* volume = m_volumes.volumes[i]; - volume->bounding_box = volume->indexed_vertex_array.bounding_box(); - volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); - } - } + for (size_t i = initial_volumes_count; i < m_volumes.volumes.size(); ++i) + m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_initialized); + + BOOST_LOG_TRIVIAL(debug) << "Loading G-code extrusion paths - end" << m_volumes.log_memory_info() << log_memory_info(); } void GLCanvas3D::_load_gcode_travel_paths(const GCodePreviewData& preview_data, const std::vector& tool_colors) @@ -5155,15 +5151,8 @@ void GLCanvas3D::_load_gcode_travel_paths(const GCodePreviewData& preview_data, } // finalize volumes and sends geometry to gpu - if (m_volumes.volumes.size() > initial_volumes_count) - { - for (size_t i = initial_volumes_count; i < m_volumes.volumes.size(); ++i) - { - GLVolume* volume = m_volumes.volumes[i]; - volume->bounding_box = volume->indexed_vertex_array.bounding_box(); - volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); - } - } + for (size_t i = initial_volumes_count; i < m_volumes.volumes.size(); ++i) + m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_initialized); } bool GLCanvas3D::_travel_paths_by_type(const GCodePreviewData& preview_data) @@ -5392,10 +5381,7 @@ void GLCanvas3D::_load_gcode_retractions(const GCodePreviewData& preview_data) _3DScene::point3_to_verts(position.position, position.width, position.height, *volume); } - - // finalize volumes and sends geometry to gpu - volume->bounding_box = volume->indexed_vertex_array.bounding_box(); - volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); + volume->indexed_vertex_array.finalize_geometry(m_initialized); } } @@ -5423,10 +5409,7 @@ void GLCanvas3D::_load_gcode_unretractions(const GCodePreviewData& preview_data) _3DScene::point3_to_verts(position.position, position.width, position.height, *volume); } - - // finalize volumes and sends geometry to gpu - volume->bounding_box = volume->indexed_vertex_array.bounding_box(); - volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); + volume->indexed_vertex_array.finalize_geometry(m_initialized); } } @@ -5452,7 +5435,7 @@ void GLCanvas3D::_load_fff_shells() instance_ids[i] = i; } - m_volumes.load_object(model_obj, object_id, instance_ids, "object", m_use_VBOs && m_initialized); + m_volumes.load_object(model_obj, object_id, instance_ids, "object", m_initialized); ++object_id; } @@ -5462,7 +5445,7 @@ void GLCanvas3D::_load_fff_shells() double max_z = print->objects()[0]->model_object()->get_model()->bounding_box().max(2); const PrintConfig& config = print->config(); unsigned int extruders_count = config.nozzle_diameter.size(); - if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) { + if ((extruders_count > 1) && config.wipe_tower && !config.complete_objects) { float depth = print->get_wipe_tower_depth(); // Calculate wipe tower brim spacing. @@ -5472,17 +5455,21 @@ void GLCanvas3D::_load_fff_shells() float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4); if (!print->is_step_done(psWipeTower)) - depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1) ; + depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1); m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, - m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower), brim_spacing * 4.5f); + !print->is_step_done(psWipeTower), brim_spacing * 4.5f, m_initialized); } } } +// While it looks like we can call +// this->reload_scene(true, true) +// the two functions are quite different: +// 1) This function only loads objects, for which the step slaposSliceSupports already finished. Therefore objects outside of the print bed never load. +// 2) This function loads object mesh with the relative scaling correction (the "relative_correction" parameter) was applied, +// therefore the mesh may be slightly larger or smaller than the mesh shown in the 3D scene. void GLCanvas3D::_load_sla_shells() { - //FIXME use reload_scene -#if 1 const SLAPrint* print = this->sla_print(); if (print->objects().empty()) // nothing to render, return @@ -5492,13 +5479,14 @@ void GLCanvas3D::_load_sla_shells() const TriangleMesh &mesh, const float color[4], bool outside_printer_detection_enabled) { m_volumes.volumes.emplace_back(new GLVolume(color)); GLVolume& v = *m_volumes.volumes.back(); - v.indexed_vertex_array.load_mesh(mesh, m_use_VBOs); - v.shader_outside_printer_detection_enabled = outside_printer_detection_enabled; + v.indexed_vertex_array.load_mesh(mesh); + v.indexed_vertex_array.finalize_geometry(this->m_initialized); + v.shader_outside_printer_detection_enabled = outside_printer_detection_enabled; v.composite_id.volume_id = volume_id; v.set_instance_offset(unscale(instance.shift(0), instance.shift(1), 0)); v.set_instance_rotation(Vec3d(0.0, 0.0, (double)instance.rotation)); v.set_instance_mirror(X, object.is_left_handed() ? -1. : 1.); - v.set_convex_hull(new TriangleMesh(std::move(mesh.convex_hull_3d())), true); + v.set_convex_hull(mesh.convex_hull_3d()); }; // adds objects' volumes @@ -5518,18 +5506,12 @@ void GLCanvas3D::_load_sla_shells() double shift_z = obj->get_current_elevation(); for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) { GLVolume& v = *m_volumes.volumes[i]; - // finalize volumes and sends geometry to gpu - v.bounding_box = v.indexed_vertex_array.bounding_box(); - v.indexed_vertex_array.finalize_geometry(m_use_VBOs); // apply shift z v.set_sla_shift_z(shift_z); } } update_volumes_colors_by_extruder(); -#else - this->reload_scene(true, true); -#endif } void GLCanvas3D::_update_gcode_volumes_visibility(const GCodePreviewData& preview_data) @@ -5611,7 +5593,7 @@ void GLCanvas3D::_update_toolpath_volumes_outside_state() for (GLVolume* volume : m_volumes.volumes) { - volume->is_outside = ((print_volume.radius() > 0.0) && volume->is_extrusion_path) ? !print_volume.contains(volume->bounding_box) : false; + volume->is_outside = ((print_volume.radius() > 0.0) && volume->is_extrusion_path) ? !print_volume.contains(volume->bounding_box()) : false; } } @@ -5673,7 +5655,7 @@ std::vector GLCanvas3D::_parse_colors(const std::vector& col void GLCanvas3D::_generate_legend_texture(const GCodePreviewData& preview_data, const std::vector& tool_colors) { - m_legend_texture.generate(preview_data, tool_colors, *this); + m_legend_texture.generate(preview_data, tool_colors, *this, true); } void GLCanvas3D::_set_warning_texture(WarningTexture::Warning warning, bool state) @@ -5692,76 +5674,6 @@ bool GLCanvas3D::_is_any_volume_outside() const return false; } -#if !ENABLE_SVG_ICONS -void GLCanvas3D::_resize_toolbars() const -{ - Size cnv_size = get_canvas_size(); - float zoom = get_camera_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; - -#if ENABLE_RETINA_GL - m_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); -#else - m_toolbar.set_icons_scale(m_canvas->GetContentScaleFactor()); -#endif /* __WXMSW__ */ - - GLToolbar::Layout::EOrientation orientation = m_toolbar.get_layout_orientation(); - - switch (m_toolbar.get_layout_type()) - { - default: - case GLToolbar::Layout::Horizontal: - { - // centers the toolbar on the top edge of the 3d scene - float top, left; - if (orientation == GLToolbar::Layout::Top) - { - top = 0.5f * (float)cnv_size.get_height() * inv_zoom; - left = -0.5f * m_toolbar.get_width() * inv_zoom; - } - else - { - top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; - left = -0.5f * m_toolbar.get_width() * inv_zoom; - } - m_toolbar.set_position(top, left); - break; - } - case GLToolbar::Layout::Vertical: - { - // centers the toolbar on the right edge of the 3d scene - float top, left; - if (orientation == GLToolbar::Layout::Left) - { - top = 0.5f * m_toolbar.get_height() * inv_zoom; - left = (-0.5f * (float)cnv_size.get_width()) * inv_zoom; - } - else - { - top = 0.5f * m_toolbar.get_height() * inv_zoom; - left = (0.5f * (float)cnv_size.get_width() - m_toolbar.get_width()) * inv_zoom; - } - m_toolbar.set_position(top, left); - break; - } - } - - if (m_view_toolbar != nullptr) - { -#if ENABLE_RETINA_GL - m_view_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); -#else - m_view_toolbar.set_icons_scale(m_canvas->GetContentScaleFactor()); -#endif /* __WXMSW__ */ - - // places the toolbar on the bottom-left corner of the 3d scene - float top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; - float left = -0.5f * (float)cnv_size.get_width() * inv_zoom; - m_view_toolbar.set_position(top, left); - } -} -#endif // !ENABLE_SVG_ICONS - void GLCanvas3D::_update_selection_from_hover() { bool ctrl_pressed = wxGetKeyState(WXK_CONTROL); @@ -5769,7 +5681,7 @@ void GLCanvas3D::_update_selection_from_hover() if (m_hover_volume_idxs.empty()) { if (!ctrl_pressed && (m_rectangle_selection.get_state() == GLSelectionRectangle::Select)) - m_selection.clear(); + m_selection.remove_all(); return; } @@ -5786,6 +5698,51 @@ void GLCanvas3D::_update_selection_from_hover() } } + bool selection_changed = false; + if (state == GLSelectionRectangle::Select) + { + bool contains_all = true; + for (int i : m_hover_volume_idxs) + { + if (!m_selection.contains_volume((unsigned int)i)) + { + contains_all = false; + break; + } + } + + // the selection is going to be modified (Add) + if (!contains_all) + { + wxGetApp().plater()->take_snapshot(_(L("Selection-Add from rectangle"))); + selection_changed = true; + } + } + else + { + bool contains_any = false; + for (int i : m_hover_volume_idxs) + { + if (m_selection.contains_volume((unsigned int)i)) + { + contains_any = true; + break; + } + } + + // the selection is going to be modified (Remove) + if (contains_any) + { + wxGetApp().plater()->take_snapshot(_(L("Selection-Remove from rectangle"))); + selection_changed = true; + } + } + + if (!selection_changed) + return; + + Plater::SuppressSnapshots suppress(wxGetApp().plater()); + if ((state == GLSelectionRectangle::Select) && !ctrl_pressed) m_selection.clear(); @@ -5808,13 +5765,29 @@ void GLCanvas3D::_update_selection_from_hover() if (m_selection.is_empty()) m_gizmos.reset_all_states(); else - m_gizmos.refresh_on_off_state(m_selection); + m_gizmos.refresh_on_off_state(); - m_gizmos.update_data(*this); + m_gizmos.update_data(); post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); m_dirty = true; } +bool GLCanvas3D::_deactivate_undo_redo_toolbar_items() +{ + if (m_undoredo_toolbar.is_item_pressed("undo")) + { + m_undoredo_toolbar.force_right_action(m_undoredo_toolbar.get_item_id("undo"), *this); + return true; + } + else if (m_undoredo_toolbar.is_item_pressed("redo")) + { + m_undoredo_toolbar.force_right_action(m_undoredo_toolbar.get_item_id("redo"), *this); + return true; + } + + return false; +} + const Print* GLCanvas3D::fff_print() const { return (m_process == nullptr) ? nullptr : m_process->fff_print(); @@ -5825,5 +5798,14 @@ const SLAPrint* GLCanvas3D::sla_print() const return (m_process == nullptr) ? nullptr : m_process->sla_print(); } +void GLCanvas3D::WipeTowerInfo::apply_wipe_tower() const +{ + DynamicPrintConfig cfg; + cfg.opt("wipe_tower_x", true)->value = m_pos(X); + cfg.opt("wipe_tower_y", true)->value = m_pos(Y); + cfg.opt("wipe_tower_rotation_angle", true)->value = (180./M_PI) * m_rotation; + wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 51a36cf69f..d23e29478e 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -4,7 +4,6 @@ #include #include -#include "libslic3r/ModelArrange.hpp" #include "3DScene.hpp" #include "GLToolbar.hpp" #include "Event.hpp" @@ -12,6 +11,7 @@ #include "Camera.hpp" #include "Selection.hpp" #include "Gizmos/GLGizmosManager.hpp" +#include "GUI_ObjectLayers.hpp" #include @@ -124,6 +124,10 @@ wxDECLARE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); class GLCanvas3D { @@ -154,32 +158,6 @@ class GLCanvas3D void reset() { first_volumes.clear(); } }; -#if !ENABLE_TEXTURES_FROM_SVG - class Shader - { - GLShader* m_shader; - - public: - Shader(); - ~Shader(); - - bool init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename); - - bool is_initialized() const; - - bool start_using() const; - void stop_using() const; - - void set_uniform(const std::string& name, float value) const; - void set_uniform(const std::string& name, const float* matrix) const; - - const GLShader* get_shader() const; - - private: - void _reset(); - }; -#endif // !ENABLE_TEXTURES_FROM_SVG - class LayersEditing { public: @@ -195,7 +173,6 @@ class GLCanvas3D static const float THICKNESS_BAR_WIDTH; static const float THICKNESS_RESET_BUTTON_HEIGHT; - bool m_use_legacy_opengl; bool m_enabled; Shader m_shader; unsigned int m_z_texture_id; @@ -248,7 +225,6 @@ class GLCanvas3D void select_object(const Model &model, int object_id); bool is_allowed() const; - void set_use_legacy_opengl(bool use_legacy_opengl); bool is_enabled() const; void set_enabled(bool enabled); @@ -374,7 +350,7 @@ class GLCanvas3D std::vector m_warnings; // Generates the texture with given text. - bool _generate(const std::string& msg, const GLCanvas3D& canvas, const bool red_colored = false); + bool generate(const std::string& msg, const GLCanvas3D& canvas, bool compress, bool red_colored = false); }; class LegendTexture : public GUI::GLTexture @@ -397,7 +373,7 @@ class GLCanvas3D void fill_color_print_legend_values(const GCodePreviewData& preview_data, const GLCanvas3D& canvas, std::vector>& cp_legend_values); - bool generate(const GCodePreviewData& preview_data, const std::vector& tool_colors, const GLCanvas3D& canvas); + bool generate(const GCodePreviewData& preview_data, const std::vector& tool_colors, const GLCanvas3D& canvas, bool compress); void render(const GLCanvas3D& canvas) const; }; @@ -435,12 +411,14 @@ private: Shader m_shader; Mouse m_mouse; mutable GLGizmosManager m_gizmos; - mutable GLToolbar m_toolbar; + mutable GLToolbar m_main_toolbar; + mutable GLToolbar m_undoredo_toolbar; ClippingPlane m_clipping_planes[2]; mutable ClippingPlane m_camera_clipping_plane; bool m_use_clipping_planes; mutable SlaCap m_sla_caps[2]; std::string m_sidebar_field; + bool m_keep_dirty; mutable GLVolumeCollection m_volumes; Selection m_selection; @@ -451,7 +429,6 @@ private: // Screen is only refreshed from the OnIdle handler if it is dirty. bool m_dirty; bool m_initialized; - bool m_use_VBOs; bool m_apply_zoom_to_volumes_filter; mutable std::vector m_hover_volume_idxs; bool m_warning_texture_enabled; @@ -460,7 +437,6 @@ private: bool m_moving_enabled; bool m_dynamic_background_enabled; bool m_multisample_allowed; - bool m_regenerate_volumes; bool m_moving; bool m_tab_down; ECursorType m_cursor_type; @@ -476,10 +452,16 @@ private: GCodePreviewVolumeIndex m_gcode_preview_volume_index; +#if ENABLE_RENDER_PICKING_PASS + bool m_show_picking_texture; +#endif // ENABLE_RENDER_PICKING_PASS + #if ENABLE_RENDER_STATISTICS RenderStats m_render_stats; #endif // ENABLE_RENDER_STATISTICS + int m_imgui_undo_redo_hovered_pos{ -1 }; + public: GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar); ~GLCanvas3D(); @@ -489,7 +471,7 @@ public: wxGLCanvas* get_wxglcanvas() { return m_canvas; } const wxGLCanvas* get_wxglcanvas() const { return m_canvas; } - bool init(bool useVBOs, bool use_legacy_opengl); + bool init(); void post_event(wxEvent &&event); void set_as_dirty(); @@ -500,6 +482,8 @@ public: void toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1); void toggle_model_objects_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1); + void update_instance_printable_state_for_object(size_t obj_idx); + void update_instance_printable_state_for_objects(std::vector& object_idxs); void set_config(const DynamicPrintConfig* config); void set_process(BackgroundSlicingProcess* process); @@ -508,6 +492,9 @@ public: const Selection& get_selection() const { return m_selection; } Selection& get_selection() { return m_selection; } + const GLGizmosManager& get_gizmos_manager() const { return m_gizmos; } + GLGizmosManager& get_gizmos_manager() { return m_gizmos; } + void bed_shape_changed(); void set_clipping_plane(unsigned int id, const ClippingPlane& plane) @@ -539,7 +526,8 @@ public: void enable_moving(bool enable); void enable_gizmos(bool enable); void enable_selection(bool enable); - void enable_toolbar(bool enable); + void enable_main_toolbar(bool enable); + void enable_undoredo_toolbar(bool enable); void enable_dynamic_background(bool enable); void allow_multisample(bool allow); @@ -591,18 +579,20 @@ public: void set_tooltip(const std::string& tooltip) const; - void do_move(); - void do_rotate(); - void do_scale(); - void do_flatten(); - void do_mirror(); + // the following methods add a snapshot to the undo/redo stack, unless the given string is empty + void do_move(const std::string& snapshot_type); + void do_rotate(const std::string& snapshot_type); + void do_scale(const std::string& snapshot_type); + void do_flatten(const Vec3d& normal, const std::string& snapshot_type); + void do_mirror(const std::string& snapshot_type); - void set_camera_zoom(float zoom); + void set_camera_zoom(double zoom); void update_gizmos_on_off_state(); void reset_all_gizmos() { m_gizmos.reset_all_states(); } void handle_sidebar_focus_event(const std::string& opt_key, bool focus_on); + void handle_layers_data_focus_event(const t_layer_height_range range, const EditorType type); void update_ui_from_settings(); @@ -610,15 +600,33 @@ public: int get_move_volume_id() const { return m_mouse.drag.move_volume_idx; } int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); } - - arr::WipeTowerInfo get_wipe_tower_info() const; - void arrange_wipe_tower(const arr::WipeTowerInfo& wti) const; + + class WipeTowerInfo { + protected: + Vec2d m_pos = {std::nan(""), std::nan("")}; + Vec2d m_bb_size = {0., 0.}; + double m_rotation = 0.; + friend class GLCanvas3D; + public: + + inline operator bool() const + { + return !std::isnan(m_pos.x()) && !std::isnan(m_pos.y()); + } + + inline const Vec2d& pos() const { return m_pos; } + inline double rotation() const { return m_rotation; } + inline const Vec2d bb_size() const { return m_bb_size; } + + void apply_wipe_tower() const; + }; + + WipeTowerInfo get_wipe_tower_info() const; // Returns the view ray line, in world coordinate, at the given mouse position. Linef3 mouse_ray(const Point& mouse_pos); void set_mouse_as_dragging() { m_mouse.dragging = true; } - void disable_regenerate_volumes() { m_regenerate_volumes = false; } void refresh_camera_scene_box() { m_camera.set_scene_box(scene_bounding_box()); } bool is_mouse_dragging() const { return m_mouse.dragging; } @@ -627,18 +635,31 @@ public: void set_cursor(ECursorType type); void msw_rescale(); + void start_keeping_dirty() { m_keep_dirty = true; } + void stop_keeping_dirty() { m_keep_dirty = false; } + + unsigned int get_main_toolbar_item_id(const std::string& name) const { return m_main_toolbar.get_item_id(name); } + void force_main_toolbar_left_action(unsigned int item_id) { m_main_toolbar.force_left_action(item_id, *this); } + void force_main_toolbar_right_action(unsigned int item_id) { m_main_toolbar.force_right_action(item_id, *this); } + void get_undoredo_toolbar_additional_tooltip(unsigned int item_id, std::string& text) { return m_undoredo_toolbar.get_additional_tooltip(item_id, text); } + void set_undoredo_toolbar_additional_tooltip(unsigned int item_id, const std::string& text) { m_undoredo_toolbar.set_additional_tooltip(item_id, text); } + + bool has_toolpaths_to_export() const; + void export_toolpaths_to_obj(const char* filename) const; + private: bool _is_shown_on_screen() const; - bool _init_toolbar(); + bool _init_toolbars(); + bool _init_main_toolbar(); + bool _init_undoredo_toolbar(); bool _set_current(); void _resize(unsigned int w, unsigned int h); - BoundingBoxf3 _max_bounding_box() const; + BoundingBoxf3 _max_bounding_box(bool include_gizmos, bool include_bed_model) const; - void _zoom_to_bounding_box(const BoundingBoxf3& bbox); - float _get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) const; + void _zoom_to_box(const BoundingBoxf3& box); void _refresh_if_shown_on_screen(); @@ -646,24 +667,26 @@ private: void _rectangular_selection_picking_pass() const; void _render_background() const; void _render_bed(float theta) const; - void _render_axes() const; void _render_objects() const; void _render_selection() const; #if ENABLE_RENDER_SELECTION_CENTER void _render_selection_center() const; #endif // ENABLE_RENDER_SELECTION_CENTER + void _render_overlays() const; void _render_warning_texture() const; void _render_legend_texture() const; void _render_volumes_for_picking() const; void _render_current_gizmo() const; void _render_gizmos_overlay() const; - void _render_toolbar() const; + void _render_main_toolbar() const; + void _render_undoredo_toolbar() const; void _render_view_toolbar() const; #if ENABLE_SHOW_CAMERA_TARGET void _render_camera_target() const; #endif // ENABLE_SHOW_CAMERA_TARGET void _render_sla_slices() const; void _render_selection_sidebar_hints() const; + void _render_undo_redo_stack(const bool is_undo, float pos_x); void _update_volumes_hover_state() const; @@ -703,8 +726,8 @@ private: void _load_gcode_unretractions(const GCodePreviewData& preview_data); // generates objects and wipe tower geometry void _load_fff_shells(); - // generates objects geometry for sla - void _load_sla_shells(); + // Load SLA objects and support structures for objects, for which the slaposSliceSupports step has been finished. + void _load_sla_shells(); // sets gcode geometry visibility according to user selection void _update_gcode_volumes_visibility(const GCodePreviewData& preview_data); void _update_toolpath_volumes_outside_state(); @@ -719,13 +742,11 @@ private: bool _is_any_volume_outside() const; -#if !ENABLE_SVG_ICONS - void _resize_toolbars() const; -#endif // !ENABLE_SVG_ICONS - // updates the selection from the content of m_hover_volume_idxs void _update_selection_from_hover(); + bool _deactivate_undo_redo_toolbar_items(); + static std::vector _parse_colors(const std::vector& colors); public: diff --git a/src/slic3r/GUI/GLCanvas3DManager.cpp b/src/slic3r/GUI/GLCanvas3DManager.cpp index e409bed0dd..a764fec664 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -15,40 +15,112 @@ #include #include +#ifdef __APPLE__ +#include "../Utils/MacDarkMode.hpp" +#endif // __APPLE__ + namespace Slic3r { namespace GUI { GLCanvas3DManager::GLInfo::GLInfo() - : version("") - , glsl_version("") - , vendor("") - , renderer("") + : m_detected(false) + , m_version("") + , m_glsl_version("") + , m_vendor("") + , m_renderer("") + , m_max_tex_size(0) + , m_max_anisotropy(0.0f) { } -void GLCanvas3DManager::GLInfo::detect() +const std::string& GLCanvas3DManager::GLInfo::get_version() const +{ + if (!m_detected) + detect(); + + return m_version; +} + +const std::string& GLCanvas3DManager::GLInfo::get_glsl_version() const +{ + if (!m_detected) + detect(); + + return m_glsl_version; +} + +const std::string& GLCanvas3DManager::GLInfo::get_vendor() const +{ + if (!m_detected) + detect(); + + return m_vendor; +} + +const std::string& GLCanvas3DManager::GLInfo::get_renderer() const +{ + if (!m_detected) + detect(); + + return m_renderer; +} + +int GLCanvas3DManager::GLInfo::get_max_tex_size() const +{ + if (!m_detected) + detect(); + + // clamp to avoid the texture generation become too slow and use too much GPU memory +#ifdef __APPLE__ + // and use smaller texture for non retina systems + return (Slic3r::GUI::mac_max_scaling_factor() > 1.0) ? std::min(m_max_tex_size, 8192) : std::min(m_max_tex_size / 2, 4096); +#else + // and use smaller texture for older OpenGL versions + return is_version_greater_or_equal_to(3, 0) ? std::min(m_max_tex_size, 8192) : std::min(m_max_tex_size / 2, 4096); +#endif // __APPLE__ +} + +float GLCanvas3DManager::GLInfo::get_max_anisotropy() const +{ + if (!m_detected) + detect(); + + return m_max_anisotropy; +} + +void GLCanvas3DManager::GLInfo::detect() const { const char* data = (const char*)::glGetString(GL_VERSION); if (data != nullptr) - version = data; + m_version = data; data = (const char*)::glGetString(GL_SHADING_LANGUAGE_VERSION); if (data != nullptr) - glsl_version = data; + m_glsl_version = data; data = (const char*)::glGetString(GL_VENDOR); if (data != nullptr) - vendor = data; + m_vendor = data; data = (const char*)::glGetString(GL_RENDERER); if (data != nullptr) - renderer = data; + m_renderer = data; + + glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_max_tex_size)); + + if (GLEW_EXT_texture_filter_anisotropic) + glsafe(::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &m_max_anisotropy)); + + m_detected = true; } bool GLCanvas3DManager::GLInfo::is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const { + if (!m_detected) + detect(); + std::vector tokens; - boost::split(tokens, version, boost::is_any_of(" "), boost::token_compress_on); + boost::split(tokens, m_version, boost::is_any_of(" "), boost::token_compress_on); if (tokens.empty()) return false; @@ -75,6 +147,9 @@ bool GLCanvas3DManager::GLInfo::is_version_greater_or_equal_to(unsigned int majo std::string GLCanvas3DManager::GLInfo::to_string(bool format_as_html, bool extensions) const { + if (!m_detected) + detect(); + std::stringstream out; std::string h2_start = format_as_html ? "" : ""; @@ -84,10 +159,10 @@ std::string GLCanvas3DManager::GLInfo::to_string(bool format_as_html, bool exten std::string line_end = format_as_html ? "
" : "\n"; out << h2_start << "OpenGL installation" << h2_end << line_end; - out << b_start << "GL version: " << b_end << (version.empty() ? "N/A" : version) << line_end; - out << b_start << "Vendor: " << b_end << (vendor.empty() ? "N/A" : vendor) << line_end; - out << b_start << "Renderer: " << b_end << (renderer.empty() ? "N/A" : renderer) << line_end; - out << b_start << "GLSL version: " << b_end << (glsl_version.empty() ? "N/A" : glsl_version) << line_end; + out << b_start << "GL version: " << b_end << (m_version.empty() ? "N/A" : m_version) << line_end; + out << b_start << "Vendor: " << b_end << (m_vendor.empty() ? "N/A" : m_vendor) << line_end; + out << b_start << "Renderer: " << b_end << (m_renderer.empty() ? "N/A" : m_renderer) << line_end; + out << b_start << "GLSL version: " << b_end << (m_glsl_version.empty() ? "N/A" : m_glsl_version) << line_end; if (extensions) { @@ -111,22 +186,18 @@ std::string GLCanvas3DManager::GLInfo::to_string(bool format_as_html, bool exten } GLCanvas3DManager::EMultisampleState GLCanvas3DManager::s_multisample = GLCanvas3DManager::MS_Unknown; +bool GLCanvas3DManager::s_compressed_textures_supported = false; +GLCanvas3DManager::GLInfo GLCanvas3DManager::s_gl_info; GLCanvas3DManager::GLCanvas3DManager() : m_context(nullptr) , m_gl_initialized(false) - , m_use_legacy_opengl(false) - , m_use_VBOs(false) { } GLCanvas3DManager::~GLCanvas3DManager() { - if (m_context != nullptr) - { - delete m_context; - m_context = nullptr; - } + this->destroy(); } bool GLCanvas3DManager::add(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar) @@ -134,7 +205,7 @@ bool GLCanvas3DManager::add(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLTo if (canvas == nullptr) return false; - if (_get_canvas(canvas) != m_canvases.end()) + if (do_get_canvas(canvas) != m_canvases.end()) return false; GLCanvas3D* canvas3D = new GLCanvas3D(canvas, bed, camera, view_toolbar); @@ -159,7 +230,7 @@ bool GLCanvas3DManager::add(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLTo bool GLCanvas3DManager::remove(wxGLCanvas* canvas) { - CanvasesMap::iterator it = _get_canvas(canvas); + CanvasesMap::iterator it = do_get_canvas(canvas); if (it == m_canvases.end()) return false; @@ -190,77 +261,92 @@ void GLCanvas3DManager::init_gl() if (!m_gl_initialized) { glewInit(); - m_gl_info.detect(); - const AppConfig* config = GUI::get_app_config(); - m_use_legacy_opengl = (config == nullptr) || (config->get("use_legacy_opengl") == "1"); - m_use_VBOs = !m_use_legacy_opengl && m_gl_info.is_version_greater_or_equal_to(2, 0); m_gl_initialized = true; + if (GLEW_EXT_texture_compression_s3tc) + s_compressed_textures_supported = true; + else + s_compressed_textures_supported = false; } } -std::string GLCanvas3DManager::get_gl_info(bool format_as_html, bool extensions) const -{ - return m_gl_info.to_string(format_as_html, extensions); -} - bool GLCanvas3DManager::init(wxGLCanvas* canvas) { - CanvasesMap::const_iterator it = _get_canvas(canvas); + CanvasesMap::const_iterator it = do_get_canvas(canvas); if (it != m_canvases.end()) - return (it->second != nullptr) ? _init(*it->second) : false; + return (it->second != nullptr) ? init(*it->second) : false; else return false; } +void GLCanvas3DManager::destroy() +{ + if (m_context != nullptr) + { + delete m_context; + m_context = nullptr; + } +} + GLCanvas3D* GLCanvas3DManager::get_canvas(wxGLCanvas* canvas) { - CanvasesMap::const_iterator it = _get_canvas(canvas); + CanvasesMap::const_iterator it = do_get_canvas(canvas); return (it != m_canvases.end()) ? it->second : nullptr; } wxGLCanvas* GLCanvas3DManager::create_wxglcanvas(wxWindow *parent) { - int attribList[] = { WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 24, WX_GL_SAMPLE_BUFFERS, GL_TRUE, WX_GL_SAMPLES, 4, 0 }; + int attribList[] = { + WX_GL_RGBA, + WX_GL_DOUBLEBUFFER, + // RGB channels each should be allocated with 8 bit depth. One should almost certainly get these bit depths by default. + WX_GL_MIN_RED, 8, + WX_GL_MIN_GREEN, 8, + WX_GL_MIN_BLUE, 8, + // Requesting an 8 bit alpha channel. Interestingly, the NVIDIA drivers would most likely work with some alpha plane, but glReadPixels would not return + // the alpha channel on NVIDIA if not requested when the GL context is created. + WX_GL_MIN_ALPHA, 8, + WX_GL_DEPTH_SIZE, 24, + WX_GL_SAMPLE_BUFFERS, GL_TRUE, + WX_GL_SAMPLES, 4, + 0 + }; if (s_multisample == MS_Unknown) { - _detect_multisample(attribList); - // debug output - std::cout << "Multisample " << (can_multisample() ? "enabled" : "disabled") << std::endl; + detect_multisample(attribList); +// // debug output +// std::cout << "Multisample " << (can_multisample() ? "enabled" : "disabled") << std::endl; } - if (! can_multisample()) { - attribList[4] = 0; - } + if (! can_multisample()) + attribList[12] = 0; return new wxGLCanvas(parent, wxID_ANY, attribList, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS); } -GLCanvas3DManager::CanvasesMap::iterator GLCanvas3DManager::_get_canvas(wxGLCanvas* canvas) +GLCanvas3DManager::CanvasesMap::iterator GLCanvas3DManager::do_get_canvas(wxGLCanvas* canvas) { return (canvas == nullptr) ? m_canvases.end() : m_canvases.find(canvas); } -GLCanvas3DManager::CanvasesMap::const_iterator GLCanvas3DManager::_get_canvas(wxGLCanvas* canvas) const +GLCanvas3DManager::CanvasesMap::const_iterator GLCanvas3DManager::do_get_canvas(wxGLCanvas* canvas) const { return (canvas == nullptr) ? m_canvases.end() : m_canvases.find(canvas); } -bool GLCanvas3DManager::_init(GLCanvas3D& canvas) +bool GLCanvas3DManager::init(GLCanvas3D& canvas) { if (!m_gl_initialized) init_gl(); - return canvas.init(m_use_VBOs, m_use_legacy_opengl); + return canvas.init(); } -void GLCanvas3DManager::_detect_multisample(int* attribList) +void GLCanvas3DManager::detect_multisample(int* attribList) { int wxVersion = wxMAJOR_VERSION * 10000 + wxMINOR_VERSION * 100 + wxRELEASE_NUMBER; const AppConfig* app_config = GUI::get_app_config(); - bool enable_multisample = app_config != nullptr - && app_config->get("use_legacy_opengl") != "1" - && wxVersion >= 30003; + bool enable_multisample = wxVersion >= 30003; s_multisample = (enable_multisample && wxGLCanvas::IsDisplaySupported(attribList)) ? MS_Enabled : MS_Disabled; // Alternative method: it was working on previous version of wxWidgets but not with the latest, at least on Windows diff --git a/src/slic3r/GUI/GLCanvas3DManager.hpp b/src/slic3r/GUI/GLCanvas3DManager.hpp index 75647e6b25..760266a274 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -29,21 +29,39 @@ struct Camera; class GLCanvas3DManager { - struct GLInfo +public: + class GLInfo { - std::string version; - std::string glsl_version; - std::string vendor; - std::string renderer; + mutable bool m_detected; + mutable std::string m_version; + mutable std::string m_glsl_version; + mutable std::string m_vendor; + mutable std::string m_renderer; + + mutable int m_max_tex_size; + mutable float m_max_anisotropy; + + public: GLInfo(); - void detect(); + const std::string& get_version() const; + const std::string& get_glsl_version() const; + const std::string& get_vendor() const; + const std::string& get_renderer() const; + + int get_max_tex_size() const; + float get_max_anisotropy() const; + bool is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const; std::string to_string(bool format_as_html, bool extensions) const; + + private: + void detect() const; }; +private: enum EMultisampleState : unsigned char { MS_Unknown, @@ -55,11 +73,10 @@ class GLCanvas3DManager CanvasesMap m_canvases; wxGLContext* m_context; - GLInfo m_gl_info; + static GLInfo s_gl_info; bool m_gl_initialized; - bool m_use_legacy_opengl; - bool m_use_VBOs; static EMultisampleState s_multisample; + static bool s_compressed_textures_supported; public: GLCanvas3DManager(); @@ -72,21 +89,25 @@ public: unsigned int count() const; void init_gl(); - std::string get_gl_info(bool format_as_html, bool extensions) const; bool init(wxGLCanvas* canvas); + void destroy(); GLCanvas3D* get_canvas(wxGLCanvas* canvas); static bool can_multisample() { return s_multisample == MS_Enabled; } + static bool are_compressed_textures_supported() { return s_compressed_textures_supported; } + static wxGLCanvas* create_wxglcanvas(wxWindow *parent); -private: - CanvasesMap::iterator _get_canvas(wxGLCanvas* canvas); - CanvasesMap::const_iterator _get_canvas(wxGLCanvas* canvas) const; + static const GLInfo& get_gl_info() { return s_gl_info; } - bool _init(GLCanvas3D& canvas); - static void _detect_multisample(int* attribList); +private: + CanvasesMap::iterator do_get_canvas(wxGLCanvas* canvas); + CanvasesMap::const_iterator do_get_canvas(wxGLCanvas* canvas) const; + + bool init(GLCanvas3D& canvas); + static void detect_multisample(int* attribList); }; } // namespace GUI diff --git a/src/slic3r/GUI/GLSelectionRectangle.cpp b/src/slic3r/GUI/GLSelectionRectangle.cpp index 9684bb5ec9..684563bff3 100644 --- a/src/slic3r/GUI/GLSelectionRectangle.cpp +++ b/src/slic3r/GUI/GLSelectionRectangle.cpp @@ -68,7 +68,8 @@ namespace GUI { if (!is_dragging()) return; - float zoom = canvas.get_camera().zoom; + const Camera& camera = canvas.get_camera(); + float zoom = (float)camera.get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; Size cnv_size = canvas.get_canvas_size(); @@ -96,6 +97,11 @@ namespace GUI { glsafe(::glPushMatrix()); glsafe(::glLoadIdentity()); + // ensure that the rectangle is renderered inside the frustrum + glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.5))); + // ensure that the overlay fits the frustrum near z plane + double gui_scale = camera.get_gui_scale(); + glsafe(::glScaled(gui_scale, gui_scale, 1.0)); glsafe(::glPushAttrib(GL_ENABLE_BIT)); glsafe(::glLineStipple(4, 0xAAAA)); diff --git a/src/slic3r/GUI/GLShader.cpp b/src/slic3r/GUI/GLShader.cpp index 7f3bc0893a..577f6e1b50 100644 --- a/src/slic3r/GUI/GLShader.cpp +++ b/src/slic3r/GUI/GLShader.cpp @@ -268,7 +268,6 @@ sub SetMatrix } */ -#if ENABLE_TEXTURES_FROM_SVG Shader::Shader() : m_shader(nullptr) { @@ -363,6 +362,5 @@ void Shader::reset() m_shader = nullptr; } } -#endif // ENABLE_TEXTURES_FROM_SVG } // namespace Slic3r diff --git a/src/slic3r/GUI/GLShader.hpp b/src/slic3r/GUI/GLShader.hpp index 58e2678d03..df2a23f15c 100644 --- a/src/slic3r/GUI/GLShader.hpp +++ b/src/slic3r/GUI/GLShader.hpp @@ -37,7 +37,6 @@ public: std::string last_error; }; -#if ENABLE_TEXTURES_FROM_SVG class Shader { GLShader* m_shader; @@ -66,7 +65,6 @@ public: private: void reset(); }; -#endif // ENABLE_TEXTURES_FROM_SVG } diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index 2fff0869ad..4fdf12489d 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -12,6 +12,10 @@ #include #include +#include + +#define STB_DXT_IMPLEMENTATION +#include "stb_dxt/stb_dxt.h" #include "nanosvg/nanosvg.h" #include "nanosvg/nanosvgrast.h" @@ -21,6 +25,100 @@ namespace Slic3r { namespace GUI { +void GLTexture::Compressor::reset() +{ + if (m_thread.joinable()) { + m_abort_compressing = true; + m_thread.join(); + m_levels.clear(); + m_num_levels_compressed = 0; + m_abort_compressing = false; + } + assert(m_levels.empty()); + assert(m_abort_compressing == false); + assert(m_num_levels_compressed == 0); +} + +void GLTexture::Compressor::start_compressing() +{ + // The worker thread should be stopped already. + assert(! m_thread.joinable()); + assert(! m_levels.empty()); + assert(m_abort_compressing == false); + assert(m_num_levels_compressed == 0); + if (! m_levels.empty()) { + std::thread thrd(&GLTexture::Compressor::compress, this); + m_thread = std::move(thrd); + } +} + +bool GLTexture::Compressor::unsent_compressed_data_available() const +{ + if (m_levels.empty()) + return false; + // Querying the atomic m_num_levels_compressed value synchronizes processor caches, so that the dat of m_levels modified by the worker thread are accessible to the calling thread. + unsigned int num_compressed = m_num_levels_compressed; + for (unsigned int i = 0; i < num_compressed; ++ i) + if (! m_levels[i].sent_to_gpu && ! m_levels[i].compressed_data.empty()) + return true; + return false; +} + +void GLTexture::Compressor::send_compressed_data_to_gpu() +{ + // this method should be called inside the main thread of Slicer or a new OpenGL context (sharing resources) would be needed + if (m_levels.empty()) + return; + + glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); + glsafe(::glBindTexture(GL_TEXTURE_2D, m_texture.m_id)); + // Querying the atomic m_num_levels_compressed value synchronizes processor caches, so that the dat of m_levels modified by the worker thread are accessible to the calling thread. + int num_compressed = (int)m_num_levels_compressed; + for (int i = 0; i < num_compressed; ++ i) + { + Level& level = m_levels[i]; + if (! level.sent_to_gpu && ! level.compressed_data.empty()) + { + glsafe(::glCompressedTexSubImage2D(GL_TEXTURE_2D, (GLint)i, 0, 0, (GLsizei)level.w, (GLsizei)level.h, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)level.compressed_data.size(), (const GLvoid*)level.compressed_data.data())); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, i)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (i > 0) ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR)); + level.sent_to_gpu = true; + // we are done with the compressed data, we can discard it + level.compressed_data.clear(); + } + } + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); + + if (num_compressed == (unsigned int)m_levels.size()) + // Finalize the worker thread, close it. + this->reset(); +} + +void GLTexture::Compressor::compress() +{ + // reference: https://github.com/Cyan4973/RygsDXTc + + assert(m_num_levels_compressed == 0); + assert(m_abort_compressing == false); + + for (Level& level : m_levels) + { + if (m_abort_compressing) + break; + + // stb_dxt library, despite claiming that the needed size of the destination buffer is equal to (source buffer size)/4, + // crashes if doing so, so we start with twice the required size + level.compressed_data = std::vector(level.w * level.h * 2, 0); + int compressed_size = 0; + rygCompress(level.compressed_data.data(), level.src_data.data(), level.w, level.h, 1, compressed_size); + level.compressed_data.resize(compressed_size); + + // we are done with the source data, we can discard it + level.src_data.clear(); + ++ m_num_levels_compressed; + } +} + GLTexture::Quad_UVs GLTexture::FullTextureUVs = { { 0.0f, 1.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f }, { 0.0f, 0.0f } }; GLTexture::GLTexture() @@ -28,6 +126,7 @@ GLTexture::GLTexture() , m_width(0) , m_height(0) , m_source("") + , m_compressor(*this) { } @@ -36,7 +135,7 @@ GLTexture::~GLTexture() reset(); } -bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps) +bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy) { reset(); @@ -44,12 +143,12 @@ bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps) return false; if (boost::algorithm::iends_with(filename, ".png")) - return load_from_png(filename, use_mipmaps); + return load_from_png(filename, use_mipmaps, compression_type, apply_anisotropy); else return false; } -bool GLTexture::load_from_svg_file(const std::string& filename, bool use_mipmaps, unsigned int max_size_px) +bool GLTexture::load_from_svg_file(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px) { reset(); @@ -57,12 +156,12 @@ bool GLTexture::load_from_svg_file(const std::string& filename, bool use_mipmaps return false; if (boost::algorithm::iends_with(filename, ".svg")) - return load_from_svg(filename, use_mipmaps, max_size_px); + return load_from_svg(filename, use_mipmaps, compress, apply_anisotropy, max_size_px); else return false; } -bool GLTexture::load_from_svg_files_as_sprites_array(const std::vector& filenames, const std::vector>& states, unsigned int sprite_size_px) +bool GLTexture::load_from_svg_files_as_sprites_array(const std::vector& filenames, const std::vector>& states, unsigned int sprite_size_px, bool compress) { reset(); @@ -178,7 +277,10 @@ bool GLTexture::load_from_svg_files_as_sprites_array(const std::vector data(w * h * 4, 0); + bool compression_enabled = (compression_type != None) && GLEW_EXT_texture_compression_s3tc; - while ((w > 1) || (h > 1)) - { - ++level; - - w = std::max(w / 2, 1); - h = std::max(h / 2, 1); - - int n_pixels = w * h; - - image = image.ResampleBicubic(w, h); - - unsigned char* img_rgb = image.GetData(); - unsigned char* img_alpha = image.GetAlpha(); - - data.resize(n_pixels * 4); - for (int i = 0; i < n_pixels; ++i) - { - int data_id = i * 4; - int img_id = i * 3; - data[data_id + 0] = img_rgb[img_id + 0]; - data[data_id + 1] = img_rgb[img_id + 1]; - data[data_id + 2] = img_rgb[img_id + 2]; - data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255; - } - - glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)w, (GLsizei)h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); - } - - return (unsigned int)level; -} - -bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps) -{ // Load a PNG with an alpha channel. wxImage image; if (!image.LoadFile(wxString::FromUTF8(filename.c_str()), wxBITMAP_TYPE_PNG)) @@ -302,8 +369,32 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps) m_width = image.GetWidth(); m_height = image.GetHeight(); - int n_pixels = m_width * m_height; + bool requires_rescale = false; + + if (compression_enabled && (compression_type == MultiThreaded)) + { + // the stb_dxt compression library seems to like only texture sizes which are a multiple of 4 + int width_rem = m_width % 4; + int height_rem = m_height % 4; + + if (width_rem != 0) + { + m_width += (4 - width_rem); + requires_rescale = true; + } + + if (height_rem != 0) + { + m_height += (4 - height_rem); + requires_rescale = true; + } + } + + if (requires_rescale) + image = image.ResampleBicubic(m_width, m_height); + + int n_pixels = m_width * m_height; if (n_pixels <= 0) { reset(); @@ -335,34 +426,109 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps) glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); glsafe(::glGenTextures(1, &m_id)); glsafe(::glBindTexture(GL_TEXTURE_2D, m_id)); - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); + + if (apply_anisotropy) + { + GLfloat max_anisotropy = GLCanvas3DManager::get_gl_info().get_max_anisotropy(); + if (max_anisotropy > 1.0f) + glsafe(::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy)); + } + + if (compression_enabled) + { + if (compression_type == SingleThreaded) + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); + else + { + // initializes the texture on GPU + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); + // and send the uncompressed data to the compressor + m_compressor.add_level((unsigned int)m_width, (unsigned int)m_height, data); + } + } + else + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); + if (use_mipmaps) { // we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards - unsigned int levels_count = generate_mipmaps(image); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, levels_count)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); + int lod_w = m_width; + int lod_h = m_height; + GLint level = 0; + // we do not need to generate all levels down to 1x1 + while ((lod_w > 16) || (lod_h > 16)) + { + ++level; + + lod_w = std::max(lod_w / 2, 1); + lod_h = std::max(lod_h / 2, 1); + n_pixels = lod_w * lod_h; + + image = image.ResampleBicubic(lod_w, lod_h); + + data.resize(n_pixels * 4); + + img_rgb = image.GetData(); + img_alpha = image.GetAlpha(); + + for (int i = 0; i < n_pixels; ++i) + { + int data_id = i * 4; + int img_id = i * 3; + data[data_id + 0] = img_rgb[img_id + 0]; + data[data_id + 1] = img_rgb[img_id + 1]; + data[data_id + 2] = img_rgb[img_id + 2]; + data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255; + } + + if (compression_enabled) + { + if (compression_type == SingleThreaded) + glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); + else + { + // initializes the texture on GPU + glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); + // and send the uncompressed data to the compressor + m_compressor.add_level((unsigned int)lod_w, (unsigned int)lod_h, data); + } + } + else + glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); + } + + if (!compression_enabled) + { + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); + } } else { glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); } + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); m_source = filename; + if (compression_enabled && (compression_type == MultiThreaded)) + // start asynchronous compression + m_compressor.start_compressing(); + return true; } -bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, unsigned int max_size_px) +bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px) { + bool compression_enabled = compress && GLEW_EXT_texture_compression_s3tc; + NSVGimage* image = nsvgParseFromFile(filename.c_str(), "px", 96.0f); if (image == nullptr) { -// printf("Could not open SVG image.\n"); reset(); return false; } @@ -371,6 +537,20 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns m_width = (int)(scale * image->width); m_height = (int)(scale * image->height); + + if (compression_enabled) + { + // the stb_dxt compression library seems to like only texture sizes which are a multiple of 4 + int width_rem = m_width % 4; + int height_rem = m_height % 4; + + if (width_rem != 0) + m_width += (4 - width_rem); + + if (height_rem != 0) + m_height += (4 - height_rem); + } + int n_pixels = m_width * m_height; if (n_pixels <= 0) @@ -383,7 +563,6 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns NSVGrasterizer* rast = nsvgCreateRasterizer(); if (rast == nullptr) { -// printf("Could not init rasterizer.\n"); nsvgDelete(image); reset(); return false; @@ -397,14 +576,32 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); glsafe(::glGenTextures(1, &m_id)); glsafe(::glBindTexture(GL_TEXTURE_2D, m_id)); - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); + + if (apply_anisotropy) + { + GLfloat max_anisotropy = GLCanvas3DManager::get_gl_info().get_max_anisotropy(); + if (max_anisotropy > 1.0f) + glsafe(::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy)); + } + + if (compression_enabled) + { + // initializes the texture on GPU + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); + // and send the uncompressed data to the compressor + m_compressor.add_level((unsigned int)m_width, (unsigned int)m_height, data); + } + else + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); + if (use_mipmaps) { // we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards int lod_w = m_width; int lod_h = m_height; GLint level = 0; - while ((lod_w > 1) || (lod_h > 1)) + // we do not need to generate all levels down to 1x1 + while ((lod_w > 16) || (lod_h > 16)) { ++level; @@ -412,24 +609,42 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns lod_h = std::max(lod_h / 2, 1); scale /= 2.0f; + data.resize(lod_w * lod_h * 4); + nsvgRasterize(rast, image, 0, 0, scale, data.data(), lod_w, lod_h, lod_w * 4); - glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); + if (compression_enabled) + { + // initializes the texture on GPU + glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); + // and send the uncompressed data to the compressor + m_compressor.add_level((unsigned int)lod_w, (unsigned int)lod_h, data); + } + else + glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); } - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); + if (!compression_enabled) + { + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); + } } else { glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); } + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); m_source = filename; + if (compression_enabled) + // start asynchronous compression + m_compressor.start_compressing(); + nsvgDeleteRasterizer(rast); nsvgDelete(image); diff --git a/src/slic3r/GUI/GLTexture.hpp b/src/slic3r/GUI/GLTexture.hpp index e00b3a3bea..c4063b93d4 100644 --- a/src/slic3r/GUI/GLTexture.hpp +++ b/src/slic3r/GUI/GLTexture.hpp @@ -1,8 +1,10 @@ #ifndef slic3r_GLTexture_hpp_ #define slic3r_GLTexture_hpp_ +#include #include #include +#include class wxImage; @@ -11,7 +13,55 @@ namespace GUI { class GLTexture { + class Compressor + { + struct Level + { + unsigned int w; + unsigned int h; + std::vector src_data; + std::vector compressed_data; + bool sent_to_gpu; + + Level(unsigned int w, unsigned int h, const std::vector& data) : w(w), h(h), src_data(data), sent_to_gpu(false) {} + }; + + GLTexture& m_texture; + std::vector m_levels; + std::thread m_thread; + // Does the caller want the background thread to stop? + // This atomic also works as a memory barrier for synchronizing the cancel event with the worker thread. + std::atomic m_abort_compressing; + // How many levels were compressed since the start of the background processing thread? + // This atomic also works as a memory barrier for synchronizing results of the worker thread with the calling thread. + std::atomic m_num_levels_compressed; + + public: + explicit Compressor(GLTexture& texture) : m_texture(texture), m_abort_compressing(false), m_num_levels_compressed(0) {} + ~Compressor() { reset(); } + + void reset(); + + void add_level(unsigned int w, unsigned int h, const std::vector& data) { m_levels.emplace_back(w, h, data); } + + void start_compressing(); + + bool unsent_compressed_data_available() const; + void send_compressed_data_to_gpu(); + bool all_compressed_data_sent_to_gpu() const { return m_levels.empty(); } + + private: + void compress(); + }; + public: + enum ECompressionType : unsigned char + { + None, + SingleThreaded, + MultiThreaded + }; + struct UV { float u; @@ -33,13 +83,14 @@ namespace GUI { int m_width; int m_height; std::string m_source; + Compressor m_compressor; public: GLTexture(); virtual ~GLTexture(); - bool load_from_file(const std::string& filename, bool use_mipmaps); - bool load_from_svg_file(const std::string& filename, bool use_mipmaps, unsigned int max_size_px); + bool load_from_file(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy); + bool load_from_svg_file(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px); // meanings of states: (std::pair) // first field (int): // 0 -> no changes @@ -48,7 +99,7 @@ namespace GUI { // second field (bool): // false -> no changes // true -> add background color - bool load_from_svg_files_as_sprites_array(const std::vector& filenames, const std::vector>& states, unsigned int sprite_size_px); + bool load_from_svg_files_as_sprites_array(const std::vector& filenames, const std::vector>& states, unsigned int sprite_size_px, bool compress); void reset(); unsigned int get_id() const { return m_id; } @@ -57,14 +108,18 @@ namespace GUI { const std::string& get_source() const { return m_source; } + bool unsent_compressed_data_available() const { return m_compressor.unsent_compressed_data_available(); } + void send_compressed_data_to_gpu() { m_compressor.send_compressed_data_to_gpu(); } + bool all_compressed_data_sent_to_gpu() const { return m_compressor.all_compressed_data_sent_to_gpu(); } + static void render_texture(unsigned int tex_id, float left, float right, float bottom, float top); static void render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const Quad_UVs& uvs); - protected: - unsigned int generate_mipmaps(wxImage& image); private: - bool load_from_png(const std::string& filename, bool use_mipmaps); - bool load_from_svg(const std::string& filename, bool use_mipmaps, unsigned int max_size_px); + bool load_from_png(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy); + bool load_from_svg(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px); + + friend class Compressor; }; } // namespace GUI diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 00cbdfec71..63387bf2db 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -34,20 +34,25 @@ wxDEFINE_EVENT(EVT_GLVIEWTOOLBAR_PREVIEW, SimpleEvent); const GLToolbarItem::ActionCallback GLToolbarItem::Default_Action_Callback = [](){}; const GLToolbarItem::VisibilityCallback GLToolbarItem::Default_Visibility_Callback = []()->bool { return true; }; -const GLToolbarItem::EnabledStateCallback GLToolbarItem::Default_Enabled_State_Callback = []()->bool { return true; }; +const GLToolbarItem::EnablingCallback GLToolbarItem::Default_Enabling_Callback = []()->bool { return true; }; +const GLToolbarItem::RenderCallback GLToolbarItem::Default_Render_Callback = [](float, float, float, float){}; + +GLToolbarItem::Data::Option::Option() + : toggable(false) + , action_callback(Default_Action_Callback) + , render_callback(nullptr) +{ +} GLToolbarItem::Data::Data() : name("") -#if ENABLE_SVG_ICONS , icon_filename("") -#endif // ENABLE_SVG_ICONS , tooltip("") + , additional_tooltip("") , sprite_id(-1) - , is_toggable(false) , visible(true) - , action_callback(Default_Action_Callback) , visibility_callback(Default_Visibility_Callback) - , enabled_state_callback(Default_Enabled_State_Callback) + , enabling_callback(Default_Enabling_Callback) { } @@ -55,6 +60,7 @@ GLToolbarItem::GLToolbarItem(GLToolbarItem::EType type, const GLToolbarItem::Dat : m_type(type) , m_state(Normal) , m_data(data) + , m_last_action_type(Undefined) { } @@ -70,7 +76,7 @@ bool GLToolbarItem::update_visibility() bool GLToolbarItem::update_enabled_state() { - bool enabled = m_data.enabled_state_callback(); + bool enabled = m_data.enabling_callback(); bool ret = (is_enabled() != enabled); if (ret) m_state = enabled ? GLToolbarItem::Normal : GLToolbarItem::Disabled; @@ -81,6 +87,14 @@ bool GLToolbarItem::update_enabled_state() void GLToolbarItem::render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const { GLTexture::render_sub_texture(tex_id, left, right, bottom, top, get_uvs(tex_width, tex_height, icon_size)); + + if (is_pressed()) + { + if ((m_last_action_type == Left) && m_data.left.can_render()) + m_data.left.render_callback(left, right, bottom, top); + else if ((m_last_action_type == Right) && m_data.right.can_render()) + m_data.right.render_callback(left, right, bottom, top); + } } GLTexture::Quad_UVs GLToolbarItem::get_uvs(unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const @@ -105,14 +119,6 @@ GLTexture::Quad_UVs GLToolbarItem::get_uvs(unsigned int tex_width, unsigned int return uvs; } -#if !ENABLE_SVG_ICONS -ItemsIconsTexture::Metadata::Metadata() - : filename("") - , icon_size(0) -{ -} -#endif // !ENABLE_SVG_ICONS - BackgroundTexture::Metadata::Metadata() : filename("") , left(0) @@ -122,44 +128,32 @@ BackgroundTexture::Metadata::Metadata() { } -#if ENABLE_SVG_ICONS const float GLToolbar::Default_Icons_Size = 40.0f; -#endif // ENABLE_SVG_ICONS GLToolbar::Layout::Layout() : type(Horizontal) - , orientation(Center) + , horizontal_orientation(HO_Center) + , vertical_orientation(VO_Center) , top(0.0f) , left(0.0f) , border(0.0f) , separator_size(0.0f) , gap_size(0.0f) -#if ENABLE_SVG_ICONS , icons_size(Default_Icons_Size) , scale(1.0f) -#else - , icons_scale(1.0f) -#endif // ENABLE_SVG_ICONS , width(0.0f) , height(0.0f) , dirty(true) { } -#if ENABLE_SVG_ICONS GLToolbar::GLToolbar(GLToolbar::EType type, const std::string& name) -#else -GLToolbar::GLToolbar(GLToolbar::EType type) -#endif // ENABLE_SVG_ICONS : m_type(type) -#if ENABLE_SVG_ICONS , m_name(name) -#endif // ENABLE_SVG_ICONS , m_enabled(false) -#if ENABLE_SVG_ICONS , m_icons_texture_dirty(true) -#endif // ENABLE_SVG_ICONS , m_tooltip("") + , m_pressed_toggable_id(-1) { } @@ -171,30 +165,16 @@ GLToolbar::~GLToolbar() } } -#if ENABLE_SVG_ICONS bool GLToolbar::init(const BackgroundTexture::Metadata& background_texture) -#else -bool GLToolbar::init(const ItemsIconsTexture::Metadata& icons_texture, const BackgroundTexture::Metadata& background_texture) -#endif // ENABLE_SVG_ICONS { -#if ENABLE_SVG_ICONS if (m_background_texture.texture.get_id() != 0) return true; std::string path = resources_dir() + "/icons/"; bool res = false; -#else - if (m_icons_texture.texture.get_id() != 0) - return true; - - std::string path = resources_dir() + "/icons/"; - bool res = !icons_texture.filename.empty() && m_icons_texture.texture.load_from_file(path + icons_texture.filename, false); - if (res) - m_icons_texture.metadata = icons_texture; -#endif // ENABLE_SVG_ICONS if (!background_texture.filename.empty()) - res = m_background_texture.texture.load_from_file(path + background_texture.filename, false); + res = m_background_texture.texture.load_from_file(path + background_texture.filename, false, GLTexture::SingleThreaded, false); if (res) m_background_texture.metadata = background_texture; @@ -213,16 +193,6 @@ void GLToolbar::set_layout_type(GLToolbar::Layout::EType type) m_layout.dirty = true; } -GLToolbar::Layout::EOrientation GLToolbar::get_layout_orientation() const -{ - return m_layout.orientation; -} - -void GLToolbar::set_layout_orientation(GLToolbar::Layout::EOrientation orientation) -{ - m_layout.orientation = orientation; -} - void GLToolbar::set_position(float top, float left) { m_layout.top = top; @@ -247,7 +217,6 @@ void GLToolbar::set_gap_size(float size) m_layout.dirty = true; } -#if ENABLE_SVG_ICONS void GLToolbar::set_icons_size(float size) { if (m_layout.icons_size != size) @@ -267,13 +236,6 @@ void GLToolbar::set_scale(float scale) m_icons_texture_dirty = true; } } -#else -void GLToolbar::set_icons_scale(float scale) -{ - m_layout.icons_scale = scale; - m_layout.dirty = true; -} -#endif // ENABLE_SVG_ICONS bool GLToolbar::is_enabled() const { @@ -341,7 +303,7 @@ void GLToolbar::select_item(const std::string& name) bool GLToolbar::is_item_pressed(const std::string& name) const { - for (GLToolbarItem* item : m_items) + for (const GLToolbarItem* item : m_items) { if (item->get_name() == name) return item->is_pressed(); @@ -352,7 +314,7 @@ bool GLToolbar::is_item_pressed(const std::string& name) const bool GLToolbar::is_item_disabled(const std::string& name) const { - for (GLToolbarItem* item : m_items) + for (const GLToolbarItem* item : m_items) { if (item->get_name() == name) return item->is_disabled(); @@ -363,7 +325,7 @@ bool GLToolbar::is_item_disabled(const std::string& name) const bool GLToolbar::is_item_visible(const std::string& name) const { - for (GLToolbarItem* item : m_items) + for (const GLToolbarItem* item : m_items) { if (item->get_name() == name) return item->is_visible(); @@ -372,11 +334,71 @@ bool GLToolbar::is_item_visible(const std::string& name) const return false; } +bool GLToolbar::is_any_item_pressed() const +{ + for (const GLToolbarItem* item : m_items) + { + if (item->is_pressed()) + return true; + } + + return false; +} + +unsigned int GLToolbar::get_item_id(const std::string& name) const +{ + for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i) + { + if (m_items[i]->get_name() == name) + return i; + } + + return -1; +} + +void GLToolbar::force_left_action(unsigned int item_id, GLCanvas3D& parent) +{ + do_action(GLToolbarItem::Left, item_id, parent, false); +} + +void GLToolbar::force_right_action(unsigned int item_id, GLCanvas3D& parent) +{ + do_action(GLToolbarItem::Right, item_id, parent, false); +} + +void GLToolbar::get_additional_tooltip(unsigned int item_id, std::string& text) +{ + if (item_id < (unsigned int)m_items.size()) + { + GLToolbarItem* item = m_items[item_id]; + if (item != nullptr) + { + text = item->get_additional_tooltip(); + return; + } + } + + text = L(""); +} + +void GLToolbar::set_additional_tooltip(unsigned int item_id, const std::string& text) +{ + if (item_id < (unsigned int)m_items.size()) + { + GLToolbarItem* item = m_items[item_id]; + if (item != nullptr) + item->set_additional_tooltip(text); + } +} + bool GLToolbar::update_items_state() { bool ret = false; ret |= update_items_visibility(); ret |= update_items_enabled_state(); + if (!is_any_item_pressed()) + m_pressed_toggable_id = -1; + return ret; } @@ -385,15 +407,8 @@ void GLToolbar::render(const GLCanvas3D& parent) const if (!m_enabled || m_items.empty()) return; -#if ENABLE_SVG_ICONS if (m_icons_texture_dirty) generate_icons_texture(); -#endif // ENABLE_SVG_ICONS - - glsafe(::glDisable(GL_DEPTH_TEST)); - - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); switch (m_layout.type) { @@ -401,12 +416,13 @@ void GLToolbar::render(const GLCanvas3D& parent) const case Layout::Horizontal: { render_horizontal(parent); break; } case Layout::Vertical: { render_vertical(parent); break; } } - - glsafe(::glPopMatrix()); } bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) { + if (!m_enabled) + return false; + Vec2d mouse_pos((double)evt.GetX(), (double)evt.GetY()); bool processed = false; @@ -437,7 +453,7 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) if (item_id == -1) { // mouse is outside the toolbar - m_tooltip = ""; + m_tooltip = L(""); } else { @@ -447,10 +463,11 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) m_mouse_capture.left = true; m_mouse_capture.parent = &parent; processed = true; - if ((item_id != -2) && !m_items[item_id]->is_separator()) + if ((item_id != -2) && !m_items[item_id]->is_separator() && ((m_pressed_toggable_id == -1) || (m_items[item_id]->get_last_action_type() == GLToolbarItem::Left))) { // mouse is inside an icon - do_action((unsigned int)item_id, parent); + do_action(GLToolbarItem::Left, (unsigned int)item_id, parent, true); + parent.set_as_dirty(); } } else if (evt.MiddleDown()) @@ -462,6 +479,13 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) { m_mouse_capture.right = true; m_mouse_capture.parent = &parent; + processed = true; + if ((item_id != -2) && !m_items[item_id]->is_separator() && ((m_pressed_toggable_id == -1) || (m_items[item_id]->get_last_action_type() == GLToolbarItem::Right))) + { + // mouse is inside an icon + do_action(GLToolbarItem::Right, (unsigned int)item_id, parent, true); + parent.set_as_dirty(); + } } else if (evt.LeftUp()) processed = true; @@ -499,20 +523,12 @@ float GLToolbar::get_width_horizontal() const float GLToolbar::get_width_vertical() const { -#if ENABLE_SVG_ICONS return (2.0f * m_layout.border + m_layout.icons_size) * m_layout.scale; -#else - return 2.0f * m_layout.border * m_layout.icons_scale + m_icons_texture.metadata.icon_size * m_layout.icons_scale; -#endif // ENABLE_SVG_ICONS } float GLToolbar::get_height_horizontal() const { -#if ENABLE_SVG_ICONS return (2.0f * m_layout.border + m_layout.icons_size) * m_layout.scale; -#else - return 2.0f * m_layout.border * m_layout.icons_scale + m_icons_texture.metadata.icon_size * m_layout.icons_scale; -#endif // ENABLE_SVG_ICONS } float GLToolbar::get_height_vertical() const @@ -522,7 +538,6 @@ float GLToolbar::get_height_vertical() const float GLToolbar::get_main_size() const { -#if ENABLE_SVG_ICONS float size = 2.0f * m_layout.border; for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i) { @@ -538,59 +553,64 @@ float GLToolbar::get_main_size() const if (m_items.size() > 1) size += ((float)m_items.size() - 1.0f) * m_layout.gap_size; - size *= m_layout.scale; -#else - float size = 2.0f * m_layout.border * m_layout.icons_scale; - for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i) - { - if (!m_items[i]->is_visible()) - continue; - - if (m_items[i]->is_separator()) - size += m_layout.separator_size * m_layout.icons_scale; - else - size += (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale; - } - - if (m_items.size() > 1) - size += ((float)m_items.size() - 1.0f) * m_layout.gap_size * m_layout.icons_scale; -#endif // ENABLE_SVG_ICONS - - return size; + return size * m_layout.scale; } -void GLToolbar::do_action(unsigned int item_id, GLCanvas3D& parent) +void GLToolbar::do_action(GLToolbarItem::EActionType type, unsigned int item_id, GLCanvas3D& parent, bool check_hover) { - if (item_id < (unsigned int)m_items.size()) + if ((m_pressed_toggable_id == -1) || (m_pressed_toggable_id == item_id)) { - GLToolbarItem* item = m_items[item_id]; - if ((item != nullptr) && !item->is_separator() && item->is_hovered()) + if (item_id < (unsigned int)m_items.size()) { - if (item->is_toggable()) + GLToolbarItem* item = m_items[item_id]; + if ((item != nullptr) && !item->is_separator() && (!check_hover || item->is_hovered())) { - GLToolbarItem::EState state = item->get_state(); - if (state == GLToolbarItem::Hover) - item->set_state(GLToolbarItem::HoverPressed); - else if (state == GLToolbarItem::HoverPressed) - item->set_state(GLToolbarItem::Hover); - - parent.render(); - item->do_action(); - } - else - { - if (m_type == Radio) - select_item(item->get_name()); - else - item->set_state(GLToolbarItem::HoverPressed); - - parent.render(); - item->do_action(); - if ((m_type == Normal) && (item->get_state() != GLToolbarItem::Disabled)) + if (((type == GLToolbarItem::Right) && item->is_right_toggable()) || + ((type == GLToolbarItem::Left) && item->is_left_toggable())) { - // the item may get disabled during the action, if not, set it back to hover state - item->set_state(GLToolbarItem::Hover); + GLToolbarItem::EState state = item->get_state(); + if (state == GLToolbarItem::Hover) + item->set_state(GLToolbarItem::HoverPressed); + else if (state == GLToolbarItem::HoverPressed) + item->set_state(GLToolbarItem::Hover); + else if (state == GLToolbarItem::Pressed) + item->set_state(GLToolbarItem::Normal); + else if (state == GLToolbarItem::Normal) + item->set_state(GLToolbarItem::Pressed); + + m_pressed_toggable_id = item->is_pressed() ? item_id : -1; + item->reset_last_action_type(); + parent.render(); + switch (type) + { + default: + case GLToolbarItem::Left: { item->do_left_action(); break; } + case GLToolbarItem::Right: { item->do_right_action(); break; } + } + } + else + { + if (m_type == Radio) + select_item(item->get_name()); + else + item->set_state(item->is_hovered() ? GLToolbarItem::HoverPressed : GLToolbarItem::Pressed); + + item->reset_last_action_type(); + parent.render(); + switch (type) + { + default: + case GLToolbarItem::Left: { item->do_left_action(); break; } + case GLToolbarItem::Right: { item->do_right_action(); break; } + } + + if ((m_type == Normal) && (item->get_state() != GLToolbarItem::Disabled)) + { + // the item may get disabled during the action, if not, set it back to hover state + item->set_state(GLToolbarItem::Hover); + parent.render(); + } } } } @@ -600,7 +620,7 @@ void GLToolbar::do_action(unsigned int item_id, GLCanvas3D& parent) std::string GLToolbar::update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent) { if (!m_enabled) - return ""; + return L(""); switch (m_layout.type) { @@ -614,22 +634,14 @@ std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLC { // NB: mouse_pos is already scaled appropriately - float zoom = parent.get_camera().zoom; + float zoom = (float)parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; -#if ENABLE_SVG_ICONS float factor = m_layout.scale * inv_zoom; -#else - float factor = m_layout.icons_scale * inv_zoom; -#endif // ENABLE_SVG_ICONS Size cnv_size = parent.get_canvas_size(); Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); -#if ENABLE_SVG_ICONS float scaled_icons_size = m_layout.icons_size * factor; -#else - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; -#endif // ENABLE_SVG_ICONS float scaled_separator_size = m_layout.separator_size * factor; float scaled_gap_size = m_layout.gap_size * factor; float scaled_border = m_layout.border * factor; @@ -657,7 +669,15 @@ std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLC GLToolbarItem::EState state = item->get_state(); bool inside = (left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top); if (inside) + { tooltip = item->get_tooltip(); + if (!item->is_pressed()) + { + const std::string& additional_tooltip = item->get_additional_tooltip(); + if (!additional_tooltip.empty()) + tooltip += L("\n") + additional_tooltip; + } + } switch (state) { @@ -719,22 +739,14 @@ std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCan { // NB: mouse_pos is already scaled appropriately - float zoom = parent.get_camera().zoom; + float zoom = (float)parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; -#if ENABLE_SVG_ICONS float factor = m_layout.scale * inv_zoom; -#else - float factor = m_layout.icons_scale * inv_zoom; -#endif // ENABLE_SVG_ICONS Size cnv_size = parent.get_canvas_size(); Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); -#if ENABLE_SVG_ICONS float scaled_icons_size = m_layout.icons_size * factor; -#else - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; -#endif // ENABLE_SVG_ICONS float scaled_separator_size = m_layout.separator_size * factor; float scaled_gap_size = m_layout.gap_size * factor; float scaled_border = m_layout.border * factor; @@ -761,7 +773,15 @@ std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCan GLToolbarItem::EState state = item->get_state(); bool inside = (left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top); if (inside) + { tooltip = item->get_tooltip(); + if (!item->is_pressed()) + { + const std::string& additional_tooltip = item->get_additional_tooltip(); + if (!additional_tooltip.empty()) + tooltip += L("\n") + additional_tooltip; + } + } switch (state) { @@ -836,22 +856,14 @@ int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3 { // NB: mouse_pos is already scaled appropriately - float zoom = parent.get_camera().zoom; + float zoom = (float)parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; -#if ENABLE_SVG_ICONS float factor = m_layout.scale * inv_zoom; -#else - float factor = m_layout.icons_scale * inv_zoom; -#endif // ENABLE_SVG_ICONS Size cnv_size = parent.get_canvas_size(); Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); -#if ENABLE_SVG_ICONS float scaled_icons_size = m_layout.icons_size * factor; -#else - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; -#endif // ENABLE_SVG_ICONS float scaled_separator_size = m_layout.separator_size * factor; float scaled_gap_size = m_layout.gap_size * factor; float scaled_border = m_layout.border * factor; @@ -919,22 +931,14 @@ int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& { // NB: mouse_pos is already scaled appropriately - float zoom = parent.get_camera().zoom; + float zoom = (float)parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; -#if ENABLE_SVG_ICONS float factor = m_layout.scale * inv_zoom; -#else - float factor = m_layout.icons_scale * inv_zoom; -#endif // ENABLE_SVG_ICONS Size cnv_size = parent.get_canvas_size(); Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); -#if ENABLE_SVG_ICONS float scaled_icons_size = m_layout.icons_size * factor; -#else - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; -#endif // ENABLE_SVG_ICONS float scaled_separator_size = m_layout.separator_size * factor; float scaled_gap_size = m_layout.gap_size * factor; float scaled_border = m_layout.border * factor; @@ -998,36 +1002,95 @@ int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& return -1; } +void GLToolbar::render_background(float left, float top, float right, float bottom, float border) const +{ + unsigned int tex_id = m_background_texture.texture.get_id(); + float tex_width = (float)m_background_texture.texture.get_width(); + float tex_height = (float)m_background_texture.texture.get_height(); + if ((tex_id != 0) && (tex_width > 0) && (tex_height > 0)) + { + float inv_tex_width = (tex_width != 0.0f) ? 1.0f / tex_width : 0.0f; + float inv_tex_height = (tex_height != 0.0f) ? 1.0f / tex_height : 0.0f; + + float internal_left = left + border; + float internal_right = right - border; + float internal_top = top - border; + float internal_bottom = bottom + border; + + float left_uv = 0.0f; + float right_uv = 1.0f; + float top_uv = 1.0f; + float bottom_uv = 0.0f; + + float internal_left_uv = (float)m_background_texture.metadata.left * inv_tex_width; + float internal_right_uv = 1.0f - (float)m_background_texture.metadata.right * inv_tex_width; + float internal_top_uv = 1.0f - (float)m_background_texture.metadata.top * inv_tex_height; + float internal_bottom_uv = (float)m_background_texture.metadata.bottom * inv_tex_height; + + // top-left corner + if ((m_layout.horizontal_orientation == Layout::HO_Left) || (m_layout.vertical_orientation == Layout::VO_Top)) + GLTexture::render_sub_texture(tex_id, left, internal_left, internal_top, top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + else + GLTexture::render_sub_texture(tex_id, left, internal_left, internal_top, top, { { left_uv, internal_top_uv }, { internal_left_uv, internal_top_uv }, { internal_left_uv, top_uv }, { left_uv, top_uv } }); + + // top edge + if (m_layout.vertical_orientation == Layout::VO_Top) + GLTexture::render_sub_texture(tex_id, internal_left, internal_right, internal_top, top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + else + GLTexture::render_sub_texture(tex_id, internal_left, internal_right, internal_top, top, { { internal_left_uv, internal_top_uv }, { internal_right_uv, internal_top_uv }, { internal_right_uv, top_uv }, { internal_left_uv, top_uv } }); + + // top-right corner + if ((m_layout.horizontal_orientation == Layout::HO_Right) || (m_layout.vertical_orientation == Layout::VO_Top)) + GLTexture::render_sub_texture(tex_id, internal_right, right, internal_top, top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + else + GLTexture::render_sub_texture(tex_id, internal_right, right, internal_top, top, { { internal_right_uv, internal_top_uv }, { right_uv, internal_top_uv }, { right_uv, top_uv }, { internal_right_uv, top_uv } }); + + // center-left edge + if (m_layout.horizontal_orientation == Layout::HO_Left) + GLTexture::render_sub_texture(tex_id, left, internal_left, internal_bottom, internal_top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + else + GLTexture::render_sub_texture(tex_id, left, internal_left, internal_bottom, internal_top, { { left_uv, internal_bottom_uv }, { internal_left_uv, internal_bottom_uv }, { internal_left_uv, internal_top_uv }, { left_uv, internal_top_uv } }); + + // center + GLTexture::render_sub_texture(tex_id, internal_left, internal_right, internal_bottom, internal_top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + + // center-right edge + if (m_layout.horizontal_orientation == Layout::HO_Right) + GLTexture::render_sub_texture(tex_id, internal_right, right, internal_bottom, internal_top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + else + GLTexture::render_sub_texture(tex_id, internal_right, right, internal_bottom, internal_top, { { internal_right_uv, internal_bottom_uv }, { right_uv, internal_bottom_uv }, { right_uv, internal_top_uv }, { internal_right_uv, internal_top_uv } }); + + // bottom-left corner + if ((m_layout.horizontal_orientation == Layout::HO_Left) || (m_layout.vertical_orientation == Layout::VO_Bottom)) + GLTexture::render_sub_texture(tex_id, left, internal_left, bottom, internal_bottom, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + else + GLTexture::render_sub_texture(tex_id, left, internal_left, bottom, internal_bottom, { { left_uv, bottom_uv }, { internal_left_uv, bottom_uv }, { internal_left_uv, internal_bottom_uv }, { left_uv, internal_bottom_uv } }); + + // bottom edge + if (m_layout.vertical_orientation == Layout::VO_Bottom) + GLTexture::render_sub_texture(tex_id, internal_left, internal_right, bottom, internal_bottom, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + else + GLTexture::render_sub_texture(tex_id, internal_left, internal_right, bottom, internal_bottom, { { internal_left_uv, bottom_uv }, { internal_right_uv, bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_left_uv, internal_bottom_uv } }); + + // bottom-right corner + if ((m_layout.horizontal_orientation == Layout::HO_Right) || (m_layout.vertical_orientation == Layout::VO_Bottom)) + GLTexture::render_sub_texture(tex_id, internal_right, right, bottom, internal_bottom, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + else + GLTexture::render_sub_texture(tex_id, internal_right, right, bottom, internal_bottom, { { internal_right_uv, bottom_uv }, { right_uv, bottom_uv }, { right_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv } }); + } +} + void GLToolbar::render_horizontal(const GLCanvas3D& parent) const { -#if ENABLE_SVG_ICONS unsigned int tex_id = m_icons_texture.get_id(); int tex_width = m_icons_texture.get_width(); int tex_height = m_icons_texture.get_height(); -#else - unsigned int tex_id = m_icons_texture.texture.get_id(); - int tex_width = m_icons_texture.texture.get_width(); - int tex_height = m_icons_texture.texture.get_height(); -#endif // ENABLE_SVG_ICONS -#if !ENABLE_SVG_ICONS - if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) - return; -#endif // !ENABLE_SVG_ICONS - - float zoom = parent.get_camera().zoom; + float zoom = (float)parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; -#if ENABLE_SVG_ICONS float factor = inv_zoom * m_layout.scale; -#else - float factor = inv_zoom * m_layout.icons_scale; -#endif // ENABLE_SVG_ICONS -#if ENABLE_SVG_ICONS float scaled_icons_size = m_layout.icons_size * factor; -#else - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; -#endif // ENABLE_SVG_ICONS float scaled_separator_size = m_layout.separator_size * factor; float scaled_gap_size = m_layout.gap_size * factor; float scaled_border = m_layout.border * factor; @@ -1042,96 +1105,13 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const float right = left + scaled_width; float bottom = top - scaled_height; - // renders background - unsigned int bg_tex_id = m_background_texture.texture.get_id(); - float bg_tex_width = (float)m_background_texture.texture.get_width(); - float bg_tex_height = (float)m_background_texture.texture.get_height(); - if ((bg_tex_id != 0) && (bg_tex_width > 0) && (bg_tex_height > 0)) - { - float inv_bg_tex_width = (bg_tex_width != 0.0f) ? 1.0f / bg_tex_width : 0.0f; - float inv_bg_tex_height = (bg_tex_height != 0.0f) ? 1.0f / bg_tex_height : 0.0f; - - float bg_uv_left = 0.0f; - float bg_uv_right = 1.0f; - float bg_uv_top = 1.0f; - float bg_uv_bottom = 0.0f; - - float bg_left = left; - float bg_right = right; - float bg_top = top; - float bg_bottom = bottom; - float bg_width = right - left; - float bg_height = top - bottom; - float bg_min_size = std::min(bg_width, bg_height); - - float bg_uv_i_left = (float)m_background_texture.metadata.left * inv_bg_tex_width; - float bg_uv_i_right = 1.0f - (float)m_background_texture.metadata.right * inv_bg_tex_width; - float bg_uv_i_top = 1.0f - (float)m_background_texture.metadata.top * inv_bg_tex_height; - float bg_uv_i_bottom = (float)m_background_texture.metadata.bottom * inv_bg_tex_height; - - float bg_i_left = bg_left + scaled_border; - float bg_i_right = bg_right - scaled_border; - float bg_i_top = bg_top - scaled_border; - float bg_i_bottom = bg_bottom + scaled_border; - - switch (m_layout.orientation) - { - case Layout::Top: - { - bg_uv_top = bg_uv_i_top; - bg_i_top = bg_top; - break; - } - case Layout::Bottom: - { - bg_uv_bottom = bg_uv_i_bottom; - bg_i_bottom = bg_bottom; - break; - } - case Layout::Center: - { - break; - } - }; - - if ((m_layout.border > 0) && (bg_uv_top != bg_uv_i_top)) - { - if (bg_uv_left != bg_uv_i_left) - GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_top, bg_top, { { bg_uv_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_top }, { bg_uv_left, bg_uv_top } }); - - GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_top, bg_top, { { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_top }, { bg_uv_i_left, bg_uv_top } }); - - if (bg_uv_right != bg_uv_i_right) - GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_top, bg_top, { { bg_uv_i_right, bg_uv_i_top }, { bg_uv_right, bg_uv_i_top }, { bg_uv_right, bg_uv_top }, { bg_uv_i_right, bg_uv_top } }); - } - - if ((m_layout.border > 0) && (bg_uv_left != bg_uv_i_left)) - GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_bottom, bg_i_top, { { bg_uv_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_left, bg_uv_i_top } }); - - GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_bottom, bg_i_top, { { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top } }); - - if ((m_layout.border > 0) && (bg_uv_right != bg_uv_i_right)) - GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_bottom, bg_i_top, { { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top } }); - - if ((m_layout.border > 0) && (bg_uv_bottom != bg_uv_i_bottom)) - { - if (bg_uv_left != bg_uv_i_left) - GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_bottom, bg_i_bottom, { { bg_uv_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_left, bg_uv_i_bottom } }); - - GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_bottom, bg_i_bottom, { { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_right, bg_uv_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom } }); - - if (bg_uv_right != bg_uv_i_right) - GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_bottom, bg_i_bottom, { { bg_uv_i_right, bg_uv_bottom }, { bg_uv_right, bg_uv_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom } }); - } - } + render_background(left, top, right, bottom, scaled_border); left += scaled_border; top -= scaled_border; -#if ENABLE_SVG_ICONS if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) return; -#endif // ENABLE_SVG_ICONS // renders icons for (const GLToolbarItem* item : m_items) @@ -1143,11 +1123,7 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const left += separator_stride; else { -#if ENABLE_SVG_ICONS item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, (unsigned int)(m_layout.icons_size * m_layout.scale)); -#else - item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, m_icons_texture.metadata.icon_size); -#endif // ENABLE_SVG_ICONS left += icon_stride; } } @@ -1155,34 +1131,15 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const void GLToolbar::render_vertical(const GLCanvas3D& parent) const { -#if ENABLE_SVG_ICONS unsigned int tex_id = m_icons_texture.get_id(); int tex_width = m_icons_texture.get_width(); int tex_height = m_icons_texture.get_height(); -#else - unsigned int tex_id = m_icons_texture.texture.get_id(); - int tex_width = m_icons_texture.texture.get_width(); - int tex_height = m_icons_texture.texture.get_height(); -#endif // ENABLE_SVG_ICONS -#if !ENABLE_SVG_ICONS - if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) - return; -#endif // !ENABLE_SVG_ICONS - - float zoom = parent.get_camera().zoom; + float zoom = (float)parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; -#if ENABLE_SVG_ICONS float factor = inv_zoom * m_layout.scale; -#else - float factor = inv_zoom * m_layout.icons_scale; -#endif // ENABLE_SVG_ICONS -#if ENABLE_SVG_ICONS float scaled_icons_size = m_layout.icons_size * factor; -#else - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale * factor; -#endif // ENABLE_SVG_ICONS float scaled_separator_size = m_layout.separator_size * factor; float scaled_gap_size = m_layout.gap_size * factor; float scaled_border = m_layout.border * factor; @@ -1197,96 +1154,13 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) const float right = left + scaled_width; float bottom = top - scaled_height; - // renders background - unsigned int bg_tex_id = m_background_texture.texture.get_id(); - float bg_tex_width = (float)m_background_texture.texture.get_width(); - float bg_tex_height = (float)m_background_texture.texture.get_height(); - if ((bg_tex_id != 0) && (bg_tex_width > 0) && (bg_tex_height > 0)) - { - float inv_bg_tex_width = (bg_tex_width != 0.0f) ? 1.0f / bg_tex_width : 0.0f; - float inv_bg_tex_height = (bg_tex_height != 0.0f) ? 1.0f / bg_tex_height : 0.0f; - - float bg_uv_left = 0.0f; - float bg_uv_right = 1.0f; - float bg_uv_top = 1.0f; - float bg_uv_bottom = 0.0f; - - float bg_left = left; - float bg_right = right; - float bg_top = top; - float bg_bottom = bottom; - float bg_width = right - left; - float bg_height = top - bottom; - float bg_min_size = std::min(bg_width, bg_height); - - float bg_uv_i_left = (float)m_background_texture.metadata.left * inv_bg_tex_width; - float bg_uv_i_right = 1.0f - (float)m_background_texture.metadata.right * inv_bg_tex_width; - float bg_uv_i_top = 1.0f - (float)m_background_texture.metadata.top * inv_bg_tex_height; - float bg_uv_i_bottom = (float)m_background_texture.metadata.bottom * inv_bg_tex_height; - - float bg_i_left = bg_left + scaled_border; - float bg_i_right = bg_right - scaled_border; - float bg_i_top = bg_top - scaled_border; - float bg_i_bottom = bg_bottom + scaled_border; - - switch (m_layout.orientation) - { - case Layout::Left: - { - bg_uv_left = bg_uv_i_left; - bg_i_left = bg_left; - break; - } - case Layout::Right: - { - bg_uv_right = bg_uv_i_right; - bg_i_right = bg_right; - break; - } - case Layout::Center: - { - break; - } - }; - - if ((m_layout.border > 0) && (bg_uv_top != bg_uv_i_top)) - { - if (bg_uv_left != bg_uv_i_left) - GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_top, bg_top, { { bg_uv_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_top }, { bg_uv_left, bg_uv_top } }); - - GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_top, bg_top, { { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_top }, { bg_uv_i_left, bg_uv_top } }); - - if (bg_uv_right != bg_uv_i_right) - GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_top, bg_top, { { bg_uv_i_right, bg_uv_i_top }, { bg_uv_right, bg_uv_i_top }, { bg_uv_right, bg_uv_top }, { bg_uv_i_right, bg_uv_top } }); - } - - if ((m_layout.border > 0) && (bg_uv_left != bg_uv_i_left)) - GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_bottom, bg_i_top, { { bg_uv_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_left, bg_uv_i_top } }); - - GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_bottom, bg_i_top, { { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top } }); - - if ((m_layout.border > 0) && (bg_uv_right != bg_uv_i_right)) - GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_bottom, bg_i_top, { { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top } }); - - if ((m_layout.border > 0) && (bg_uv_bottom != bg_uv_i_bottom)) - { - if (bg_uv_left != bg_uv_i_left) - GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_bottom, bg_i_bottom, { { bg_uv_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_left, bg_uv_i_bottom } }); - - GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_bottom, bg_i_bottom, { { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_right, bg_uv_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom } }); - - if (bg_uv_right != bg_uv_i_right) - GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_bottom, bg_i_bottom, { { bg_uv_i_right, bg_uv_bottom }, { bg_uv_right, bg_uv_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom } }); - } - } + render_background(left, top, right, bottom, scaled_border); left += scaled_border; top -= scaled_border; -#if ENABLE_SVG_ICONS if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) return; -#endif // ENABLE_SVG_ICONS // renders icons for (const GLToolbarItem* item : m_items) @@ -1298,17 +1172,12 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) const top -= separator_stride; else { -#if ENABLE_SVG_ICONS item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, (unsigned int)(m_layout.icons_size * m_layout.scale)); -#else - item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, m_icons_texture.metadata.icon_size); -#endif // ENABLE_SVG_ICONS top -= icon_stride; } } } -#if ENABLE_SVG_ICONS bool GLToolbar::generate_icons_texture() const { std::string path = resources_dir() + "/icons/"; @@ -1338,13 +1207,12 @@ bool GLToolbar::generate_icons_texture() const states.push_back(std::make_pair(1, true)); } - bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, (unsigned int)(m_layout.icons_size * m_layout.scale)); + bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, (unsigned int)(m_layout.icons_size * m_layout.scale), true); if (res) m_icons_texture_dirty = false; return res; } -#endif // ENABLE_SVG_ICONS bool GLToolbar::update_items_visibility() { @@ -1378,9 +1246,15 @@ bool GLToolbar::update_items_enabled_state() { bool ret = false; - for (GLToolbarItem* item : m_items) + for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i) { + GLToolbarItem* item = m_items[i]; ret |= item->update_enabled_state(); + if (item->is_enabled() && (m_pressed_toggable_id != -1) && (m_pressed_toggable_id != i)) + { + ret = true; + item->set_state(GLToolbarItem::Disabled); + } } if (ret) diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index 24314d60ff..527317e589 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -36,7 +36,8 @@ class GLToolbarItem public: typedef std::function ActionCallback; typedef std::function VisibilityCallback; - typedef std::function EnabledStateCallback; + typedef std::function EnablingCallback; + typedef std::function RenderCallback; enum EType : unsigned char { @@ -45,6 +46,14 @@ public: Num_Types }; + enum EActionType : unsigned char + { + Undefined, + Left, + Right, + Num_Action_Types + }; + enum EState : unsigned char { Normal, @@ -57,29 +66,43 @@ public: struct Data { + struct Option + { + bool toggable; + ActionCallback action_callback; + RenderCallback render_callback; + + Option(); + + bool can_render() const { return toggable && (render_callback != nullptr); } + }; + std::string name; -#if ENABLE_SVG_ICONS std::string icon_filename; -#endif // ENABLE_SVG_ICONS std::string tooltip; + std::string additional_tooltip; unsigned int sprite_id; - bool is_toggable; + // mouse left click + Option left; + // mouse right click + Option right; bool visible; - ActionCallback action_callback; VisibilityCallback visibility_callback; - EnabledStateCallback enabled_state_callback; + EnablingCallback enabling_callback; Data(); }; static const ActionCallback Default_Action_Callback; static const VisibilityCallback Default_Visibility_Callback; - static const EnabledStateCallback Default_Enabled_State_Callback; + static const EnablingCallback Default_Enabling_Callback; + static const RenderCallback Default_Render_Callback; private: EType m_type; EState m_state; Data m_data; + EActionType m_last_action_type; public: GLToolbarItem(EType type, const Data& data); @@ -88,22 +111,30 @@ public: void set_state(EState state) { m_state = state; } const std::string& get_name() const { return m_data.name; } -#if ENABLE_SVG_ICONS const std::string& get_icon_filename() const { return m_data.icon_filename; } -#endif // ENABLE_SVG_ICONS const std::string& get_tooltip() const { return m_data.tooltip; } + const std::string& get_additional_tooltip() const { return m_data.additional_tooltip; } + void set_additional_tooltip(const std::string& text) { m_data.additional_tooltip = text; } - void do_action() { m_data.action_callback(); } + void do_left_action() { m_last_action_type = Left; m_data.left.action_callback(); } + void do_right_action() { m_last_action_type = Right; m_data.right.action_callback(); } bool is_enabled() const { return m_state != Disabled; } bool is_disabled() const { return m_state == Disabled; } bool is_hovered() const { return (m_state == Hover) || (m_state == HoverPressed); } bool is_pressed() const { return (m_state == Pressed) || (m_state == HoverPressed); } - - bool is_toggable() const { return m_data.is_toggable; } bool is_visible() const { return m_data.visible; } bool is_separator() const { return m_type == Separator; } + bool is_left_toggable() const { return m_data.left.toggable; } + bool is_right_toggable() const { return m_data.right.toggable; } + + bool has_left_render_callback() const { return m_data.left.render_callback != nullptr; } + bool has_right_render_callback() const { return m_data.right.render_callback != nullptr; } + + EActionType get_last_action_type() const { return m_last_action_type; } + void reset_last_action_type() { m_last_action_type = Undefined; } + // returns true if the state changes bool update_visibility(); // returns true if the state changes @@ -118,27 +149,6 @@ private: friend class GLToolbar; }; -#if !ENABLE_SVG_ICONS -// items icon textures are assumed to be square and all with the same size in pixels, no internal check is done -// icons are layed-out into the texture starting from the top-left corner in the same order as enum GLToolbarItem::EState -// from left to right -struct ItemsIconsTexture -{ - struct Metadata - { - // path of the file containing the icons' texture - std::string filename; - // size of the square icons, in pixels - unsigned int icon_size; - - Metadata(); - }; - - GLTexture texture; - Metadata metadata; -}; -#endif // !ENABLE_SVG_ICONS - struct BackgroundTexture { struct Metadata @@ -164,9 +174,7 @@ struct BackgroundTexture class GLToolbar { public: -#if ENABLE_SVG_ICONS static const float Default_Icons_Size; -#endif // ENABLE_SVG_ICONS enum EType : unsigned char { @@ -184,29 +192,32 @@ public: Num_Types }; - enum EOrientation : unsigned int + enum EHorizontalOrientation : unsigned char { - Top, - Bottom, - Left, - Right, - Center, - Num_Locations + HO_Left, + HO_Center, + HO_Right, + Num_Horizontal_Orientations + }; + + enum EVerticalOrientation : unsigned char + { + VO_Top, + VO_Center, + VO_Bottom, + Num_Vertical_Orientations }; EType type; - EOrientation orientation; + EHorizontalOrientation horizontal_orientation; + EVerticalOrientation vertical_orientation; float top; float left; float border; float separator_size; float gap_size; -#if ENABLE_SVG_ICONS float icons_size; float scale; -#else - float icons_scale; -#endif // ENABLE_SVG_ICONS float width; float height; @@ -219,16 +230,10 @@ private: typedef std::vector ItemsList; EType m_type; -#if ENABLE_SVG_ICONS std::string m_name; -#endif // ENABLE_SVG_ICONS bool m_enabled; -#if ENABLE_SVG_ICONS mutable GLTexture m_icons_texture; mutable bool m_icons_texture_dirty; -#else - ItemsIconsTexture m_icons_texture; -#endif // ENABLE_SVG_ICONS BackgroundTexture m_background_texture; mutable Layout m_layout; @@ -249,36 +254,27 @@ private: MouseCapture m_mouse_capture; std::string m_tooltip; + unsigned int m_pressed_toggable_id; public: -#if ENABLE_SVG_ICONS GLToolbar(EType type, const std::string& name); -#else - explicit GLToolbar(EType type); -#endif // ENABLE_SVG_ICONS ~GLToolbar(); -#if ENABLE_SVG_ICONS bool init(const BackgroundTexture::Metadata& background_texture); -#else - bool init(const ItemsIconsTexture::Metadata& icons_texture, const BackgroundTexture::Metadata& background_texture); -#endif // ENABLE_SVG_ICONS Layout::EType get_layout_type() const; void set_layout_type(Layout::EType type); - Layout::EOrientation get_layout_orientation() const; - void set_layout_orientation(Layout::EOrientation orientation); + Layout::EHorizontalOrientation get_horizontal_orientation() const { return m_layout.horizontal_orientation; } + void set_horizontal_orientation(Layout::EHorizontalOrientation orientation) { m_layout.horizontal_orientation = orientation; } + Layout::EVerticalOrientation get_vertical_orientation() const { return m_layout.vertical_orientation; } + void set_vertical_orientation(Layout::EVerticalOrientation orientation) { m_layout.vertical_orientation = orientation; } void set_position(float top, float left); void set_border(float border); void set_separator_size(float size); void set_gap_size(float size); -#if ENABLE_SVG_ICONS void set_icons_size(float size); void set_scale(float scale); -#else - void set_icons_scale(float scale); -#endif // ENABLE_SVG_ICONS bool is_enabled() const; void set_enabled(bool enable); @@ -295,8 +291,17 @@ public: bool is_item_disabled(const std::string& name) const; bool is_item_visible(const std::string& name) const; + bool is_any_item_pressed() const; + + unsigned int get_item_id(const std::string& name) const; + + void force_left_action(unsigned int item_id, GLCanvas3D& parent); + void force_right_action(unsigned int item_id, GLCanvas3D& parent); + const std::string& get_tooltip() const { return m_tooltip; } + void get_additional_tooltip(unsigned int item_id, std::string& text); + void set_additional_tooltip(unsigned int item_id, const std::string& text); // returns true if any item changed its state bool update_items_state(); @@ -312,7 +317,7 @@ private: float get_height_horizontal() const; float get_height_vertical() const; float get_main_size() const; - void do_action(unsigned int item_id, GLCanvas3D& parent); + void do_action(GLToolbarItem::EActionType type, unsigned int item_id, GLCanvas3D& parent, bool check_hover); std::string update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent); std::string update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D& parent); std::string update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& parent); @@ -321,12 +326,11 @@ private: int contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3D& parent) const; int contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& parent) const; + void render_background(float left, float top, float right, float bottom, float border) const; void render_horizontal(const GLCanvas3D& parent) const; void render_vertical(const GLCanvas3D& parent) const; -#if ENABLE_SVG_ICONS bool generate_icons_texture() const; -#endif // ENABLE_SVG_ICONS // returns true if any item changed its state bool update_items_visibility(); diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 9a641c7c04..826f2d6fcf 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -135,8 +135,7 @@ void config_wizard(int reason) wxGetApp().load_current_presets(); - if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA && - wxGetApp().obj_list()->has_multi_part_objects()) + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA && model_has_multi_part_objects(wxGetApp().model())) { show_info(nullptr, _(L("It's impossible to print multi-part object(s) with SLA technology.")) + "\n\n" + @@ -149,6 +148,13 @@ void config_wizard(int reason) void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index /*= 0*/) { try{ + + if (config.def()->get(opt_key)->type == coBools && config.def()->get(opt_key)->nullable) { + ConfigOptionBoolsNullable* vec_new = new ConfigOptionBoolsNullable{ boost::any_cast(value) }; + config.option(opt_key)->set_at(vec_new, opt_index, 0); + return; + } + switch (config.def()->get(opt_key)->type) { case coFloatOrPercent:{ std::string str = boost::any_cast(value); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index b75b946e6f..dff9fc1a97 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -67,9 +67,12 @@ wxString file_wildcards(FileType file_type, const std::string &custom_extension) /* FT_MODEL */ "Known files (*.stl, *.obj, *.amf, *.xml, *.3mf, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.3mf;*.3MF;*.prusa;*.PRUSA", /* FT_PROJECT */ "Project files (*.3mf, *.amf)|*.3mf;*.3MF;*.amf;*.AMF", - /* FT_INI */ "INI files (*.ini)|*.ini;*.INI", - /* FT_SVG */ "SVG files (*.svg)|*.svg;*.SVG", - /* FT_PNGZIP */"Masked SLA files (*.sl1)|*.sl1;*.SL1", + /* FT_INI */ "INI files (*.ini)|*.ini;*.INI", + /* FT_SVG */ "SVG files (*.svg)|*.svg;*.SVG", + + /* FT_TEX */ "Texture (*.png, *.svg)|*.png;*.PNG;*.svg;*.SVG", + + /* FT_PNGZIP */ "Masked SLA files (*.sl1)|*.sl1;*.SL1", }; std::string out = defaults[file_type]; @@ -123,7 +126,16 @@ static void generic_exception_handle() try { throw; - } catch (const std::exception &ex) { + } catch (const std::bad_alloc& ex) { + // bad_alloc in main thread is most likely fatal. Report immediately to the user (wxLogError would be delayed) + // and terminate the app so it is at least certain to happen now. + wxString errmsg = wxString::Format(_(L("%s has encountered an error. It was likely caused by running out of memory. " + "If you are sure you have enough RAM on your system, this may also be a bug and we would " + "be glad if you reported it.\n\nThe application will now terminate.")), SLIC3R_APP_NAME); + wxMessageBox(errmsg + "\n\n" + wxString(ex.what()), _(L("Fatal error")), wxOK | wxICON_ERROR); + BOOST_LOG_TRIVIAL(error) << boost::format("std::bad_alloc exception: %1%") % ex.what(); + std::terminate(); + } catch (const std::exception& ex) { wxLogError("Internal error: %s", ex.what()); BOOST_LOG_TRIVIAL(error) << boost::format("Uncaught exception: %1%") % ex.what(); throw; @@ -141,6 +153,18 @@ GUI_App::GUI_App() , m_imgui(new ImGuiWrapper()) {} +GUI_App::~GUI_App() +{ + if (app_config != nullptr) + delete app_config; + + if (preset_bundle != nullptr) + delete preset_bundle; + + if (preset_updater != nullptr) + delete preset_updater; +} + bool GUI_App::OnInit() { try { @@ -158,7 +182,8 @@ bool GUI_App::on_init_inner() wxCHECK_MSG(wxDirExists(resources_dir), false, wxString::Format("Resources path does not exist or is not a directory: %s", resources_dir)); - SetAppName(SLIC3R_APP_KEY); + //SetAppName(SLIC3R_APP_KEY); + SetAppName(SLIC3R_APP_KEY "-alpha"); SetAppDisplayName(SLIC3R_APP_NAME); // Enable this to get the default Win32 COMCTRL32 behavior of static boxes. @@ -265,11 +290,23 @@ bool GUI_App::on_init_inner() } CallAfter([this] { - if (!config_wizard_startup(app_conf_exists)) { - // Only notify if there was no wizard so as not to bother too much ... - preset_updater->slic3r_update_notify(); - } + config_wizard_startup(app_conf_exists); + preset_updater->slic3r_update_notify(); preset_updater->sync(preset_bundle); + const GLCanvas3DManager::GLInfo &glinfo = GLCanvas3DManager::get_gl_info(); + if (! glinfo.is_version_greater_or_equal_to(2, 0)) { + // Complain about the OpenGL version. + wxString message = wxString::Format( + _(L("PrusaSlicer requires OpenGL 2.0 capable graphics driver to run correctly, \n" + "while OpenGL version %s, render %s, vendor %s was detected.")), wxString(glinfo.get_version()), wxString(glinfo.get_renderer()), wxString(glinfo.get_vendor())); + message += "\n"; + message += _(L("You may need to update your graphics card driver.")); +#ifdef _WIN32 + message += "\n"; + message += _(L("As a workaround, you may run PrusaSlicer with a software rendered 3D graphics by running prusa-slicer.exe with the --sw_renderer parameter.")); +#endif + wxMessageBox(message, wxString("PrusaSlicer - ") + _(L("Unsupported OpenGL version")), wxOK | wxICON_ERROR); + } }); } }); @@ -524,7 +561,7 @@ void GUI_App::persist_window_geometry(wxTopLevelWindow *window, bool default_max }); } -void GUI_App::load_project(wxWindow *parent, wxString& input_file) +void GUI_App::load_project(wxWindow *parent, wxString& input_file) const { input_file.Clear(); wxFileDialog dialog(parent ? parent : GetTopWindow(), @@ -536,7 +573,7 @@ void GUI_App::load_project(wxWindow *parent, wxString& input_file) input_file = dialog.GetPath(); } -void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) +void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const { input_files.Clear(); wxFileDialog dialog(parent ? parent : GetTopWindow(), @@ -844,7 +881,7 @@ void GUI_App::add_config_menu(wxMenuBar *menu) // This is called when closing the application, when loading a config file or when starting the config wizard // to notify the user whether he is aware that some preset changes will be lost. -bool GUI_App::check_unsaved_changes() +bool GUI_App::check_unsaved_changes(const wxString &header) { wxString dirty; PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); @@ -858,8 +895,12 @@ bool GUI_App::check_unsaved_changes() // No changes, the application may close or reload presets. return true; // Ask the user. + wxString message; + if (! header.empty()) + message = header + "\n\n"; + message += _(L("The presets on the following tabs were modified")) + ": " + dirty + "\n\n" + _(L("Discard changes and continue anyway?")); wxMessageDialog dialog(mainframe, - _(L("The presets on the following tabs were modified")) + ": " + dirty + "\n\n" + _(L("Discard changes and continue anyway?")), + message, wxString(SLIC3R_APP_NAME) + " - " + _(L("Unsaved Presets")), wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); return dialog.ShowModal() == wxID_YES; @@ -924,14 +965,19 @@ ObjectList* GUI_App::obj_list() return sidebar().obj_list(); } +ObjectLayers* GUI_App::obj_layers() +{ + return sidebar().obj_layers(); +} + Plater* GUI_App::plater() { return plater_; } -ModelObjectPtrs* GUI_App::model_objects() +Model& GUI_App::model() { - return &plater_->model().objects; + return plater_->model(); } wxNotebook* GUI_App::tab_panel() const @@ -939,6 +985,7 @@ wxNotebook* GUI_App::tab_panel() const return mainframe->m_tabpanel; } +// extruders count from selected printer preset int GUI_App::extruders_cnt() const { const Preset& preset = preset_bundle->printers.get_selected_preset(); @@ -946,9 +993,45 @@ int GUI_App::extruders_cnt() const preset.config.option("nozzle_diameter")->values.size(); } +// extruders count from edited printer preset +int GUI_App::extruders_edited_cnt() const +{ + const Preset& preset = preset_bundle->printers.get_edited_preset(); + return preset.printer_technology() == ptSLA ? 1 : + preset.config.option("nozzle_diameter")->values.size(); +} + +wxString GUI_App::current_language_code_safe() const +{ + // Translate the language code to a code, for which Prusa Research maintains translations. + wxString language_code = this->current_language_code(); + size_t idx_underscore = language_code.find(language_code); + if (idx_underscore != wxString::npos) + language_code = language_code.substr(0, idx_underscore); + const std::map mapping { + { "cs", "cs_CZ", }, + { "sk", "cs_CZ", }, + { "de", "de_DE", }, + { "es", "es_ES", }, + { "fr", "fr_FR", }, + { "it", "it_IT", }, + { "ja", "ja_JP", }, + { "ko", "ko_KR", }, + { "pl", "pl_PL", }, + { "uk", "uk_UA", }, + { "zh", "zh_CN", }, + }; + auto it = mapping.find(language_code); + if (it != mapping.end()) + language_code = it->second; + else + language_code = "en_US"; + return language_code; +} + void GUI_App::open_web_page_localized(const std::string &http_address) { - wxLaunchDefaultBrowser(http_address + "&lng=" + this->current_language_code()); + wxLaunchDefaultBrowser(http_address + "&lng=" + this->current_language_code_safe()); } void GUI_App::window_pos_save(wxTopLevelWindow* window, const std::string &name) diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index b70f0dc160..b74762586b 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -44,6 +44,9 @@ enum FileType FT_INI, FT_SVG, + + FT_TEX, + FT_PNGZIP, FT_SIZE, @@ -95,6 +98,7 @@ public: bool initialized() const { return m_initialized; } GUI_App(); + ~GUI_App(); static unsigned get_colour_approx_luma(const wxColour &colour); static bool dark_mode(); @@ -120,8 +124,8 @@ public: void recreate_GUI(); void system_info(); void keyboard_shortcuts(); - void load_project(wxWindow *parent, wxString& input_file); - void import_model(wxWindow *parent, wxArrayString& input_files); + void load_project(wxWindow *parent, wxString& input_file) const; + void import_model(wxWindow *parent, wxArrayString& input_files) const; static bool catch_error(std::function cb, const std::string& err); void persist_window_geometry(wxTopLevelWindow *window, bool default_maximized = false); @@ -138,11 +142,13 @@ public: void update_mode(); void add_config_menu(wxMenuBar *menu); - bool check_unsaved_changes(); + bool check_unsaved_changes(const wxString &header = wxString()); bool checked_tab(Tab* tab); void load_current_presets(); - wxString current_language_code() { return m_wxLocale != nullptr ? m_wxLocale->GetCanonicalName() : wxString("en_US"); } + wxString current_language_code() const { return m_wxLocale != nullptr ? m_wxLocale->GetCanonicalName() : wxString("en_US"); } + // Translate the language code to a code, for which Prusa Research maintains translations. Defaults to "en_US". + wxString current_language_code_safe() const; virtual bool OnExceptionInMainLoop(); @@ -155,8 +161,9 @@ public: ObjectManipulation* obj_manipul(); ObjectSettings* obj_settings(); ObjectList* obj_list(); + ObjectLayers* obj_layers(); Plater* plater(); - std::vector *model_objects(); + Model& model(); AppConfig* app_config{ nullptr }; PresetBundle* preset_bundle{ nullptr }; @@ -166,6 +173,7 @@ public: wxNotebook* tab_panel() const ; int extruders_cnt() const; + int extruders_edited_cnt() const; std::vector tabs_list; diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp new file mode 100644 index 0000000000..b30d3ecd31 --- /dev/null +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -0,0 +1,341 @@ +#include "GUI_ObjectLayers.hpp" +#include "GUI_ObjectList.hpp" + +#include "OptionsGroup.hpp" +#include "PresetBundle.hpp" +#include "libslic3r/Model.hpp" +#include "GLCanvas3D.hpp" + +#include + +#include "I18N.hpp" + +#include + +namespace Slic3r +{ +namespace GUI +{ + +ObjectLayers::ObjectLayers(wxWindow* parent) : + OG_Settings(parent, true) +{ + m_grid_sizer = new wxFlexGridSizer(3, 5, 5); // "Min Z", "Max Z", "Layer height" & buttons sizer + m_grid_sizer->SetFlexibleDirection(wxHORIZONTAL); + + // Legend for object layers + for (const std::string col : { "Min Z", "Max Z", "Layer height" }) { + auto temp = new wxStaticText(m_parent, wxID_ANY, _(L(col)), wxDefaultPosition, /*size*/wxDefaultSize, wxST_ELLIPSIZE_MIDDLE); + temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + temp->SetBackgroundStyle(wxBG_STYLE_PAINT); + temp->SetFont(wxGetApp().bold_font()); + + m_grid_sizer->Add(temp); + } + + m_og->sizer->Clear(true); + m_og->sizer->Add(m_grid_sizer, 0, wxEXPAND | wxALL, wxOSX ? 0 : 5); + + m_bmp_delete = ScalableBitmap(parent, "remove_copies"/*"cross"*/); + m_bmp_add = ScalableBitmap(parent, "add_copies"); +} + +void ObjectLayers::select_editor(LayerRangeEditor* editor, const bool is_last_edited_range) +{ + if (is_last_edited_range && m_selection_type == editor->type()) { + /* Workaround! Under OSX we should use CallAfter() for SetFocus() after LayerEditors "reorganizations", + * because of selected control's strange behavior: + * cursor is set to the control, but blue border - doesn't. + * And as a result we couldn't edit this control. + * */ +#ifdef __WXOSX__ + wxTheApp->CallAfter([editor]() { +#endif + editor->SetFocus(); + editor->SetInsertionPointEnd(); +#ifdef __WXOSX__ + }); +#endif + } +} + +wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range) +{ + const bool is_last_edited_range = range == m_selectable_range; + + auto set_focus_data = [range, this](const EditorType type) + { + m_selectable_range = range; + m_selection_type = type; + }; + + auto update_focus_data = [range, this](const t_layer_height_range& new_range, EditorType type, bool enter_pressed) + { + // change selectable range for new one, if enter was pressed or if same range was selected + if (enter_pressed || m_selectable_range == range) + m_selectable_range = new_range; + if (enter_pressed) + m_selection_type = type; + }; + + // Add control for the "Min Z" + + auto editor = new LayerRangeEditor(this, double_to_string(range.first), etMinZ, + set_focus_data, [range, update_focus_data, this](coordf_t min_z, bool enter_pressed) + { + if (fabs(min_z - range.first) < EPSILON) { + m_selection_type = etUndef; + return false; + } + + // data for next focusing + coordf_t max_z = min_z < range.second ? range.second : min_z + 0.5; + const t_layer_height_range& new_range = { min_z, max_z }; + update_focus_data(new_range, etMinZ, enter_pressed); + + return wxGetApp().obj_list()->edit_layer_range(range, new_range); + }); + + select_editor(editor, is_last_edited_range); + m_grid_sizer->Add(editor); + + // Add control for the "Max Z" + + editor = new LayerRangeEditor(this, double_to_string(range.second), etMaxZ, + set_focus_data, [range, update_focus_data, this](coordf_t max_z, bool enter_pressed) + { + if (fabs(max_z - range.second) < EPSILON || range.first > max_z) { + m_selection_type = etUndef; + return false; // LayersList would not be updated/recreated + } + + // data for next focusing + const t_layer_height_range& new_range = { range.first, max_z }; + update_focus_data(new_range, etMaxZ, enter_pressed); + + return wxGetApp().obj_list()->edit_layer_range(range, new_range); + }); + + select_editor(editor, is_last_edited_range); + m_grid_sizer->Add(editor); + + // Add control for the "Layer height" + + editor = new LayerRangeEditor(this, + double_to_string(m_object->layer_config_ranges[range].option("layer_height")->getFloat()), + etLayerHeight, set_focus_data, [range, this](coordf_t layer_height, bool) + { + return wxGetApp().obj_list()->edit_layer_range(range, layer_height); + }); + + select_editor(editor, is_last_edited_range); + + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(editor); + m_grid_sizer->Add(sizer); + + return sizer; +} + +void ObjectLayers::create_layers_list() +{ + for (const auto layer : m_object->layer_config_ranges) + { + const t_layer_height_range& range = layer.first; + auto sizer = create_layer(range); + + auto del_btn = new ScalableButton(m_parent, wxID_ANY, m_bmp_delete); + del_btn->SetToolTip(_(L("Remove layer"))); + + sizer->Add(del_btn, 0, wxRIGHT | wxLEFT, em_unit(m_parent)); + + del_btn->Bind(wxEVT_BUTTON, [this, range](wxEvent &event) { + wxGetApp().obj_list()->del_layer_range(range); + }); + + auto add_btn = new ScalableButton(m_parent, wxID_ANY, m_bmp_add); + add_btn->SetToolTip(_(L("Add layer"))); + + sizer->Add(add_btn, 0, wxRIGHT, em_unit(m_parent)); + + add_btn->Bind(wxEVT_BUTTON, [this, range](wxEvent &event) { + wxGetApp().obj_list()->add_layer_range_after_current(range); + }); + } +} + +void ObjectLayers::update_layers_list() +{ + ObjectList* objects_ctrl = wxGetApp().obj_list(); + if (objects_ctrl->multiple_selection()) return; + + const auto item = objects_ctrl->GetSelection(); + if (!item) return; + + const int obj_idx = objects_ctrl->get_selected_obj_idx(); + if (obj_idx < 0) return; + + const ItemType type = objects_ctrl->GetModel()->GetItemType(item); + if (!(type & (itLayerRoot | itLayer))) return; + + m_object = objects_ctrl->object(obj_idx); + if (!m_object || m_object->layer_config_ranges.empty()) return; + + // Delete all controls from options group except of the legends + + const int cols = m_grid_sizer->GetEffectiveColsCount(); + const int rows = m_grid_sizer->GetEffectiveRowsCount(); + for (int idx = cols*rows-1; idx >= cols; idx--) { + wxSizerItem* t = m_grid_sizer->GetItem(idx); + if (t->IsSizer()) + t->GetSizer()->Clear(true); + else + t->DeleteWindows(); + m_grid_sizer->Remove(idx); + } + + // Add new control according to the selected item + + if (type & itLayerRoot) + create_layers_list(); + else + create_layer(objects_ctrl->GetModel()->GetLayerRangeByItem(item)); + + m_parent->Layout(); +} + +void ObjectLayers::update_scene_from_editor_selection() const +{ + // needed to show the visual hints in 3D scene + wxGetApp().plater()->canvas3D()->handle_layers_data_focus_event(m_selectable_range, m_selection_type); +} + +void ObjectLayers::UpdateAndShow(const bool show) +{ + if (show) + update_layers_list(); + + OG_Settings::UpdateAndShow(show); +} + +void ObjectLayers::msw_rescale() +{ + m_bmp_delete.msw_rescale(); + m_bmp_add.msw_rescale(); +} + +void ObjectLayers::reset_selection() +{ + m_selectable_range = { 0.0, 0.0 }; + m_selection_type = etLayerHeight; +} + +LayerRangeEditor::LayerRangeEditor( ObjectLayers* parent, + const wxString& value, + EditorType type, + std::function set_focus_data_fn, + std::function edit_fn + ) : + m_valid_value(value), + m_type(type), + m_set_focus_data(set_focus_data_fn), + wxTextCtrl(parent->m_parent, wxID_ANY, value, wxDefaultPosition, + wxSize(8 * em_unit(parent->m_parent), wxDefaultCoord), wxTE_PROCESS_ENTER) +{ + this->SetFont(wxGetApp().normal_font()); + + this->Bind(wxEVT_TEXT_ENTER, [this, edit_fn](wxEvent&) + { + m_enter_pressed = true; + // If LayersList wasn't updated/recreated, we can call wxEVT_KILL_FOCUS.Skip() + if (m_type&etLayerHeight) { + if (!edit_fn(get_value(), true)) + SetValue(m_valid_value); + else + m_valid_value = double_to_string(get_value()); + m_call_kill_focus = true; + } + else if (!edit_fn(get_value(), true)) { + SetValue(m_valid_value); + m_call_kill_focus = true; + } + }, this->GetId()); + + this->Bind(wxEVT_KILL_FOCUS, [this, edit_fn](wxFocusEvent& e) + { + if (!m_enter_pressed) { +#ifndef __WXGTK__ + /* Update data for next editor selection. + * But under GTK it lucks like there is no information about selected control at e.GetWindow(), + * so we'll take it from wxEVT_LEFT_DOWN event + * */ + LayerRangeEditor* new_editor = dynamic_cast(e.GetWindow()); + if (new_editor) + new_editor->set_focus_data(); +#endif // not __WXGTK__ + // If LayersList wasn't updated/recreated, we should call e.Skip() + if (m_type & etLayerHeight) { + if (!edit_fn(get_value(), false)) + SetValue(m_valid_value); + else + m_valid_value = double_to_string(get_value()); + e.Skip(); + } + else if (!edit_fn(get_value(), false)) { + SetValue(m_valid_value); + e.Skip(); + } + } + else if (m_call_kill_focus) { + m_call_kill_focus = false; + e.Skip(); + } + }, this->GetId()); + + this->Bind(wxEVT_SET_FOCUS, [this, parent](wxFocusEvent& e) + { + set_focus_data(); + parent->update_scene_from_editor_selection(); + e.Skip(); + }, this->GetId()); + +#ifdef __WXGTK__ // Workaround! To take information about selectable range + this->Bind(wxEVT_LEFT_DOWN, [this](wxEvent& e) + { + set_focus_data(); + e.Skip(); + }, this->GetId()); +#endif //__WXGTK__ + + this->Bind(wxEVT_CHAR, ([this](wxKeyEvent& event) + { + // select all text using Ctrl+A + if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL)) + this->SetSelection(-1, -1); //select all + event.Skip(); + })); +} + +coordf_t LayerRangeEditor::get_value() +{ + wxString str = GetValue(); + + coordf_t layer_height; + // Replace the first occurence of comma in decimal number. + str.Replace(",", ".", false); + if (str == ".") + layer_height = 0.0; + else + { + if (!str.ToCDouble(&layer_height) || layer_height < 0.0f) + { + show_error(m_parent, _(L("Invalid numeric input."))); + SetValue(double_to_string(layer_height)); + } + } + + return layer_height; +} + +} //namespace GUI +} //namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/GUI_ObjectLayers.hpp b/src/slic3r/GUI/GUI_ObjectLayers.hpp new file mode 100644 index 0000000000..f274183e2b --- /dev/null +++ b/src/slic3r/GUI/GUI_ObjectLayers.hpp @@ -0,0 +1,88 @@ +#ifndef slic3r_GUI_ObjectLayers_hpp_ +#define slic3r_GUI_ObjectLayers_hpp_ + +#include "GUI_ObjectSettings.hpp" +#include "wxExtensions.hpp" + +#ifdef __WXOSX__ +#include "../libslic3r/PrintConfig.hpp" +#endif + +class wxBoxSizer; + +namespace Slic3r { +class ModelObject; + +namespace GUI { +class ConfigOptionsGroup; + +typedef double coordf_t; +typedef std::pair t_layer_height_range; + +class ObjectLayers; + +enum EditorType +{ + etUndef = 0, + etMinZ = 1, + etMaxZ = 2, + etLayerHeight = 4, +}; + +class LayerRangeEditor : public wxTextCtrl +{ + bool m_enter_pressed { false }; + bool m_call_kill_focus { false }; + wxString m_valid_value; + EditorType m_type; + + std::function m_set_focus_data; + +public: + LayerRangeEditor( ObjectLayers* parent, + const wxString& value = wxEmptyString, + EditorType type = etUndef, + std::function set_focus_data_fn = [](EditorType) {;}, + std::function edit_fn = [](coordf_t, bool) {return false; } + ); + ~LayerRangeEditor() {} + + EditorType type() const {return m_type;} + void set_focus_data() const { m_set_focus_data(m_type);} + +private: + coordf_t get_value(); +}; + +class ObjectLayers : public OG_Settings +{ + ScalableBitmap m_bmp_delete; + ScalableBitmap m_bmp_add; + ModelObject* m_object {nullptr}; + + wxFlexGridSizer* m_grid_sizer; + t_layer_height_range m_selectable_range; + EditorType m_selection_type {etUndef}; + +public: + ObjectLayers(wxWindow* parent); + ~ObjectLayers() {} + + void select_editor(LayerRangeEditor* editor, const bool is_last_edited_range); + wxSizer* create_layer(const t_layer_height_range& range); // without_buttons + void create_layers_list(); + void update_layers_list(); + + void update_scene_from_editor_selection() const; + + void UpdateAndShow(const bool show) override; + void msw_rescale(); + void reset_selection(); + void set_selectable_range(const t_layer_height_range& range) { m_selectable_range = range; } + + friend class LayerRangeEditor; +}; + +}} + +#endif // slic3r_GUI_ObjectLayers_hpp_ diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index dffa02e95b..94f3252ae7 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1,6 +1,7 @@ #include "libslic3r/libslic3r.h" #include "GUI_ObjectList.hpp" #include "GUI_ObjectManipulation.hpp" +#include "GUI_ObjectLayers.hpp" #include "GUI_App.hpp" #include "I18N.hpp" @@ -16,6 +17,10 @@ #include #include "slic3r/Utils/FixModelByWin10.hpp" +#ifdef __WXMSW__ +#include "wx/uiaction.h" +#endif /* __WXMSW__ */ + namespace Slic3r { namespace GUI @@ -24,18 +29,18 @@ namespace GUI wxDEFINE_EVENT(EVT_OBJ_LIST_OBJECT_SELECT, SimpleEvent); // pt_FFF -FreqSettingsBundle FREQ_SETTINGS_BUNDLE_FFF = +SettingsBundle FREQ_SETTINGS_BUNDLE_FFF = { { L("Layers and Perimeters"), { "layer_height" , "perimeters", "top_solid_layers", "bottom_solid_layers" } }, { L("Infill") , { "fill_density", "fill_pattern" } }, { L("Support material") , { "support_material", "support_material_auto", "support_material_threshold", "support_material_pattern", "support_material_buildplate_only", "support_material_spacing" } }, - { L("Extruders") , { "wipe_into_infill", "wipe_into_objects" } } + { L("Wipe options") , { "wipe_into_infill", "wipe_into_objects" } } }; // pt_SLA -FreqSettingsBundle FREQ_SETTINGS_BUNDLE_SLA = +SettingsBundle FREQ_SETTINGS_BUNDLE_SLA = { { L("Pad and Support") , { "supports_enable", "pad_enable" } } }; @@ -65,6 +70,11 @@ static int extruders_count() return wxGetApp().extruders_cnt(); } +static void take_snapshot(const wxString& snapshot_name) +{ + wxGetApp().plater()->take_snapshot(snapshot_name); +} + ObjectList::ObjectList(wxWindow* parent) : wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE), m_parent(parent) @@ -81,6 +91,7 @@ ObjectList::ObjectList(wxWindow* parent) : CATEGORY_ICON[L("Speed")] = create_scaled_bitmap(nullptr, "time"); CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap(nullptr, "funnel"); CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap(nullptr, "funnel"); + CATEGORY_ICON[L("Wipe options")] = create_scaled_bitmap(nullptr, "funnel"); // CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap(nullptr, "skirt+brim"); // CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap(nullptr, "time"); CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap(nullptr, "wrench"); @@ -112,6 +123,10 @@ ObjectList::ObjectList(wxWindow* parent) : * instead of real last clicked item. * So, let check last selected item in such strange way */ +#ifdef __WXMSW__ + // Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected. + int new_selected_column = -1; +#endif __WXMSW__ if (wxGetKeyState(WXK_SHIFT)) { wxDataViewItemArray sels; @@ -121,13 +136,34 @@ ObjectList::ObjectList(wxWindow* parent) : else m_last_selected_item = event.GetItem(); } - else - m_last_selected_item = event.GetItem(); + else { + wxDataViewItem new_selected_item = event.GetItem(); +#ifdef __WXMSW__ + // Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected. + wxDataViewItem item; + wxDataViewColumn *col; + this->HitTest(get_mouse_position_in_control(), item, col); + new_selected_column = (col == nullptr) ? -1 : (int)col->GetModelColumn(); + if (new_selected_item == m_last_selected_item && m_last_selected_column != -1 && m_last_selected_column != new_selected_column) { + // Mouse clicked on another column of the active row. Simulate keyboard enter to enter the editing mode of the current column. + wxUIActionSimulator sim; + sim.Char(WXK_RETURN); + } +#endif __WXMSW__ + m_last_selected_item = new_selected_item; + } +#ifdef __WXMSW__ + m_last_selected_column = new_selected_column; +#endif __WXMSW__ selection_changed(); #ifndef __WXMSW__ set_tooltip_for_item(get_mouse_position_in_control()); -#endif //__WXMSW__ +#endif //__WXMSW__ + +#ifndef __WXOSX__ + list_manipulation(); +#endif //__WXOSX__ }); #ifdef __WXOSX__ @@ -137,20 +173,24 @@ ObjectList::ObjectList(wxWindow* parent) : // Bind(wxEVT_KEY_DOWN, &ObjectList::OnChar, this); { // Accelerators - wxAcceleratorEntry entries[6]; + wxAcceleratorEntry entries[8]; entries[0].Set(wxACCEL_CTRL, (int) 'C', wxID_COPY); entries[1].Set(wxACCEL_CTRL, (int) 'X', wxID_CUT); entries[2].Set(wxACCEL_CTRL, (int) 'V', wxID_PASTE); entries[3].Set(wxACCEL_CTRL, (int) 'A', wxID_SELECTALL); - entries[4].Set(wxACCEL_NORMAL, WXK_DELETE, wxID_DELETE); - entries[5].Set(wxACCEL_NORMAL, WXK_BACK, wxID_DELETE); - wxAcceleratorTable accel(6, entries); + entries[4].Set(wxACCEL_CTRL, (int) 'Z', wxID_UNDO); + entries[5].Set(wxACCEL_CTRL, (int) 'Y', wxID_REDO); + entries[6].Set(wxACCEL_NORMAL, WXK_DELETE, wxID_DELETE); + entries[7].Set(wxACCEL_NORMAL, WXK_BACK, wxID_DELETE); + wxAcceleratorTable accel(8, entries); SetAcceleratorTable(accel); - this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_COPY)); }, wxID_COPY); - this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE)); }, wxID_PASTE); - this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->select_item_all_children(); }, wxID_SELECTALL); - this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->remove(); }, wxID_DELETE); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->copy(); }, wxID_COPY); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->paste(); }, wxID_PASTE); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->select_item_all_children(); }, wxID_SELECTALL); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->remove(); }, wxID_DELETE); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->undo(); }, wxID_UNDO); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->redo(); }, wxID_REDO); } #else __WXOSX__ Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { key_event(event); }); // doesn't work on OSX @@ -158,7 +198,7 @@ ObjectList::ObjectList(wxWindow* parent) : #ifdef __WXMSW__ GetMainWindow()->Bind(wxEVT_MOTION, [this](wxMouseEvent& event) { - set_tooltip_for_item(/*event.GetPosition()*/get_mouse_position_in_control()); + set_tooltip_for_item(get_mouse_position_in_control()); event.Skip(); }); #endif //__WXMSW__ @@ -169,13 +209,27 @@ ObjectList::ObjectList(wxWindow* parent) : Bind(wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE, &ObjectList::OnDropPossible, this); Bind(wxEVT_DATAVIEW_ITEM_DROP, &ObjectList::OnDrop, this); - Bind(wxEVT_DATAVIEW_ITEM_EDITING_DONE, &ObjectList::OnEditingDone, this); +#ifdef __WXMSW__ + Bind(wxEVT_DATAVIEW_ITEM_EDITING_STARTED, &ObjectList::OnEditingStarted, this); +#endif /* __WXMSW__ */ + Bind(wxEVT_DATAVIEW_ITEM_EDITING_DONE, &ObjectList::OnEditingDone, this); Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &ObjectList::ItemValueChanged, this); Bind(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, [this](wxCommandEvent& e) { last_volume_is_deleted(e.GetInt()); }); - Bind(wxEVT_SIZE, ([this](wxSizeEvent &e) { this->EnsureVisible(this->GetCurrentItem()); e.Skip(); })); + Bind(wxEVT_SIZE, ([this](wxSizeEvent &e) { +#ifdef __WXGTK__ + // On GTK, the EnsureVisible call is postponed to Idle processing (see wxDataViewCtrl::m_ensureVisibleDefered). + // So the postponed EnsureVisible() call is planned for an item, which may not exist at the Idle processing time, if this wxEVT_SIZE + // event is succeeded by a delete of the currently active item. We are trying our luck by postponing the wxEVT_SIZE triggered EnsureVisible(), + // which seems to be working as of now. + this->CallAfter([this](){ this->EnsureVisible(this->GetCurrentItem()); }); +#else + this->EnsureVisible(this->GetCurrentItem()); +#endif + e.Skip(); + })); } ObjectList::~ObjectList() @@ -201,16 +255,20 @@ void ObjectList::create_objects_ctrl() EnableDropTarget(wxDF_UNICODETEXT); #endif // wxUSE_DRAG_AND_DROP && wxUSE_UNICODE - // column 0(Icon+Text) of the view control: + // column ItemName(Icon+Text) of the view control: // And Icon can be consisting of several bitmaps AppendColumn(new wxDataViewColumn(_(L("Name")), new BitmapTextRenderer(), - 0, 20*wxGetApp().em_unit()/*200*/, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE)); + colName, 20*wxGetApp().em_unit()/*200*/, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE)); - // column 1 of the view control: + // column PrintableProperty (Icon) of the view control: + AppendBitmapColumn(" ", colPrint, wxDATAVIEW_CELL_INERT, int(2 * wxGetApp().em_unit()), + wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); + + // column Extruder of the view control: AppendColumn(create_objects_list_extruder_column(4)); - // column 2 of the view control: - AppendBitmapColumn(" ", 2, wxDATAVIEW_CELL_INERT, int(2.5 * wxGetApp().em_unit())/*25*/, + // column ItemEditing of the view control: + AppendBitmapColumn("Editing", colEditing, wxDATAVIEW_CELL_INERT, int(2.5 * wxGetApp().em_unit())/*25*/, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); } @@ -261,7 +319,7 @@ wxString ObjectList::get_mesh_errors_list(const int obj_idx, const int vol_idx / const stl_stats& stats = vol_idx == -1 ? (*m_objects)[obj_idx]->get_object_stl_stats() : - (*m_objects)[obj_idx]->volumes[vol_idx]->mesh.stl.stats; + (*m_objects)[obj_idx]->volumes[vol_idx]->mesh().stl.stats; std::map error_msg = { { L("degenerate facets"), stats.degenerate_facets }, @@ -304,28 +362,34 @@ void ObjectList::set_tooltip_for_item(const wxPoint& pt) * Just this->SetToolTip(tooltip) => has no effect. */ - if (!item) + if (!item || GetSelectedItemsCount() > 1) { GetMainWindow()->SetToolTip(""); // hide tooltip return; } - if (col->GetTitle() == " " && GetSelectedItemsCount()<2) - GetMainWindow()->SetToolTip(_(L("Right button click the icon to change the object settings"))); - else if (col->GetTitle() == _("Name")) - { -#ifdef __WXMSW__ - if (pt.x < 2 * wxGetApp().em_unit() || pt.x > 4 * wxGetApp().em_unit()) { - GetMainWindow()->SetToolTip(""); // hide tooltip - return; - } + wxString tooltip = ""; + + if (col->GetTitle() == _(L("Editing"))) +#ifdef __WXOSX__ + tooltip = _(L("Right button click the icon to change the object settings")); +#else + tooltip = _(L("Click the icon to change the object settings")); #endif //__WXMSW__ + else if (col->GetTitle() == " ") +#ifdef __WXOSX__ + tooltip = _(L("Right button click the icon to change the object printable property")); +#else + tooltip = _(L("Click the icon to change the object printable property")); +#endif //__WXMSW__ + else if (col->GetTitle() == _("Name") && (pt.x >= 2 * wxGetApp().em_unit() && pt.x <= 4 * wxGetApp().em_unit())) + { int obj_idx, vol_idx; get_selected_item_indexes(obj_idx, vol_idx, item); - GetMainWindow()->SetToolTip(get_mesh_errors_list(obj_idx, vol_idx)); + tooltip = get_mesh_errors_list(obj_idx, vol_idx); } - else - GetMainWindow()->SetToolTip(""); // hide tooltip + + GetMainWindow()->SetToolTip(tooltip); } wxPoint ObjectList::get_mouse_position_in_control() @@ -349,13 +413,12 @@ DynamicPrintConfig& ObjectList::get_item_config(const wxDataViewItem& item) cons assert(item); const ItemType type = m_objects_model->GetItemType(item); - const int obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) : - m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); - + const int obj_idx = m_objects_model->GetObjectIdByItem(item); const int vol_idx = type & itVolume ? m_objects_model->GetVolumeIdByItem(item) : -1; assert(obj_idx >= 0 || ((type & itVolume) && vol_idx >=0)); return type & itVolume ?(*m_objects)[obj_idx]->volumes[vol_idx]->config : + type & itLayer ?(*m_objects)[obj_idx]->layer_config_ranges[m_objects_model->GetLayerRangeByItem(item)] : (*m_objects)[obj_idx]->config; } @@ -367,7 +430,7 @@ wxDataViewColumn* ObjectList::create_objects_list_extruder_column(int extruders_ choices.Add(wxString::Format("%d", i)); wxDataViewChoiceRenderer *c = new wxDataViewChoiceRenderer(choices, wxDATAVIEW_CELL_EDITABLE, wxALIGN_CENTER_HORIZONTAL); - wxDataViewColumn* column = new wxDataViewColumn(_(L("Extruder")), c, 1, + wxDataViewColumn* column = new wxDataViewColumn(_(L("Extruder")), c, colExtruder, 8*wxGetApp().em_unit()/*80*/, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); return column; } @@ -387,7 +450,7 @@ void ObjectList::update_extruder_values_for_items(const int max_extruder) else extruder = wxString::Format("%d", object->config.option("extruder")->value); - m_objects_model->SetValue(extruder, item, 1); + m_objects_model->SetValue(extruder, item, colExtruder); if (object->volumes.size() > 1) { for (auto id = 0; id < object->volumes.size(); id++) { @@ -399,7 +462,7 @@ void ObjectList::update_extruder_values_for_items(const int max_extruder) else extruder = wxString::Format("%d", object->volumes[id]->config.option("extruder")->value); - m_objects_model->SetValue(extruder, item, 1); + m_objects_model->SetValue(extruder, item, colExtruder); } } } @@ -411,7 +474,7 @@ void ObjectList::update_objects_list_extruder_column(int extruders_count) if (printer_technology() == ptSLA) extruders_count = 1; - wxDataViewChoiceRenderer* ch_render = dynamic_cast(GetColumn(1)->GetRenderer()); + wxDataViewChoiceRenderer* ch_render = dynamic_cast(GetColumn(colExtruder)->GetRenderer()); if (ch_render->GetChoices().GetCount() - 1 == extruders_count) return; @@ -420,47 +483,54 @@ void ObjectList::update_objects_list_extruder_column(int extruders_count) if (m_objects && extruders_count > 1) update_extruder_values_for_items(extruders_count); - // delete old 2nd column - DeleteColumn(GetColumn(1)); - // insert new created 3rd column - InsertColumn(1, create_objects_list_extruder_column(extruders_count)); + // delete old extruder column + DeleteColumn(GetColumn(colExtruder)); + // insert new created extruder column + InsertColumn(colExtruder, create_objects_list_extruder_column(extruders_count)); // set show/hide for this column set_extruder_column_hidden(extruders_count <= 1); //a workaround for a wrong last column width updating under OSX - GetColumn(2)->SetWidth(25); + GetColumn(colEditing)->SetWidth(25); m_prevent_update_extruder_in_config = false; } void ObjectList::set_extruder_column_hidden(const bool hide) const { - GetColumn(1)->SetHidden(hide); + GetColumn(colExtruder)->SetHidden(hide); } void ObjectList::update_extruder_in_config(const wxDataViewItem& item) { if (m_prevent_update_extruder_in_config) return; - if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { + + const ItemType item_type = m_objects_model->GetItemType(item); + if (item_type & itObject) { const int obj_idx = m_objects_model->GetIdByItem(item); m_config = &(*m_objects)[obj_idx]->config; } else { - const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetParent(item)); + const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); + if (item_type & itVolume) + { const int volume_id = m_objects_model->GetVolumeIdByItem(item); if (obj_idx < 0 || volume_id < 0) return; m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; + } + else if (item_type & itLayer) + m_config = &get_item_config(item); } wxVariant variant; - m_objects_model->GetValue(variant, item, 1); + m_objects_model->GetValue(variant, item, colExtruder); const wxString selection = variant.GetString(); if (!m_config || selection.empty()) return; - const int extruder = selection.size() > 1 ? 0 : atoi(selection.c_str()); + const int extruder = /*selection.size() > 1 ? 0 : */atoi(selection.c_str()); m_config->set_key_value("extruder", new ConfigOptionInt(extruder)); // update scene @@ -471,13 +541,15 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const { const int obj_idx = m_objects_model->GetObjectIdByItem(item); if (obj_idx < 0) return; + const int volume_id = m_objects_model->GetVolumeIdByItem(item); - if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { + take_snapshot(volume_id < 0 ? _(L("Rename Object")) : _(L("Rename Sub-object"))); + + if (m_objects_model->GetItemType(item) & itObject) { (*m_objects)[obj_idx]->name = m_objects_model->GetName(item).ToUTF8().data(); return; } - const int volume_id = m_objects_model->GetVolumeIdByItem(item); if (volume_id < 0) return; (*m_objects)[obj_idx]->volumes[volume_id]->name = m_objects_model->GetName(item).ToUTF8().data(); } @@ -542,6 +614,7 @@ void ObjectList::msw_rescale_icons() CATEGORY_ICON[L("Speed")] = create_scaled_bitmap(nullptr, "time"); CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap(nullptr, "funnel"); CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap(nullptr, "funnel"); + CATEGORY_ICON[L("Wipe options")] = create_scaled_bitmap(nullptr, "funnel"); // CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap(nullptr, "skirt+brim"); // CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap(nullptr, "time"); CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap(nullptr, "wrench"); @@ -569,9 +642,75 @@ void ObjectList::selection_changed() wxPostEvent(this, event); } + if (const wxDataViewItem item = GetSelection()) + { + const ItemType type = m_objects_model->GetItemType(item); + // to correct visual hints for layers editing on the Scene + if (type & (itLayer|itLayerRoot)) { + wxGetApp().obj_layers()->reset_selection(); + + if (type & itLayerRoot) + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); + else { + wxGetApp().obj_layers()->set_selectable_range(m_objects_model->GetLayerRangeByItem(item)); + wxGetApp().obj_layers()->update_scene_from_editor_selection(); + } + } + } + part_selection_changed(); } +void ObjectList::fill_layer_config_ranges_cache() +{ + wxDataViewItemArray sel_layers; + GetSelections(sel_layers); + + const int obj_idx = m_objects_model->GetObjectIdByItem(sel_layers[0]); + if (obj_idx < 0 || (int)m_objects->size() <= obj_idx) + return; + + const t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + m_layer_config_ranges_cache.clear(); + + for (const auto layer_item : sel_layers) + if (m_objects_model->GetItemType(layer_item) & itLayer) { + auto range = m_objects_model->GetLayerRangeByItem(layer_item); + auto it = ranges.find(range); + if (it != ranges.end()) + m_layer_config_ranges_cache[it->first] = it->second; + } +} + +void ObjectList::paste_layers_into_list() +{ + const int obj_idx = m_objects_model->GetObjectIdByItem(GetSelection()); + + if (obj_idx < 0 || (int)m_objects->size() <= obj_idx || + m_layer_config_ranges_cache.empty() || printer_technology() == ptSLA) + return; + + const wxDataViewItem object_item = m_objects_model->GetItemById(obj_idx); + wxDataViewItem layers_item = m_objects_model->GetLayerRootItem(object_item); + if (layers_item) + m_objects_model->Delete(layers_item); + + t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + + // and create Layer item(s) according to the layer_config_ranges + for (const auto range : m_layer_config_ranges_cache) + ranges.emplace(range); + + layers_item = add_layer_root_item(object_item); + + changed_object(obj_idx); + + select_item(layers_item); +#ifndef __WXOSX__ + selection_changed(); +#endif //no __WXOSX__ +} + void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes) { if ((obj_idx < 0) || ((int)m_objects->size() <= obj_idx)) @@ -580,7 +719,6 @@ void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& vol if (volumes.empty()) return; - ModelObject& model_object = *(*m_objects)[obj_idx]; const auto object_item = m_objects_model->GetItemById(obj_idx); wxDataViewItemArray items; @@ -590,10 +728,7 @@ void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& vol const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(object_item, wxString::FromUTF8(volume->name.c_str()), volume->type(), volume->get_mesh_errors_count()>0 , volume->config.has("extruder") ? volume->config.option("extruder")->value : 0); - auto opt_keys = volume->config.keys(); - if (!opt_keys.empty() && !((opt_keys.size() == 1) && (opt_keys[0] == "extruder"))) - select_item(m_objects_model->AddSettingsChild(vol_item)); - + add_settings_item(vol_item, &volume->config); items.Add(vol_item); } @@ -647,26 +782,31 @@ void ObjectList::OnChar(wxKeyEvent& event) #endif /* __WXOSX__ */ void ObjectList::OnContextMenu(wxDataViewEvent&) +{ + list_manipulation(); +} + +void ObjectList::list_manipulation() { wxDataViewItem item; wxDataViewColumn* col; const wxPoint pt = get_mouse_position_in_control(); HitTest(pt, item, col); - if (!item) -#ifdef __WXOSX__ // #ys_FIXME temporary workaround for OSX - // after Yosemite OS X version, HitTest return undefined item - item = GetSelection(); - if (item) - show_context_menu(); - else - printf("undefined item\n"); - return; -#else - return; +#ifdef __WXOSX__ // temporary workaround for OSX + // after Yosemite OS X version, HitTest return undefined item + if (!item) item = GetSelection(); #endif // __WXOSX__ + + if (!item) { + printf("undefined item\n"); + return; + } + const wxString title = col->GetTitle(); if (title == " ") + toggle_printable_state(item); + else if (title == _("Editing")) show_context_menu(); else if (title == _("Name")) { @@ -699,10 +839,11 @@ void ObjectList::show_context_menu() if (item) { const ItemType type = m_objects_model->GetItemType(item); - if (!(type & (itObject | itVolume | itInstance))) + if (!(type & (itObject | itVolume | itLayer | itInstance))) return; wxMenu* menu = type & itInstance ? &m_menu_instance : + type & itLayer ? &m_menu_layer : m_objects_model->GetParent(item) != wxDataViewItem(0) ? &m_menu_part : printer_technology() == ptFFF ? &m_menu_object : &m_menu_sla_object; @@ -713,6 +854,34 @@ void ObjectList::show_context_menu() } } +void ObjectList::copy() +{ + // if (m_selection_mode & smLayer) + // fill_layer_config_ranges_cache(); + // else { + // m_layer_config_ranges_cache.clear(); + wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_COPY)); + // } +} + +void ObjectList::paste() +{ + // if (!m_layer_config_ranges_cache.empty()) + // paste_layers_into_list(); + // else + wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE)); +} + +void ObjectList::undo() +{ + wxGetApp().plater()->undo(); +} + +void ObjectList::redo() +{ + wxGetApp().plater()->redo(); +} + #ifndef __WXOSX__ void ObjectList::key_event(wxKeyEvent& event) { @@ -727,10 +896,14 @@ void ObjectList::key_event(wxKeyEvent& event) } else if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL/*WXK_SHIFT*/)) select_item_all_children(); - else if (wxGetKeyState(wxKeyCode('C')) && wxGetKeyState(WXK_CONTROL)) - wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_COPY)); + else if (wxGetKeyState(wxKeyCode('C')) && wxGetKeyState(WXK_CONTROL)) + copy(); else if (wxGetKeyState(wxKeyCode('V')) && wxGetKeyState(WXK_CONTROL)) - wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE)); + paste(); + else if (wxGetKeyState(wxKeyCode('Y')) && wxGetKeyState(WXK_CONTROL)) + redo(); + else if (wxGetKeyState(wxKeyCode('Z')) && wxGetKeyState(WXK_CONTROL)) + undo(); else event.Skip(); } @@ -816,6 +989,7 @@ void ObjectList::OnDrop(wxDataViewEvent &event) if (m_dragged_data.type() == itInstance) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(),_(L("Instances to Separated Objects"))); instances_to_separated_object(m_dragged_data.obj_idx(), m_dragged_data.inst_idxs()); m_dragged_data.clear(); return; @@ -833,6 +1007,8 @@ void ObjectList::OnDrop(wxDataViewEvent &event) // if (to_volume_id > from_volume_id) to_volume_id--; // #endif // __WXGTK__ + take_snapshot(_(L("Remove Volume(s)"))); + auto& volumes = (*m_objects)[m_dragged_data.obj_idx()]->volumes; auto delta = to_volume_id < from_volume_id ? -1 : 1; int cnt = 0; @@ -871,7 +1047,7 @@ std::vector ObjectList::get_options(const bool is_part) const std::vector& ObjectList::get_options_for_bundle(const wxString& bundle_name) { - const FreqSettingsBundle& bundle = printer_technology() == ptSLA ? + const SettingsBundle& bundle = printer_technology() == ptSLA ? FREQ_SETTINGS_BUNDLE_SLA : FREQ_SETTINGS_BUNDLE_FFF; for (auto& it : bundle) @@ -881,7 +1057,7 @@ const std::vector& ObjectList::get_options_for_bundle(const wxStrin } #if 0 // if "Quick menu" is selected - FreqSettingsBundle& bundle_quick = printer_technology() == ptSLA ? + SettingsBundle& bundle_quick = printer_technology() == ptSLA ? m_freq_settings_sla: m_freq_settings_fff; for (auto& it : bundle_quick) @@ -895,6 +1071,11 @@ const std::vector& ObjectList::get_options_for_bundle(const wxStrin return empty; } +static bool improper_category(const std::string& category, const int extruders_cnt) +{ + return category.empty() || (extruders_cnt == 1 && (category == "Extruders" || category == "Wipe options" )); +} + void ObjectList::get_options_menu(settings_menu_hierarchy& settings_menu, const bool is_part) { auto options = get_options(is_part); @@ -906,8 +1087,8 @@ void ObjectList::get_options_menu(settings_menu_hierarchy& settings_menu, const { auto const opt = config.def()->get(option); auto category = opt->category; - if (category.empty() || - (category == "Extruders" && extruders_cnt == 1)) continue; + if (improper_category(category, extruders_cnt)) + continue; const std::string& label = !opt->full_label.empty() ? opt->full_label : opt->label; std::pair option_label(option, label); @@ -923,12 +1104,19 @@ void ObjectList::get_settings_choice(const wxString& category_name) { wxArrayString names; wxArrayInt selections; + wxDataViewItem item = GetSelection(); + + const ItemType item_type = m_objects_model->GetItemType(item); settings_menu_hierarchy settings_menu; - const bool is_part = m_objects_model->GetParent(GetSelection()) != wxDataViewItem(0); + const bool is_part = item_type & (itVolume | itLayer); get_options_menu(settings_menu, is_part); std::vector< std::pair > *settings_list = nullptr; + if (!m_config) + m_config = &get_item_config(item); + + assert(m_config); auto opt_keys = m_config->keys(); for (auto& cat : settings_menu) @@ -957,7 +1145,7 @@ void ObjectList::get_settings_choice(const wxString& category_name) if (selection_cnt > 0) { // Add selected items to the "Quick menu" - FreqSettingsBundle& freq_settings = printer_technology() == ptSLA ? + SettingsBundle& freq_settings = printer_technology() == ptSLA ? m_freq_settings_sla : m_freq_settings_fff; bool changed_existing = false; @@ -998,6 +1186,11 @@ void ObjectList::get_settings_choice(const wxString& category_name) } #endif + const wxString snapshot_text = item_type & itLayer ? _(L("Add Settings for Layers")) : + item_type & itVolume ? _(L("Add Settings for Sub-object")) : + _(L("Add Settings for Object")); + take_snapshot(snapshot_text); + std::vector selected_options; selected_options.reserve(selection_cnt); for (auto sel : selections) @@ -1027,17 +1220,40 @@ void ObjectList::get_settings_choice(const wxString& category_name) } - // Add settings item for object - update_settings_item(); + // Add settings item for object/sub-object and show them + if (!(item_type & (itObject | itVolume | itLayer))) + item = m_objects_model->GetTopParent(item); + show_settings(add_settings_item(item, m_config)); } void ObjectList::get_freq_settings_choice(const wxString& bundle_name) { - const std::vector& options = get_options_for_bundle(bundle_name); + std::vector options = get_options_for_bundle(bundle_name); + wxDataViewItem item = GetSelection(); + + ItemType item_type = m_objects_model->GetItemType(item); + + /* Because of we couldn't edited layer_height for ItVolume from settings list, + * correct options according to the selected item type : + * remove "layer_height" option + */ + if ((item_type & itVolume) && bundle_name == _("Layers and Perimeters")) { + const auto layer_height_it = std::find(options.begin(), options.end(), "layer_height"); + if (layer_height_it != options.end()) + options.erase(layer_height_it); + } + + if (!m_config) + m_config = &get_item_config(item); assert(m_config); auto opt_keys = m_config->keys(); + const wxString snapshot_text = item_type & itLayer ? _(L("Add Settings Bundle for Layers")) : + item_type & itVolume ? _(L("Add Settings Bundle for Sub-object")) : + _(L("Add Settings Bundle for Object")); + take_snapshot(snapshot_text); + const DynamicPrintConfig& from_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; for (auto& opt_key : options) { @@ -1052,30 +1268,22 @@ void ObjectList::get_freq_settings_choice(const wxString& bundle_name) } } - // Add settings item for object - update_settings_item(); + // Add settings item for object/sub-object and show them + if (!(item_type & (itObject | itVolume | itLayer))) + item = m_objects_model->GetTopParent(item); + show_settings(add_settings_item(item, m_config)); } -void ObjectList::update_settings_item() +void ObjectList::show_settings(const wxDataViewItem settings_item) { - auto item = GetSelection(); - if (item) { - if (m_objects_model->GetItemType(item) == itInstance) - item = m_objects_model->GetTopParent(item); - const auto settings_item = m_objects_model->IsSettingsItem(item) ? item : m_objects_model->GetSettingsItem(item); - select_item(settings_item ? settings_item : - m_objects_model->AddSettingsChild(item)); + if (!settings_item) + return; - // update object selection on Plater - if (!m_prevent_canvas_selection_update) - update_selections_on_canvas(); - } - else { - auto panel = wxGetApp().sidebar().scrolled_panel(); - panel->Freeze(); - wxGetApp().obj_settings()->UpdateAndShow(true); - panel->Thaw(); - } + select_item(settings_item); + + // update object selection on Plater + if (!m_prevent_canvas_selection_update) + update_selections_on_canvas(); } wxMenu* ObjectList::append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type) { @@ -1137,6 +1345,12 @@ wxMenuItem* ObjectList::append_menu_item_split(wxMenu* menu) [this]() { return is_splittable(); }, wxGetApp().plater()); } +wxMenuItem* ObjectList::append_menu_item_layers_editing(wxMenu* menu) +{ + return append_menu_item(menu, wxID_ANY, _(L("Edit Layers")), "", + [this](wxCommandEvent&) { layers_editing(); }, "edit_layers_all", menu); +} + wxMenuItem* ObjectList::append_menu_item_settings(wxMenu* menu_) { MenuWithSeparators* menu = dynamic_cast(menu_); @@ -1219,6 +1433,13 @@ wxMenuItem* ObjectList::append_menu_item_instance_to_object(wxMenu* menu, wxWind [this](wxCommandEvent&) { split_instances(); }, "", menu, [](){return wxGetApp().plater()->can_set_instance_to_object(); }, parent); } +wxMenuItem* ObjectList::append_menu_item_printable(wxMenu* menu, wxWindow* parent) +{ + return append_menu_check_item(menu, wxID_ANY, _(L("Printable")), "", [this](wxCommandEvent&) { + wxGetApp().plater()->canvas3D()->get_selection().toggle_instance_printable_state(); + }, menu); +} + void ObjectList::append_menu_items_osx(wxMenu* menu) { append_menu_item(menu, wxID_ANY, _(L("Rename")), "", @@ -1301,7 +1522,11 @@ void ObjectList::create_object_popupmenu(wxMenu *menu) append_menu_item_scale_selection_to_fit_print_volume(menu); // Split object to parts - m_menu_item_split = append_menu_item_split(menu); + append_menu_item_split(menu); + menu->AppendSeparator(); + + // Layers Editing for object + append_menu_item_layers_editing(menu); menu->AppendSeparator(); // rest of a object_menu will be added later in: @@ -1330,7 +1555,7 @@ void ObjectList::create_part_popupmenu(wxMenu *menu) append_menu_item_fix_through_netfabb(menu); append_menu_item_export_stl(menu); - m_menu_item_split_part = append_menu_item_split(menu); + append_menu_item_split(menu); // Append change part type menu->AppendSeparator(); @@ -1376,13 +1601,13 @@ wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu) void ObjectList::create_freq_settings_popupmenu(wxMenu *menu) { // Add default settings bundles - const FreqSettingsBundle& bundle = printer_technology() == ptFFF ? + const SettingsBundle& bundle = printer_technology() == ptFFF ? FREQ_SETTINGS_BUNDLE_FFF : FREQ_SETTINGS_BUNDLE_SLA; const int extruders_cnt = extruders_count(); for (auto& it : bundle) { - if (it.first.empty() || it.first == "Extruders" && extruders_cnt == 1) + if (improper_category(it.first, extruders_cnt)) continue; append_menu_item(menu, wxID_ANY, _(it.first), "", @@ -1391,11 +1616,11 @@ void ObjectList::create_freq_settings_popupmenu(wxMenu *menu) } #if 0 // Add "Quick" settings bundles - const FreqSettingsBundle& bundle_quick = printer_technology() == ptFFF ? + const SettingsBundle& bundle_quick = printer_technology() == ptFFF ? m_freq_settings_fff : m_freq_settings_sla; for (auto& it : bundle_quick) { - if (it.first.empty() || it.first == "Extruders" && extruders_cnt == 1) + if (improper_category(it.first, extruders_cnt)) continue; append_menu_item(menu, wxID_ANY, wxString::Format(_(L("Quick Add Settings (%s)")), _(it.first)), "", @@ -1405,9 +1630,9 @@ void ObjectList::create_freq_settings_popupmenu(wxMenu *menu) #endif } -void ObjectList::update_opt_keys(t_config_option_keys& opt_keys) +void ObjectList::update_opt_keys(t_config_option_keys& opt_keys, const bool is_object) { - auto full_current_opts = get_options(false); + auto full_current_opts = get_options(!is_object); for (int i = opt_keys.size()-1; i >= 0; --i) if (find(full_current_opts.begin(), full_current_opts.end(), opt_keys[i]) == full_current_opts.end()) opt_keys.erase(opt_keys.begin() + i); @@ -1415,18 +1640,28 @@ void ObjectList::update_opt_keys(t_config_option_keys& opt_keys) void ObjectList::load_subobject(ModelVolumeType type) { - auto item = GetSelection(); - if (!item || m_objects_model->GetParent(item) != wxDataViewItem(0)) + wxDataViewItem item = GetSelection(); + // we can add volumes for Object or Instance + if (!item || !(m_objects_model->GetItemType(item)&(itObject|itInstance))) return; - int obj_idx = m_objects_model->GetIdByItem(item); + const int obj_idx = m_objects_model->GetObjectIdByItem(item); if (obj_idx < 0) return; + // Get object item, if Instance is selected + if (m_objects_model->GetItemType(item)&itInstance) + item = m_objects_model->GetItemById(obj_idx); + + take_snapshot(_(L("Load Part"))); + std::vector> volumes_info; load_part((*m_objects)[obj_idx], volumes_info, type); changed_object(obj_idx); + if (type == ModelVolumeType::MODEL_PART) + // update printable state on canvas + wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object((size_t)obj_idx); wxDataViewItem sel_item; for (const auto& volume : volumes_info ) @@ -1480,66 +1715,6 @@ void ObjectList::load_part( ModelObject* model_object, } -// Find volume transformation, so that the chained (instance_trafo * volume_trafo) will be as close to identity -// as possible in least squares norm in regard to the 8 corners of bbox. -// Bounding box is expected to be centered around zero in all axes. -Geometry::Transformation volume_to_bed_transformation(const Geometry::Transformation &instance_transformation, const BoundingBoxf3 &bbox) -{ - Geometry::Transformation out; - - if (instance_transformation.is_scaling_uniform()) { - // No need to run the non-linear least squares fitting for uniform scaling. - // Just set the inverse. - out.set_from_transform(instance_transformation.get_matrix(true).inverse()); - } - else if (Geometry::is_rotation_ninety_degrees(instance_transformation.get_rotation())) - { - // Anisotropic scaling, rotation by multiples of ninety degrees. - Eigen::Matrix3d instance_rotation_trafo = - (Eigen::AngleAxisd(instance_transformation.get_rotation().z(), Vec3d::UnitZ()) * - Eigen::AngleAxisd(instance_transformation.get_rotation().y(), Vec3d::UnitY()) * - Eigen::AngleAxisd(instance_transformation.get_rotation().x(), Vec3d::UnitX())).toRotationMatrix(); - Eigen::Matrix3d volume_rotation_trafo = - (Eigen::AngleAxisd(-instance_transformation.get_rotation().x(), Vec3d::UnitX()) * - Eigen::AngleAxisd(-instance_transformation.get_rotation().y(), Vec3d::UnitY()) * - Eigen::AngleAxisd(-instance_transformation.get_rotation().z(), Vec3d::UnitZ())).toRotationMatrix(); - - // 8 corners of the bounding box. - auto pts = Eigen::MatrixXd(8, 3); - pts(0, 0) = bbox.min.x(); pts(0, 1) = bbox.min.y(); pts(0, 2) = bbox.min.z(); - pts(1, 0) = bbox.min.x(); pts(1, 1) = bbox.min.y(); pts(1, 2) = bbox.max.z(); - pts(2, 0) = bbox.min.x(); pts(2, 1) = bbox.max.y(); pts(2, 2) = bbox.min.z(); - pts(3, 0) = bbox.min.x(); pts(3, 1) = bbox.max.y(); pts(3, 2) = bbox.max.z(); - pts(4, 0) = bbox.max.x(); pts(4, 1) = bbox.min.y(); pts(4, 2) = bbox.min.z(); - pts(5, 0) = bbox.max.x(); pts(5, 1) = bbox.min.y(); pts(5, 2) = bbox.max.z(); - pts(6, 0) = bbox.max.x(); pts(6, 1) = bbox.max.y(); pts(6, 2) = bbox.min.z(); - pts(7, 0) = bbox.max.x(); pts(7, 1) = bbox.max.y(); pts(7, 2) = bbox.max.z(); - - // Corners of the bounding box transformed into the modifier mesh coordinate space, with inverse rotation applied to the modifier. - auto qs = pts * - (instance_rotation_trafo * - Eigen::Scaling(instance_transformation.get_scaling_factor().cwiseProduct(instance_transformation.get_mirror())) * - volume_rotation_trafo).inverse().transpose(); - // Fill in scaling based on least squares fitting of the bounding box corners. - Vec3d scale; - for (int i = 0; i < 3; ++ i) - scale(i) = pts.col(i).dot(qs.col(i)) / pts.col(i).dot(pts.col(i)); - - out.set_rotation(Geometry::extract_euler_angles(volume_rotation_trafo)); - out.set_scaling_factor(Vec3d(std::abs(scale(0)), std::abs(scale(1)), std::abs(scale(2)))); - out.set_mirror(Vec3d(scale(0) > 0 ? 1. : -1, scale(1) > 0 ? 1. : -1, scale(2) > 0 ? 1. : -1)); - } - else - { - // General anisotropic scaling, general rotation. - // Keep the modifier mesh in the instance coordinate system, so the modifier mesh will not be aligned with the world. - // Scale it to get the required size. - out.set_scaling_factor(instance_transformation.get_scaling_factor().cwiseInverse()); - } - - return out; -} - void ObjectList::load_generic_subobject(const std::string& type_name, const ModelVolumeType type) { const auto obj_idx = get_selected_obj_idx(); @@ -1557,6 +1732,8 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode if (instance_idx == -1) return; + take_snapshot(_(L("Add Generic Subobject"))); + // Selected object ModelObject &model_object = *(*m_objects)[obj_idx]; // Bounding box of the selected instance in world coordinate system including the translation, without modifiers. @@ -1592,8 +1769,8 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode // First (any) GLVolume of the selected instance. They all share the same instance matrix. const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); // Transform the new modifier to be aligned with the print bed. - const BoundingBoxf3 mesh_bb = new_volume->mesh.bounding_box(); - new_volume->set_transformation(volume_to_bed_transformation(v->get_instance_transformation(), mesh_bb)); + const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); + new_volume->set_transformation(Geometry::Transformation::volume_to_bed_transformation(v->get_instance_transformation(), mesh_bb)); // Set the modifier position. auto offset = (type_name == "Slab") ? // Slab: Lift to print bed @@ -1608,6 +1785,9 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); changed_object(obj_idx); + if (type == ModelVolumeType::MODEL_PART) + // update printable state on canvas + wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object((size_t)obj_idx); const auto object_item = m_objects_model->GetTopParent(GetSelection()); select_item(m_objects_model->AddVolumeChild(object_item, name, type, @@ -1634,35 +1814,63 @@ void ObjectList::del_subobject_item(wxDataViewItem& item) if (type == itUndef) return; - if (type == itSettings) - del_settings_from_config(); - else if (type == itInstanceRoot && obj_idx != -1) + if (type & itSettings) + del_settings_from_config(m_objects_model->GetParent(item)); + else if (type & itInstanceRoot && obj_idx != -1) del_instances_from_object(obj_idx); + else if (type & itLayerRoot && obj_idx != -1) + del_layers_from_object(obj_idx); + else if (type & itLayer && obj_idx != -1) + del_layer_from_object(obj_idx, m_objects_model->GetLayerRangeByItem(item)); else if (idx == -1) return; else if (!del_subobject_from_object(obj_idx, idx, type)) return; // If last volume item with warning was deleted, unmark object item - if (type == itVolume && (*m_objects)[obj_idx]->get_mesh_errors_count() == 0) + if (type & itVolume && (*m_objects)[obj_idx]->get_mesh_errors_count() == 0) m_objects_model->DeleteWarningIcon(m_objects_model->GetParent(item)); + // If last two Instances of object is selected, show the message about impossible action + bool show_msg = false; + if (type & itInstance) { + wxDataViewItemArray instances; + m_objects_model->GetChildren(m_objects_model->GetParent(item), instances); + if (instances.Count() == 2 && IsSelected(instances[0]) && IsSelected(instances[1])) + show_msg = true; + } + m_objects_model->Delete(item); + + if (show_msg) + Slic3r::GUI::show_error(nullptr, _(L("Last instance of an object cannot be deleted."))); } -void ObjectList::del_settings_from_config() +void ObjectList::del_settings_from_config(const wxDataViewItem& parent_item) { - auto opt_keys = m_config->keys(); - if (opt_keys.size() == 1 && opt_keys[0] == "extruder") + const bool is_layer_settings = m_objects_model->GetItemType(parent_item) == itLayer; + + const int opt_cnt = m_config->keys().size(); + if (opt_cnt == 1 && m_config->has("extruder") || + is_layer_settings && opt_cnt == 2 && m_config->has("extruder") && m_config->has("layer_height")) return; + + take_snapshot(_(L("Delete Settings"))); + int extruder = -1; if (m_config->has("extruder")) extruder = m_config->option("extruder")->value; + coordf_t layer_height = 0.0; + if (is_layer_settings) + layer_height = m_config->opt_float("layer_height"); + m_config->clear(); if (extruder >= 0) m_config->set_key_value("extruder", new ConfigOptionInt(extruder)); + if (is_layer_settings) + m_config->set_key_value("layer_height", new ConfigOptionFloat(layer_height)); } void ObjectList::del_instances_from_object(const int obj_idx) @@ -1671,6 +1879,8 @@ void ObjectList::del_instances_from_object(const int obj_idx) if (instances.size() <= 1) return; + take_snapshot(_(L("Delete All Instances from Object"))); + while ( instances.size()> 1) instances.pop_back(); @@ -1679,6 +1889,26 @@ void ObjectList::del_instances_from_object(const int obj_idx) changed_object(obj_idx); } +void ObjectList::del_layer_from_object(const int obj_idx, const t_layer_height_range& layer_range) +{ + const auto del_range = object(obj_idx)->layer_config_ranges.find(layer_range); + if (del_range == object(obj_idx)->layer_config_ranges.end()) + return; + + take_snapshot(_(L("Delete Layers Range"))); + + object(obj_idx)->layer_config_ranges.erase(del_range); + + changed_object(obj_idx); +} + +void ObjectList::del_layers_from_object(const int obj_idx) +{ + object(obj_idx)->layer_config_ranges.clear(); + + changed_object(obj_idx); +} + bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, const int type) { if (obj_idx == 1000) @@ -1696,10 +1926,12 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con if (vol->is_model_part()) ++solid_cnt; if (volume->is_model_part() && solid_cnt == 1) { - Slic3r::GUI::show_error(nullptr, _(L("You can't delete the last solid part from object."))); + Slic3r::GUI::show_error(nullptr, _(L("From Object List You can't delete the last solid part from object."))); return false; } + take_snapshot(_(L("Delete Subobject"))); + object->delete_volume(idx); if (object->volumes.size() == 1) @@ -1713,9 +1945,11 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con } else if (type == itInstance) { if (object->instances.size() == 1) { - Slic3r::GUI::show_error(nullptr, _(L("You can't delete the last intance from object."))); + Slic3r::GUI::show_error(nullptr, _(L("Last instance of an object cannot be deleted."))); return false; } + + take_snapshot(_(L("Delete Instance"))); object->delete_instance(idx); } else @@ -1738,11 +1972,15 @@ void ObjectList::split() DynamicPrintConfig& config = printer_config(); const ConfigOption *nozzle_dmtrs_opt = config.option("nozzle_diameter", false); const auto nozzle_dmrs_cnt = (nozzle_dmtrs_opt == nullptr) ? size_t(1) : dynamic_cast(nozzle_dmtrs_opt)->values.size(); - if (volume->split(nozzle_dmrs_cnt) == 1) { + if (!volume->is_splittable()) { wxMessageBox(_(L("The selected object couldn't be split because it contains only one part."))); return; } + take_snapshot(_(L("Split to Parts"))); + + volume->split(nozzle_dmrs_cnt); + wxBusyCursor wait; auto model_object = (*m_objects)[obj_idx]; @@ -1761,11 +1999,7 @@ void ObjectList::split() volume->config.option("extruder")->value : 0, false); // add settings to the part, if it has those - auto opt_keys = volume->config.keys(); - if ( !(opt_keys.size() == 1 && opt_keys[0] == "extruder") ) { - select_item(m_objects_model->AddSettingsChild(vol_item)); - Expand(vol_item); - } + add_settings_item(vol_item, &volume->config); } if (parent == item) @@ -1774,6 +2008,73 @@ void ObjectList::split() changed_object(obj_idx); } +void ObjectList::layers_editing() +{ + const auto item = GetSelection(); + const int obj_idx = get_selected_obj_idx(); + if (!item || obj_idx < 0) + return; + + const wxDataViewItem obj_item = m_objects_model->GetTopParent(item); + wxDataViewItem layers_item = m_objects_model->GetLayerRootItem(obj_item); + + // if it doesn't exist now + if (!layers_item.IsOk()) + { + t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + + // set some default value + if (ranges.empty()) { + take_snapshot(_(L("Add Layers"))); + ranges[{ 0.0f, 2.0f }] = get_default_layer_config(obj_idx); + } + + // create layer root item + layers_item = add_layer_root_item(obj_item); + } + if (!layers_item.IsOk()) + return; + + // to correct visual hints for layers editing on the Scene, reset previous selection + wxGetApp().obj_layers()->reset_selection(); + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); + + // select LayerRoor item and expand + select_item(layers_item); + Expand(layers_item); +} + +wxDataViewItem ObjectList::add_layer_root_item(const wxDataViewItem obj_item) +{ + const int obj_idx = m_objects_model->GetIdByItem(obj_item); + if (obj_idx < 0 || + object(obj_idx)->layer_config_ranges.empty() || + printer_technology() == ptSLA) + return wxDataViewItem(0); + + // create LayerRoot item + wxDataViewItem layers_item = m_objects_model->AddLayersRoot(obj_item); + + // and create Layer item(s) according to the layer_config_ranges + for (const auto range : object(obj_idx)->layer_config_ranges) + add_layer_item(range.first, layers_item); + + Expand(layers_item); + return layers_item; +} + +DynamicPrintConfig ObjectList::get_default_layer_config(const int obj_idx) +{ + DynamicPrintConfig config; + coordf_t layer_height = object(obj_idx)->config.has("layer_height") ? + object(obj_idx)->config.opt_float("layer_height") : + wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_float("layer_height"); + config.set_key_value("layer_height",new ConfigOptionFloat(layer_height)); + config.set_key_value("extruder", new ConfigOptionInt(0)); + + return config; +} + bool ObjectList::get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume) { auto obj_idx = get_selected_obj_idx(); @@ -1843,6 +2144,7 @@ void ObjectList::part_selection_changed() bool update_and_show_manipulations = false; bool update_and_show_settings = false; + bool update_and_show_layers = false; const auto item = GetSelection(); @@ -1865,36 +2167,47 @@ void ObjectList::part_selection_changed() update_and_show_manipulations = true; } else { - auto parent = m_objects_model->GetParent(item); - // Take ID of the parent object to "inform" perl-side which object have to be selected on the scene - obj_idx = m_objects_model->GetIdByItem(parent); - if (m_objects_model->GetItemType(item) == itSettings) { - if (m_objects_model->GetParent(parent) == wxDataViewItem(0)) { + obj_idx = m_objects_model->GetObjectIdByItem(item); + + const ItemType type = m_objects_model->GetItemType(item); + if (type & itSettings) { + const auto parent = m_objects_model->GetParent(item); + const ItemType parent_type = m_objects_model->GetItemType(parent); + + if (parent_type & itObject) { og_name = _(L("Object Settings to modify")); m_config = &(*m_objects)[obj_idx]->config; } - else { + else if (parent_type & itVolume) { og_name = _(L("Part Settings to modify")); - auto main_parent = m_objects_model->GetParent(parent); - obj_idx = m_objects_model->GetIdByItem(main_parent); - const auto volume_id = m_objects_model->GetVolumeIdByItem(parent); + volume_id = m_objects_model->GetVolumeIdByItem(parent); m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; } + else if (parent_type & itLayer) { + og_name = _(L("Layer range Settings to modify")); + m_config = &get_item_config(parent); + } update_and_show_settings = true; } - else if (m_objects_model->GetItemType(item) == itVolume) { + else if (type & itVolume) { og_name = _(L("Part manipulation")); volume_id = m_objects_model->GetVolumeIdByItem(item); m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; update_and_show_manipulations = true; } - else if (m_objects_model->GetItemType(item) == itInstance) { + else if (type & itInstance) { og_name = _(L("Instance manipulation")); update_and_show_manipulations = true; // fill m_config by object's values - const int obj_idx_ = m_objects_model->GetObjectIdByItem(item); - m_config = &(*m_objects)[obj_idx_]->config; + m_config = &(*m_objects)[obj_idx]->config; + } + else if (type & (itLayerRoot|itLayer)) { + og_name = type & itLayerRoot ? _(L("Layers Editing")) : _(L("Layer Editing")); + update_and_show_layers = true; + + if (type & itLayer) + m_config = &get_item_config(item); } } } @@ -1914,18 +2227,87 @@ void ObjectList::part_selection_changed() if (update_and_show_settings) wxGetApp().obj_settings()->get_og()->set_name(" " + og_name + " "); + if (printer_technology() == ptSLA) + update_and_show_layers = false; + else if (update_and_show_layers) + wxGetApp().obj_layers()->get_og()->set_name(" " + og_name + " "); + Sidebar& panel = wxGetApp().sidebar(); panel.Freeze(); + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); wxGetApp().obj_manipul() ->UpdateAndShow(update_and_show_manipulations); wxGetApp().obj_settings()->UpdateAndShow(update_and_show_settings); + wxGetApp().obj_layers() ->UpdateAndShow(update_and_show_layers); wxGetApp().sidebar().show_info_sizer(); panel.Layout(); panel.Thaw(); } -void ObjectList::add_object_to_list(size_t obj_idx) +SettingsBundle ObjectList::get_item_settings_bundle(const DynamicPrintConfig* config, const bool is_object_settings) +{ + auto opt_keys = config->keys(); + if (opt_keys.empty()) + return SettingsBundle(); + + update_opt_keys(opt_keys, is_object_settings); // update options list according to print technology + + if (opt_keys.empty()) + return SettingsBundle(); + + const int extruders_cnt = wxGetApp().extruders_edited_cnt(); + + SettingsBundle bundle; + for (auto& opt_key : opt_keys) + { + auto category = config->def()->get(opt_key)->category; + if (improper_category(category, extruders_cnt)) + continue; + + std::vector< std::string > new_category; + + auto& cat_opt = bundle.find(category) == bundle.end() ? new_category : bundle.at(category); + cat_opt.push_back(opt_key); + if (cat_opt.size() == 1) + bundle[category] = cat_opt; + } + + return bundle; +} + +// Add new SettingsItem for parent_item if it doesn't exist, or just update a digest according to new config +wxDataViewItem ObjectList::add_settings_item(wxDataViewItem parent_item, const DynamicPrintConfig* config) +{ + wxDataViewItem ret = wxDataViewItem(0); + + if (!parent_item) + return ret; + + const bool is_object_settings = m_objects_model->GetItemType(parent_item) == itObject; + SettingsBundle cat_options = get_item_settings_bundle(config, is_object_settings); + if (cat_options.empty()) + return ret; + + std::vector categories; + categories.reserve(cat_options.size()); + for (auto& cat : cat_options) + categories.push_back(cat.first); + + if (m_objects_model->GetItemType(parent_item) & itInstance) + parent_item = m_objects_model->GetTopParent(parent_item); + + ret = m_objects_model->IsSettingsItem(parent_item) ? parent_item : m_objects_model->GetSettingsItem(parent_item); + + if (!ret) ret = m_objects_model->AddSettingsChild(parent_item); + + m_objects_model->UpdateSettingsDigest(ret, categories); + Expand(parent_item); + + return ret; +} + +void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) { auto model_object = (*m_objects)[obj_idx]; const wxString& item_name = from_u8(model_object->name); @@ -1944,28 +2326,34 @@ void ObjectList::add_object_to_list(size_t obj_idx) !volume->config.has("extruder") ? 0 : volume->config.option("extruder")->value, false); - auto opt_keys = volume->config.keys(); - if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) { - select_item(m_objects_model->AddSettingsChild(vol_item)); - Expand(vol_item); - } + add_settings_item(vol_item, &volume->config); } Expand(item); } // add instances to the object, if it has those if (model_object->instances.size()>1) - increase_object_instances(obj_idx, model_object->instances.size()); + { + std::vector print_idicator(model_object->instances.size()); + for (int i = 0; i < model_object->instances.size(); ++i) + print_idicator[i] = model_object->instances[i]->printable; + + const wxDataViewItem object_item = m_objects_model->GetItemById(obj_idx); + m_objects_model->AddInstanceChild(object_item, print_idicator); + Expand(m_objects_model->GetInstanceRootItem(object_item)); + } + else + m_objects_model->SetPrintableState(model_object->instances[0]->printable ? piPrintable : piUnprintable, obj_idx); // add settings to the object, if it has those - auto opt_keys = model_object->config.keys(); - if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) { - select_item(m_objects_model->AddSettingsChild(item)); - Expand(item); - } + add_settings_item(item, &model_object->config); + + // Add layers if it has + add_layer_root_item(item); #ifndef __WXOSX__ - selection_changed(); + if (call_selection_changed) + selection_changed(); #endif //__WXMSW__ } @@ -2000,6 +2388,8 @@ void ObjectList::delete_from_model_and_list(const ItemType type, const int obj_i if ( !(type&(itObject|itVolume|itInstance)) ) return; + take_snapshot(_(L("Delete Selected Item"))); + if (type&itObject) { del_object(obj_idx); delete_object_from_list(obj_idx); @@ -2035,7 +2425,7 @@ void ObjectList::delete_from_model_and_list(const std::vector& it (*m_objects)[item->obj_idx]->config.has("extruder")) { const wxString extruder = wxString::Format("%d", (*m_objects)[item->obj_idx]->config.option("extruder")->value); - m_objects_model->SetValue(extruder, m_objects_model->GetItemById(item->obj_idx), 1); + m_objects_model->SetValue(extruder, m_objects_model->GetItemById(item->obj_idx), colExtruder); } wxGetApp().plater()->canvas3D()->ensure_on_bed(item->obj_idx); } @@ -2105,24 +2495,229 @@ void ObjectList::remove() if (GetSelectedItemsCount() == 0) return; + auto delete_item = [this](wxDataViewItem item) + { + wxDataViewItem parent = m_objects_model->GetParent(item); + if (m_objects_model->GetItemType(item) & itObject) + delete_from_model_and_list(itObject, m_objects_model->GetIdByItem(item), -1); + else { + if (m_objects_model->GetItemType(item) & itLayer) { + wxDataViewItemArray children; + if (m_objects_model->GetChildren(parent, children) == 1) + parent = m_objects_model->GetTopParent(item); + } + + del_subobject_item(item); + } + + return parent; + }; + wxDataViewItemArray sels; GetSelections(sels); - for (auto& item : sels) + wxDataViewItem parent = wxDataViewItem(0); + + if (sels.Count() == 1) + parent = delete_item(GetSelection()); + else { - if (m_objects_model->GetParent(item) == wxDataViewItem(0)) - delete_from_model_and_list(itObject, m_objects_model->GetIdByItem(item), -1); - else { - if (sels.size() == 1) - select_item(m_objects_model->GetParent(item)); - del_subobject_item(item); + Plater::TakeSnapshot snapshot = Plater::TakeSnapshot(wxGetApp().plater(), _(L("Delete Selected"))); + + for (auto& item : sels) + { + if (m_objects_model->InvalidItem(item)) // item can be deleted for this moment (like last 2 Instances or Volumes) + continue; + parent = delete_item(item); } } + + if (parent && !m_objects_model->InvalidItem(parent)) { + select_item(parent); + update_selections_on_canvas(); + } +} + +void ObjectList::del_layer_range(const t_layer_height_range& range) +{ + const int obj_idx = get_selected_obj_idx(); + if (obj_idx < 0) return; + + t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + + wxDataViewItem selectable_item = GetSelection(); + + if (ranges.size() == 1) + selectable_item = m_objects_model->GetParent(selectable_item); + + wxDataViewItem layer_item = m_objects_model->GetItemByLayerRange(obj_idx, range); + del_subobject_item(layer_item); + + select_item(selectable_item); +} + +static double get_min_layer_height(const int extruder_idx) +{ + const DynamicPrintConfig& config = wxGetApp().preset_bundle->printers.get_edited_preset().config; + return config.opt_float("min_layer_height", extruder_idx <= 0 ? 0 : extruder_idx-1); +} + +static double get_max_layer_height(const int extruder_idx) +{ + const DynamicPrintConfig& config = wxGetApp().preset_bundle->printers.get_edited_preset().config; + return config.opt_float("max_layer_height", extruder_idx <= 0 ? 0 : extruder_idx-1); +} + +void ObjectList::add_layer_range_after_current(const t_layer_height_range& current_range) +{ + const int obj_idx = get_selected_obj_idx(); + if (obj_idx < 0) return; + + const wxDataViewItem layers_item = GetSelection(); + + t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + + const t_layer_height_range& last_range = (--ranges.end())->first; + + if (current_range == last_range) + { + take_snapshot(_(L("Add New Layers Range"))); + + const t_layer_height_range& new_range = { last_range.second, last_range.second + 2.0f }; + ranges[new_range] = get_default_layer_config(obj_idx); + add_layer_item(new_range, layers_item); + } + else + { + const t_layer_height_range& next_range = (++ranges.find(current_range))->first; + + if (current_range.second > next_range.first) + return; // range division has no sense + + const int layer_idx = m_objects_model->GetItemIdByLayerRange(obj_idx, next_range); + if (layer_idx < 0) + return; + + if (current_range.second == next_range.first) + { + const auto old_config = ranges.at(next_range); + + const coordf_t delta = (next_range.second - next_range.first); + if (delta < get_min_layer_height(old_config.opt_int("extruder"))/*0.05f*/) // next range division has no sense + return; + + const coordf_t midl_layer = next_range.first + 0.5f * delta; + + t_layer_height_range new_range = { midl_layer, next_range.second }; + + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add New Layers Range"))); + + // create new 2 layers instead of deleted one + + // delete old layer + + wxDataViewItem layer_item = m_objects_model->GetItemByLayerRange(obj_idx, next_range); + del_subobject_item(layer_item); + + ranges[new_range] = old_config; + add_layer_item(new_range, layers_item, layer_idx); + + new_range = { current_range.second, midl_layer }; + ranges[new_range] = get_default_layer_config(obj_idx); + add_layer_item(new_range, layers_item, layer_idx); + } + else + { + take_snapshot(_(L("Add New Layers Range"))); + + const t_layer_height_range new_range = { current_range.second, next_range.first }; + ranges[new_range] = get_default_layer_config(obj_idx); + add_layer_item(new_range, layers_item, layer_idx); + } + } + + changed_object(obj_idx); + + // select item to update layers sizer + select_item(layers_item); +} + +void ObjectList::add_layer_item(const t_layer_height_range& range, + const wxDataViewItem layers_item, + const int layer_idx /* = -1*/) +{ + const int obj_idx = m_objects_model->GetObjectIdByItem(layers_item); + if (obj_idx < 0) return; + + const DynamicPrintConfig& config = object(obj_idx)->layer_config_ranges[range]; + if (!config.has("extruder")) + return; + + const auto layer_item = m_objects_model->AddLayersChild(layers_item, + range, + config.opt_int("extruder"), + layer_idx); + add_settings_item(layer_item, &config); +} + +bool ObjectList::edit_layer_range(const t_layer_height_range& range, coordf_t layer_height) +{ + const int obj_idx = get_selected_obj_idx(); + if (obj_idx < 0) + return false; + + DynamicPrintConfig* config = &object(obj_idx)->layer_config_ranges[range]; + if (fabs(layer_height - config->opt_float("layer_height")) < EPSILON) + return false; + + const int extruder_idx = config->opt_int("extruder"); + + if (layer_height >= get_min_layer_height(extruder_idx) && + layer_height <= get_max_layer_height(extruder_idx)) + { + config->set_key_value("layer_height", new ConfigOptionFloat(layer_height)); + return true; + } + + return false; +} + +bool ObjectList::edit_layer_range(const t_layer_height_range& range, const t_layer_height_range& new_range) +{ + const int obj_idx = get_selected_obj_idx(); + if (obj_idx < 0) return false; + + take_snapshot(_(L("Edit Layers Range"))); + + const ItemType sel_type = m_objects_model->GetItemType(GetSelection()); + + t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + + const DynamicPrintConfig config = ranges[range]; + + ranges.erase(range); + ranges[new_range] = config; + + wxDataViewItem root_item = m_objects_model->GetLayerRootItem(m_objects_model->GetItemById(obj_idx)); + // To avoid update selection after deleting of a selected item (under GTK) + // set m_prevent_list_events to true + m_prevent_list_events = true; + m_objects_model->DeleteChildren(root_item); + + if (root_item.IsOk()) + // create Layer item(s) according to the layer_config_ranges + for (const auto r : ranges) + add_layer_item(r.first, root_item); + + select_item(sel_type&itLayer ? m_objects_model->GetItemByLayerRange(obj_idx, new_range) : root_item); + Expand(root_item); + + return true; } void ObjectList::init_objects() { - m_objects = wxGetApp().model_objects(); + m_objects = &wxGetApp().model().objects; } bool ObjectList::multiple_selection() const @@ -2139,20 +2734,37 @@ bool ObjectList::is_selected(const ItemType type) const return false; } +int ObjectList::get_selected_layers_range_idx() const +{ + const wxDataViewItem& item = GetSelection(); + if (!item) + return -1; + + const ItemType type = m_objects_model->GetItemType(item); + if (type & itSettings && m_objects_model->GetItemType(m_objects_model->GetParent(item)) != itLayer) + return -1; + + return m_objects_model->GetLayerIdByItem(type & itLayer ? item : m_objects_model->GetParent(item)); +} + void ObjectList::update_selections() { const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); wxDataViewItemArray sels; - m_selection_mode = smInstance; + if ( ( m_selection_mode & (smSettings|smLayer|smLayerRoot) ) == 0) + m_selection_mode = smInstance; // We doesn't update selection if SettingsItem for the current object/part is selected - if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) == itSettings ) +// if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) == itSettings ) + if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) & (itSettings | itLayerRoot | itLayer)) { const auto item = GetSelection(); - if (selection.is_single_full_object() && - m_objects_model->GetIdByItem(m_objects_model->GetParent(item)) == selection.get_object_idx()) - return; + if (selection.is_single_full_object()) { + if (m_objects_model->GetObjectIdByItem(item) == selection.get_object_idx()) + return; + sels.Add(m_objects_model->GetItemById(selection.get_object_idx())); + } if (selection.is_single_volume() || selection.is_any_modifier()) { const auto gl_vol = selection.get_volume(*selection.get_volume_idxs().begin()); if (m_objects_model->GetVolumeIdByItem(m_objects_model->GetParent(item)) == gl_vol->volume_idx()) @@ -2167,6 +2779,27 @@ void ObjectList::update_selections() else if (selection.is_single_full_object() || selection.is_multiple_full_object()) { const Selection::ObjectIdxsToInstanceIdxsMap& objects_content = selection.get_content(); + if (m_selection_mode & (smSettings | smLayer | smLayerRoot)) + { + auto obj_idx = objects_content.begin()->first; + wxDataViewItem obj_item = m_objects_model->GetItemById(obj_idx); + if (m_selection_mode & smSettings) + { + if (m_selected_layers_range_idx < 0) + sels.Add(m_objects_model->GetSettingsItem(obj_item)); + else + sels.Add(m_objects_model->GetSettingsItem(m_objects_model->GetItemByLayerId(obj_idx, m_selected_layers_range_idx))); + } + else if (m_selection_mode & smLayerRoot) + sels.Add(m_objects_model->GetLayerRootItem(obj_item)); + else if (m_selection_mode & smLayer) { + if (m_selected_layers_range_idx >= 0) + sels.Add(m_objects_model->GetItemByLayerId(obj_idx, m_selected_layers_range_idx)); + else + sels.Add(obj_item); + } + } + else { for (const auto& object : objects_content) { if (object.second.size() == 1) // object with 1 instance sels.Add(m_objects_model->GetItemById(object.first)); @@ -2191,10 +2824,23 @@ void ObjectList::update_selections() for (const auto& inst : instances) sels.Add(m_objects_model->GetItemByInstanceId(object.first, inst)); } - } + } } } else if (selection.is_any_volume() || selection.is_any_modifier()) { + if (m_selection_mode & smSettings) + { + const auto idx = *selection.get_volume_idxs().begin(); + const auto gl_vol = selection.get_volume(idx); + if (gl_vol->volume_idx() >= 0) { + // Only add GLVolumes with non-negative volume_ids. GLVolumes with negative volume ids + // are not associated with ModelVolumes, but they are temporarily generated by the backend + // (for example, SLA supports or SLA pad). + wxDataViewItem vol_item = m_objects_model->GetItemByVolumeId(gl_vol->object_idx(), gl_vol->volume_idx()); + sels.Add(m_objects_model->GetSettingsItem(vol_item)); + } + } + else { for (auto idx : selection.get_volume_idxs()) { const auto gl_vol = selection.get_volume(idx); if (gl_vol->volume_idx() >= 0) @@ -2203,7 +2849,7 @@ void ObjectList::update_selections() // (for example, SLA supports or SLA pad). sels.Add(m_objects_model->GetItemByVolumeId(gl_vol->object_idx(), gl_vol->volume_idx())); } - m_selection_mode = smVolume; + m_selection_mode = smVolume; } } else if (selection.is_single_full_instance() || selection.is_multiple_full_instance()) { @@ -2247,7 +2893,7 @@ void ObjectList::update_selections() } } - if (sels.size() == 0) + if (sels.size() == 0 || m_selection_mode & smSettings) m_selection_mode = smUndef; select_items(sels); @@ -2262,29 +2908,36 @@ void ObjectList::update_selections_on_canvas() const int sel_cnt = GetSelectedItemsCount(); if (sel_cnt == 0) { - selection.clear(); + selection.remove_all(); wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state(); return; } - auto add_to_selection = [this](const wxDataViewItem& item, Selection& selection, int instance_idx, bool as_single_selection) + std::vector volume_idxs; + Selection::EMode mode = Selection::Volume; + bool single_selection = sel_cnt == 1; + auto add_to_selection = [this, &volume_idxs, &single_selection](const wxDataViewItem& item, const Selection& selection, int instance_idx, Selection::EMode& mode) { const ItemType& type = m_objects_model->GetItemType(item); - if ( type == itInstanceRoot || m_objects_model->GetParent(item) == wxDataViewItem(0) ) { - wxDataViewItem obj_item = type == itInstanceRoot ? m_objects_model->GetParent(item) : item; - selection.add_object(m_objects_model->GetIdByItem(obj_item), as_single_selection); - return; - } + const int obj_idx = m_objects_model->GetObjectIdByItem(item); if (type == itVolume) { - const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetParent(item)); const int vol_idx = m_objects_model->GetVolumeIdByItem(item); - selection.add_volume(obj_idx, vol_idx, std::max(instance_idx, 0), as_single_selection); + std::vector idxs = selection.get_volume_idxs_from_volume(obj_idx, std::max(instance_idx, 0), vol_idx); + volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end()); } else if (type == itInstance) { - const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); const int inst_idx = m_objects_model->GetInstanceIdByItem(item); - selection.add_instance(obj_idx, inst_idx, as_single_selection); + mode = Selection::Instance; + std::vector idxs = selection.get_volume_idxs_from_instance(obj_idx, inst_idx); + volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end()); + } + else + { + mode = Selection::Instance; + single_selection &= (obj_idx != selection.get_object_idx()); + std::vector idxs = selection.get_volume_idxs_from_object(obj_idx); + volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end()); } }; @@ -2293,22 +2946,42 @@ void ObjectList::update_selections_on_canvas() if (sel_cnt == 1) { wxDataViewItem item = GetSelection(); - if (m_objects_model->GetItemType(item) & (itSettings|itInstanceRoot)) - add_to_selection(m_objects_model->GetParent(item), selection, instance_idx, true); + if (m_objects_model->GetItemType(item) & (itSettings | itInstanceRoot | itLayerRoot | itLayer)) + add_to_selection(m_objects_model->GetParent(item), selection, instance_idx, mode); else - add_to_selection(item, selection, instance_idx, true); - - wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state(); - wxGetApp().plater()->canvas3D()->render(); - return; + add_to_selection(item, selection, instance_idx, mode); } - - wxDataViewItemArray sels; - GetSelections(sels); + else + { + wxDataViewItemArray sels; + GetSelections(sels); - selection.clear(); - for (auto item: sels) - add_to_selection(item, selection, instance_idx, false); + // clear selection before adding new elements + selection.clear(); //OR remove_all()? + + for (auto item : sels) + { + add_to_selection(item, selection, instance_idx, mode); + } + } + + if (selection.contains_all_volumes(volume_idxs)) + { + // remove + volume_idxs = selection.get_missing_volume_idxs_from(volume_idxs); + if (volume_idxs.size() > 0) + { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Selection-Remove from list"))); + selection.remove_volumes(mode, volume_idxs); + } + } + else + { + // add + volume_idxs = selection.get_unselected_volume_idxs_from(volume_idxs); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Selection-Add from list"))); + selection.add_volumes(mode, volume_idxs, single_selection); + } wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state(); wxGetApp().plater()->canvas3D()->render(); @@ -2356,11 +3029,13 @@ void ObjectList::select_item_all_children() } else { const auto item = GetSelection(); - // Some volume(instance) is selected => select all volumes(instances) inside the current object - if (m_objects_model->GetItemType(item) & (itVolume | itInstance)) + const ItemType item_type = m_objects_model->GetItemType(item); + // Some volume/layer/instance is selected => select all volumes/layers/instances inside the current object + if (item_type & (itVolume | itInstance | itLayer)) m_objects_model->GetChildren(m_objects_model->GetParent(item), sels); - m_selection_mode = m_objects_model->GetItemType(item)&itVolume ? smVolume : smInstance; + m_selection_mode = item_type&itVolume ? smVolume : + item_type&itLayer ? smLayer : smInstance; } SetSelections(sels); @@ -2370,6 +3045,7 @@ void ObjectList::select_item_all_children() // update selection mode for non-multiple selection void ObjectList::update_selection_mode() { + m_selected_layers_range_idx=-1; // All items are unselected if (!GetSelection()) { @@ -2379,8 +3055,9 @@ void ObjectList::update_selection_mode() } const ItemType type = m_objects_model->GetItemType(GetSelection()); - m_selection_mode = type&itSettings ? smUndef : - type&itVolume ? smVolume : smInstance; + m_selection_mode = type & itSettings ? smUndef : + type & itLayer ? smLayer : + type & itVolume ? smVolume : smInstance; } // check last selected item. If is it possible to select it @@ -2391,33 +3068,37 @@ bool ObjectList::check_last_selection(wxString& msg_str) const bool is_shift_pressed = wxGetKeyState(WXK_SHIFT); - /* We can't mix Parts and Objects/Instances. + /* We can't mix Volumes, Layers and Objects/Instances. * So, show information about it */ const ItemType type = m_objects_model->GetItemType(m_last_selected_item); - // check a case of a selection of the Parts from different Objects - bool impossible_multipart_selection = false; - if (type & itVolume && m_selection_mode == smVolume) - { + // check a case of a selection of the same type items from different Objects + auto impossible_multi_selection = [type, this](const ItemType item_type, const SELECTION_MODE selection_mode) { + if (!(type & item_type && m_selection_mode & selection_mode)) + return false; + wxDataViewItemArray sels; GetSelections(sels); - for (const auto& sel: sels) - if (sel != m_last_selected_item && - m_objects_model->GetParent(sel) != m_objects_model->GetParent(m_last_selected_item)) - { - impossible_multipart_selection = true; - break; - } - } + for (const auto& sel : sels) + if (sel != m_last_selected_item && + m_objects_model->GetTopParent(sel) != m_objects_model->GetTopParent(m_last_selected_item)) + return true; - if (impossible_multipart_selection || + return false; + }; + + if (impossible_multi_selection(itVolume, smVolume) || + impossible_multi_selection(itLayer, smLayer ) || type & itSettings || - type & itVolume && m_selection_mode == smInstance || - !(type & itVolume) && m_selection_mode == smVolume) + type & itVolume && !(m_selection_mode & smVolume ) || + type & itLayer && !(m_selection_mode & smLayer ) || + type & itInstance && !(m_selection_mode & smInstance) + ) { // Inform user why selection isn't complited - const wxString item_type = m_selection_mode == smInstance ? _(L("Object or Instance")) : _(L("Part")); + const wxString item_type = m_selection_mode & smInstance ? _(L("Object or Instance")) : + m_selection_mode & smVolume ? _(L("Part")) : _(L("Layer")); msg_str = wxString::Format( _(L("Unsupported selection")) + "\n\n" + _(L("You started your selection with %s Item.")) + "\n" + @@ -2454,7 +3135,7 @@ void ObjectList::fix_multiselection_conflicts() wxDataViewItemArray sels; GetSelections(sels); - if (m_selection_mode == smVolume) + if (m_selection_mode & (smVolume|smLayer)) { // identify correct parent of the initial selected item const wxDataViewItem& parent = m_objects_model->GetParent(m_last_selected_item == sels.front() ? sels.back() : sels.front()); @@ -2463,8 +3144,10 @@ void ObjectList::fix_multiselection_conflicts() wxDataViewItemArray children; // selected volumes from current parent m_objects_model->GetChildren(parent, children); + const ItemType item_type = m_selection_mode & smVolume ? itVolume : itLayer; + for (const auto child : children) - if (IsSelected(child) && m_objects_model->GetItemType(child)&itVolume) + if (IsSelected(child) && m_objects_model->GetItemType(child) & item_type) sels.Add(child); // If some part is selected, unselect all items except of selected parts of the current object @@ -2555,6 +3238,8 @@ void ObjectList::change_part_type() if (new_type == type || new_type == ModelVolumeType::INVALID) return; + take_snapshot(_(L("Change Part Type"))); + const auto item = GetSelection(); volume->set_type(new_type); m_objects_model->SetVolumeType(item, new_type); @@ -2570,18 +3255,17 @@ void ObjectList::change_part_type() } else if (!settings_item && (new_type == ModelVolumeType::MODEL_PART || new_type == ModelVolumeType::PARAMETER_MODIFIER)) { - select_item(m_objects_model->AddSettingsChild(item)); + add_settings_item(item, &volume->config); } } void ObjectList::last_volume_is_deleted(const int obj_idx) { - if (obj_idx < 0 || m_objects->empty() || - obj_idx <= m_objects->size() || - (*m_objects)[obj_idx]->volumes.empty()) + if (obj_idx < 0 || obj_idx >= m_objects->size() || (*m_objects)[obj_idx]->volumes.size() != 1) return; - auto volume = (*m_objects)[obj_idx]->volumes[0]; + + auto volume = (*m_objects)[obj_idx]->volumes.front(); // clear volume's config values volume->config.clear(); @@ -2590,37 +3274,109 @@ void ObjectList::last_volume_is_deleted(const int obj_idx) volume->config.set_key_value("extruder", new ConfigOptionInt(0)); } -bool ObjectList::has_multi_part_objects() +void ObjectList::update_and_show_object_settings_item() { - if (!m_objects_model->IsEmpty()) { - wxDataViewItemArray items; - m_objects_model->GetChildren(wxDataViewItem(0), items); + const wxDataViewItem item = GetSelection(); + if (!item) return; - for (auto& item : items) - if (m_objects_model->GetItemByType(item, itVolume)) - return true; - } - return false; + const wxDataViewItem& obj_item = m_objects_model->IsSettingsItem(item) ? m_objects_model->GetParent(item) : item; + select_item(add_settings_item(obj_item, &get_item_config(obj_item))); } -void ObjectList::update_settings_items() +// Update settings item for item had it +void ObjectList::update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections) +{ + const wxDataViewItem old_settings_item = m_objects_model->GetSettingsItem(item); + const wxDataViewItem new_settings_item = add_settings_item(item, &get_item_config(item)); + + if (!new_settings_item && old_settings_item) + m_objects_model->Delete(old_settings_item); + + // if ols settings item was is selected area + if (selections.Index(old_settings_item) != wxNOT_FOUND) + { + // If settings item was just updated + if (old_settings_item == new_settings_item) + { + Sidebar& panel = wxGetApp().sidebar(); + panel.Freeze(); + + // update settings list + wxGetApp().obj_settings()->UpdateAndShow(true); + + panel.Layout(); + panel.Thaw(); + } + else + // If settings item was deleted from the list, + // it's need to be deleted from selection array, if it was there + { + selections.Remove(old_settings_item); + + // Select item, if settings_item doesn't exist for item anymore, but was selected + if (selections.Index(item) == wxNOT_FOUND) { + selections.Add(item); + select_item(item); // to correct update of the SettingsList and ManipulationPanel sizers + } + } + } +} + +void ObjectList::update_object_list_by_printer_technology() { m_prevent_canvas_selection_update = true; wxDataViewItemArray sel; GetSelections(sel); // stash selection - wxDataViewItemArray items; - m_objects_model->GetChildren(wxDataViewItem(0), items); + wxDataViewItemArray object_items; + m_objects_model->GetChildren(wxDataViewItem(0), object_items); - for (auto& item : items) { - const wxDataViewItem& settings_item = m_objects_model->GetSettingsItem(item); - select_item(settings_item ? settings_item : m_objects_model->AddSettingsChild(item)); + for (auto& object_item : object_items) { + // Update Settings Item for object + update_settings_item_and_selection(object_item, sel); - // If settings item was deleted from the list, - // it's need to be deleted from selection array, if it was there - if (settings_item != m_objects_model->GetSettingsItem(item) && - sel.Index(settings_item) != wxNOT_FOUND) { - sel.Remove(settings_item); + // Update settings for Volumes + wxDataViewItemArray all_object_subitems; + m_objects_model->GetChildren(object_item, all_object_subitems); + for (auto item : all_object_subitems) + if (m_objects_model->GetItemType(item) & itVolume) + // update settings for volume + update_settings_item_and_selection(item, sel); + + // Update Layers Items + wxDataViewItem layers_item = m_objects_model->GetLayerRootItem(object_item); + if (!layers_item) + layers_item = add_layer_root_item(object_item); + else if (printer_technology() == ptSLA) { + // If layers root item will be deleted from the list, so + // it's need to be deleted from selection array, if it was there + wxDataViewItemArray del_items; + bool some_layers_was_selected = false; + m_objects_model->GetAllChildren(layers_item, del_items); + for (auto& del_item:del_items) + if (sel.Index(del_item) != wxNOT_FOUND) { + some_layers_was_selected = true; + sel.Remove(del_item); + } + if (sel.Index(layers_item) != wxNOT_FOUND) { + some_layers_was_selected = true; + sel.Remove(layers_item); + } + + // delete all "layers" items + m_objects_model->Delete(layers_item); + + // Select object_item, if layers_item doesn't exist for item anymore, but was some of layer items was/were selected + if (some_layers_was_selected) + sel.Add(object_item); + } + else { + wxDataViewItemArray all_obj_layers; + m_objects_model->GetChildren(layers_item, all_obj_layers); + + for (auto item : all_obj_layers) + // update settings for layer + update_settings_item_and_selection(item, sel); } } @@ -2652,7 +3408,8 @@ void ObjectList::instances_to_separated_object(const int obj_idx, const std::set } // Add new object to the object_list - add_object_to_list(m_objects->size() - 1); + const size_t new_obj_indx = static_cast(m_objects->size() - 1); + add_object_to_list(new_obj_indx); for (std::set::const_reverse_iterator it = inst_idxs.rbegin(); it != inst_idxs.rend(); ++it) { @@ -2660,12 +3417,17 @@ void ObjectList::instances_to_separated_object(const int obj_idx, const std::set del_subobject_from_object(obj_idx, *it, itInstance); delete_instance_from_list(obj_idx, *it); } + + // update printable state for new volumes on canvas3D + wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object(new_obj_indx); } void ObjectList::instances_to_separated_objects(const int obj_idx) { const int inst_cnt = (*m_objects)[obj_idx]->instances.size(); + std::vector object_idxs; + for (int i = inst_cnt-1; i > 0 ; i--) { // create new object from initial @@ -2679,12 +3441,17 @@ void ObjectList::instances_to_separated_objects(const int obj_idx) } // Add new object to the object_list - add_object_to_list(m_objects->size() - 1); + const size_t new_obj_indx = static_cast(m_objects->size() - 1); + add_object_to_list(new_obj_indx); + object_idxs.push_back(new_obj_indx); // delete current instance from the initial object del_subobject_from_object(obj_idx, i, itInstance); delete_instance_from_list(obj_idx, i); } + + // update printable state for new volumes on canvas3D + wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_objects(object_idxs); } void ObjectList::split_instances() @@ -2694,6 +3461,8 @@ void ObjectList::split_instances() if (obj_idx == -1) return; + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Instances to Separated Objects"))); + if (selection.is_single_full_object()) { instances_to_separated_objects(obj_idx); @@ -2737,7 +3506,7 @@ void ObjectList::rename_item() // The icon can't be edited so get its old value and reuse it. wxVariant valueOld; - m_objects_model->GetValue(valueOld, item, 0); + m_objects_model->GetValue(valueOld, item, colName); DataViewBitmapText bmpText; bmpText << valueOld; @@ -2747,7 +3516,7 @@ void ObjectList::rename_item() wxVariant value; value << bmpText; - m_objects_model->SetValue(value, item, 0); + m_objects_model->SetValue(value, item, colName); m_objects_model->ItemChanged(item); update_name_in_model(item); @@ -2788,9 +3557,10 @@ void ObjectList::msw_rescale() // update min size !!! A width of control shouldn't be a wxDefaultCoord SetMinSize(wxSize(1, 15 * em)); - GetColumn(0)->SetWidth(19 * em); - GetColumn(1)->SetWidth( 8 * em); - GetColumn(2)->SetWidth( 2 * em); + GetColumn(colName)->SetWidth(19 * em); + GetColumn(colPrint)->SetWidth( 2 * em); + GetColumn(colExtruder)->SetWidth( 8 * em); + GetColumn(colEditing)->SetWidth( 2 * em); // rescale all icons, used by ObjectList msw_rescale_icons(); @@ -2802,7 +3572,8 @@ void ObjectList::msw_rescale() for (MenuWithSeparators* menu : { &m_menu_object, &m_menu_part, &m_menu_sla_object, - &m_menu_instance }) + &m_menu_instance, + &m_menu_layer }) msw_rescale_menu(menu); Layout(); @@ -2810,24 +3581,39 @@ void ObjectList::msw_rescale() void ObjectList::ItemValueChanged(wxDataViewEvent &event) { - if (event.GetColumn() == 0) + if (event.GetColumn() == colName) update_name_in_model(event.GetItem()); - else if (event.GetColumn() == 1) + else if (event.GetColumn() == colExtruder) update_extruder_in_config(event.GetItem()); } +#ifdef __WXMSW__ +// Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected. +// Here the last active column is forgotten, so when leaving the editing mode, the next mouse click will not enter the editing mode of the newly selected column. +void ObjectList::OnEditingStarted(wxDataViewEvent &event) +{ + m_last_selected_column = -1; +} +#endif __WXMSW__ + void ObjectList::OnEditingDone(wxDataViewEvent &event) { - if (event.GetColumn() != 0) + if (event.GetColumn() != colName) return; - const auto renderer = dynamic_cast(GetColumn(0)->GetRenderer()); + const auto renderer = dynamic_cast(GetColumn(colName)->GetRenderer()); if (renderer->WasCanceled()) wxTheApp->CallAfter([this]{ show_error(this, _(L("The supplied name is not valid;")) + "\n" + _(L("the following characters are not allowed:")) + " <>:/\\|?*\""); }); + +#ifdef __WXMSW__ + // Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected. + // Here the last active column is forgotten, so when leaving the editing mode, the next mouse click will not enter the editing mode of the newly selected column. + m_last_selected_column = -1; +#endif __WXMSW__ } void ObjectList::show_multi_selection_menu() @@ -2903,7 +3689,7 @@ void ObjectList::set_extruder_for_selected_items(const int extruder) const /* We can change extruder for Object/Volume only. * So, if Instance is selected, get its Object item and change it */ - m_objects_model->SetValue(extruder_str, type & itInstance ? m_objects_model->GetTopParent(item) : item, 1); + m_objects_model->SetValue(extruder_str, type & itInstance ? m_objects_model->GetTopParent(item) : item, colExtruder); const int obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) : m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); @@ -2915,5 +3701,89 @@ void ObjectList::set_extruder_for_selected_items(const int extruder) const wxGetApp().plater()->update(); } +void ObjectList::update_after_undo_redo() +{ + m_prevent_canvas_selection_update = true; + + Plater::SuppressSnapshots suppress(wxGetApp().plater()); + + // Unselect all objects before deleting them, so that no change of selection is emitted during deletion. + unselect_objects();//this->UnselectAll(); + m_objects_model->DeleteAll(); + + size_t obj_idx = 0; + std::vector obj_idxs; + obj_idxs.reserve(m_objects->size()); + while (obj_idx < m_objects->size()) { + add_object_to_list(obj_idx, false); + obj_idxs.push_back(obj_idx); + ++obj_idx; + } + + update_selections(); + + m_prevent_canvas_selection_update = false; + + // update printable states on canvas + wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_objects(obj_idxs); + // update scene + wxGetApp().plater()->update(); +} + +void ObjectList::update_printable_state(int obj_idx, int instance_idx) +{ + ModelObject* object = (*m_objects)[obj_idx]; + + const PrintIndicator printable = object->instances[instance_idx]->printable ? piPrintable : piUnprintable; + if (object->instances.size() == 1) + instance_idx = -1; + + m_objects_model->SetPrintableState(printable, obj_idx, instance_idx); +} + +void ObjectList::toggle_printable_state(wxDataViewItem item) +{ + const ItemType type = m_objects_model->GetItemType(item); + if (!(type&(itObject|itInstance/*|itVolume*/))) + return; + + if (type & itObject) + { + const int obj_idx = m_objects_model->GetObjectIdByItem(item); + ModelObject* object = (*m_objects)[obj_idx]; + + // get object's printable and change it + const bool printable = !m_objects_model->IsPrintable(item); + + const wxString snapshot_text = wxString::Format("%s %s", + printable ? _(L("Set Printable")) : _(L("Set Unprintable")), + object->name); + take_snapshot(snapshot_text); + + // set printable value for all instances in object + for (auto inst : object->instances) + inst->printable = printable; + + // update printable state on canvas + wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object((size_t)obj_idx); + + // update printable state in ObjectList + m_objects_model->SetObjectPrintableState(printable ? piPrintable : piUnprintable , item); + } + else + wxGetApp().plater()->canvas3D()->get_selection().toggle_instance_printable_state(); + + // update scene + wxGetApp().plater()->update(); +} + +ModelObject* ObjectList::object(const int obj_idx) const +{ + if (obj_idx < 0) + return nullptr; + + return (*m_objects)[obj_idx]; +} + } //namespace GUI -} //namespace Slic3r \ No newline at end of file +} //namespace Slic3r diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 166606e2ee..0164d20c12 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -26,13 +26,17 @@ enum class ModelVolumeType : int; // FIXME: broken build on mac os because of this is missing: typedef std::vector t_config_option_keys; -typedef std::map> FreqSettingsBundle; +typedef std::map> SettingsBundle; // category -> vector ( option ; label ) typedef std::map< std::string, std::vector< std::pair > > settings_menu_hierarchy; typedef std::vector ModelVolumePtrs; +typedef double coordf_t; +typedef std::pair t_layer_height_range; +typedef std::map t_layer_config_ranges; + namespace GUI { wxDECLARE_EVENT(EVT_OBJ_LIST_OBJECT_SELECT, SimpleEvent); @@ -62,12 +66,20 @@ struct ItemForDelete class ObjectList : public wxDataViewCtrl { +public: enum SELECTION_MODE { - smUndef, - smVolume, - smInstance - } m_selection_mode {smUndef}; + smUndef = 0, + smVolume = 1, + smInstance = 2, + smLayer = 4, + smSettings = 8, // used for undo/redo + smLayerRoot = 16, // used for undo/redo + }; + +private: + SELECTION_MODE m_selection_mode {smUndef}; + int m_selected_layers_range_idx; struct dragged_item_data { @@ -119,12 +131,17 @@ class ObjectList : public wxDataViewCtrl MenuWithSeparators m_menu_part; MenuWithSeparators m_menu_sla_object; MenuWithSeparators m_menu_instance; - wxMenuItem* m_menu_item_split { nullptr }; - wxMenuItem* m_menu_item_split_part { nullptr }; + MenuWithSeparators m_menu_layer; wxMenuItem* m_menu_item_settings { nullptr }; wxMenuItem* m_menu_item_split_instances { nullptr }; - std::vector m_bmp_vector; + ObjectDataViewModel *m_objects_model{ nullptr }; + DynamicPrintConfig *m_config {nullptr}; + std::vector *m_objects{ nullptr }; + + std::vector m_bmp_vector; + + t_layer_config_ranges m_layer_config_ranges_cache; int m_selected_object_id = -1; bool m_prevent_list_events = false; // We use this flag to avoid circular event handling Select() @@ -140,10 +157,14 @@ class ObjectList : public wxDataViewCtrl int m_selected_row = 0; wxDataViewItem m_last_selected_item {nullptr}; +#ifdef __WXMSW__ + // Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected. + int m_last_selected_column = -1; +#endif /* __MSW__ */ #if 0 - FreqSettingsBundle m_freq_settings_fff; - FreqSettingsBundle m_freq_settings_sla; + SettingsBundle m_freq_settings_fff; + SettingsBundle m_freq_settings_sla; #endif public: @@ -153,11 +174,11 @@ public: std::map CATEGORY_ICON; - ObjectDataViewModel *m_objects_model{ nullptr }; - DynamicPrintConfig *m_config {nullptr}; - - std::vector *m_objects{ nullptr }; + ObjectDataViewModel* GetModel() const { return m_objects_model; } + DynamicPrintConfig* config() const { return m_config; } + std::vector* objects() const { return m_objects; } + ModelObject* object(const int obj_idx) const ; void create_objects_ctrl(); void create_popup_menus(); @@ -192,16 +213,23 @@ public: void key_event(wxKeyEvent& event); #endif /* __WXOSX__ */ + void copy(); + void paste(); + void undo(); + void redo(); + void get_settings_choice(const wxString& category_name); void get_freq_settings_choice(const wxString& bundle_name); - void update_settings_item(); + void show_settings(const wxDataViewItem settings_item); wxMenu* append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type); void append_menu_items_add_volume(wxMenu* menu); wxMenuItem* append_menu_item_split(wxMenu* menu); + wxMenuItem* append_menu_item_layers_editing(wxMenu* menu); wxMenuItem* append_menu_item_settings(wxMenu* menu); wxMenuItem* append_menu_item_change_type(wxMenu* menu); wxMenuItem* append_menu_item_instance_to_object(wxMenu* menu, wxWindow* parent); + wxMenuItem* append_menu_item_printable(wxMenu* menu, wxWindow* parent); void append_menu_items_osx(wxMenu* menu); wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu); void append_menu_item_export_stl(wxMenu* menu) const ; @@ -215,17 +243,25 @@ public: wxMenu* create_settings_popupmenu(wxMenu *parent_menu); void create_freq_settings_popupmenu(wxMenu *parent_menu); - void update_opt_keys(t_config_option_keys& t_optopt_keys); + void update_opt_keys(t_config_option_keys& t_optopt_keys, const bool is_object); void load_subobject(ModelVolumeType type); void load_part(ModelObject* model_object, std::vector> &volumes_info, ModelVolumeType type); void load_generic_subobject(const std::string& type_name, const ModelVolumeType type); void del_object(const int obj_idx); void del_subobject_item(wxDataViewItem& item); - void del_settings_from_config(); + void del_settings_from_config(const wxDataViewItem& parent_item); void del_instances_from_object(const int obj_idx); + void del_layer_from_object(const int obj_idx, const t_layer_height_range& layer_range); + void del_layers_from_object(const int obj_idx); bool del_subobject_from_object(const int obj_idx, const int idx, const int type); void split(); + void layers_editing(); + + wxDataViewItem add_layer_root_item(const wxDataViewItem obj_item); + wxDataViewItem add_settings_item(wxDataViewItem parent_item, const DynamicPrintConfig* config); + + DynamicPrintConfig get_default_layer_config(const int obj_idx); bool get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume); bool is_splittable(); bool selected_instances_of_same_object(); @@ -235,12 +271,13 @@ public: wxBoxSizer* get_sizer() {return m_sizer;} int get_selected_obj_idx() const; DynamicPrintConfig& get_item_config(const wxDataViewItem& item) const; + SettingsBundle get_item_settings_bundle(const DynamicPrintConfig* config, const bool is_object_settings); void changed_object(const int obj_idx = -1) const; void part_selection_changed(); // Add object to the list - void add_object_to_list(size_t obj_idx); + void add_object_to_list(size_t obj_idx, bool call_selection_changed = true); // Delete object from the list void delete_object_from_list(); void delete_object_from_list(const size_t obj_idx); @@ -265,10 +302,21 @@ public: // Remove objects/sub-object from the list void remove(); + void del_layer_range(const t_layer_height_range& range); + void add_layer_range_after_current(const t_layer_height_range& current_range); + void add_layer_item (const t_layer_height_range& range, + const wxDataViewItem layers_item, + const int layer_idx = -1); + bool edit_layer_range(const t_layer_height_range& range, coordf_t layer_height); + bool edit_layer_range(const t_layer_height_range& range, + const t_layer_height_range& new_range); void init_objects(); bool multiple_selection() const ; bool is_selected(const ItemType type) const; + int get_selected_layers_range_idx() const; + void set_selected_layers_range_idx(const int range_idx) { m_selected_layers_range_idx = range_idx; } + void set_selection_mode(SELECTION_MODE mode) { m_selection_mode = mode; } void update_selections(); void update_selections_on_canvas(); void select_item(const wxDataViewItem& item); @@ -284,8 +332,10 @@ public: void change_part_type(); void last_volume_is_deleted(const int obj_idx); - bool has_multi_part_objects(); void update_settings_items(); + void update_and_show_object_settings_item(); + void update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections); + void update_object_list_by_printer_technology(); void update_object_menu(); void instances_to_separated_object(const int obj_idx, const std::set& inst_idx); @@ -295,16 +345,24 @@ public: void fix_through_netfabb(); void update_item_error_icon(const int obj_idx, int vol_idx) const ; + void fill_layer_config_ranges_cache(); + void paste_layers_into_list(); void paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes); void paste_objects_into_list(const std::vector& object_idxs); void msw_rescale(); + void update_after_undo_redo(); + //update printable state for item from objects model + void update_printable_state(int obj_idx, int instance_idx); + void toggle_printable_state(wxDataViewItem item); + private: #ifdef __WXOSX__ // void OnChar(wxKeyEvent& event); #endif /* __WXOSX__ */ void OnContextMenu(wxDataViewEvent &event); + void list_manipulation(); void OnBeginDrag(wxDataViewEvent &event); void OnDropPossible(wxDataViewEvent &event); @@ -312,6 +370,10 @@ private: bool can_drop(const wxDataViewItem& item) const ; void ItemValueChanged(wxDataViewEvent &event); +#ifdef __WXMSW__ + // Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected. + void OnEditingStarted(wxDataViewEvent &event); +#endif /* __WXMSW__ */ void OnEditingDone(wxDataViewEvent &event); void show_multi_selection_menu(); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 5bf25f3fc0..29013389e5 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -17,6 +17,28 @@ namespace Slic3r namespace GUI { + +// Helper function to be used by drop to bed button. Returns lowest point of this +// volume in world coordinate system. +static double get_volume_min_z(const GLVolume* volume) +{ + const Transform3f& world_matrix = volume->world_matrix().cast(); + + // need to get the ModelVolume pointer + const ModelObject* mo = wxGetApp().model().objects[volume->composite_id.object_id]; + const ModelVolume* mv = mo->volumes[volume->composite_id.volume_id]; + const TriangleMesh& hull = mv->get_convex_hull(); + + float min_z = std::numeric_limits::max(); + for (const stl_facet& facet : hull.stl.facet_start) { + for (int i = 0; i < 3; ++ i) + min_z = std::min(min_z, Vec3f::UnitZ().dot(world_matrix * facet.vertex[i])); + } + return min_z; +} + + + static wxBitmapComboBox* create_word_local_combo(wxWindow *parent) { wxSize size(15 * wxGetApp().em_unit(), -1); @@ -92,6 +114,7 @@ void msw_rescale_word_local_combo(wxBitmapComboBox* combo) combo->SetValue(selection); } + ObjectManipulation::ObjectManipulation(wxWindow* parent) : OG_Settings(parent, true) #ifndef __APPLE__ @@ -130,8 +153,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : auto manifold_warning_icon = [this](wxWindow* parent) { m_fix_throught_netfab_bitmap = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(m_fix_throught_netfab_bitmap); +// auto sizer = new wxBoxSizer(wxHORIZONTAL); +// sizer->Add(m_fix_throught_netfab_bitmap); if (is_windows10()) m_fix_throught_netfab_bitmap->Bind(wxEVT_CONTEXT_MENU, [this](wxCommandEvent &e) @@ -144,17 +167,19 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : update_warning_icon_state(wxGetApp().obj_list()->get_mesh_errors_list()); }); - return sizer; +// return sizer; + return m_fix_throught_netfab_bitmap; }; - line.append_widget(manifold_warning_icon); + // line.append_widget(manifold_warning_icon); + line.near_label_widget = manifold_warning_icon; def.label = ""; def.gui_type = "legend"; def.tooltip = L("Object name"); #ifdef __APPLE__ - def.width = 19; + def.width = 20; #else - def.width = 21; + def.width = 22; #endif def.set_default_value(new ConfigOptionString{ " " }); line.append_option(Option(def, "object_name")); @@ -162,16 +187,71 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : const int field_width = 5; + // Mirror button size: + const int mirror_btn_width = 3; + // Legend for object modification line = Line{ "", "" }; def.label = ""; def.type = coString; - def.width = field_width/*50*/; + def.width = field_width - mirror_btn_width;//field_width/*50*/; - for (const std::string axis : { "x", "y", "z" }) { - const std::string label = boost::algorithm::to_upper_copy(axis); - def.set_default_value(new ConfigOptionString{ " " + label }); - Option option = Option(def, axis + "_axis_legend"); + // Load bitmaps to be used for the mirroring buttons: + m_mirror_bitmap_on = ScalableBitmap(parent, "mirroring_on"); + m_mirror_bitmap_off = ScalableBitmap(parent, "mirroring_off"); + m_mirror_bitmap_hidden = ScalableBitmap(parent, "mirroring_transparent.png"); + + static const char axes[] = { 'X', 'Y', 'Z' }; + for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) { + const char label = axes[axis_idx]; + def.set_default_value(new ConfigOptionString{ std::string(" ") + label }); + Option option(def, std::string() + label + "_axis_legend"); + + // We will add a button to toggle mirroring to each axis: + auto mirror_button = [this, mirror_btn_width, axis_idx, label](wxWindow* parent) { + wxSize btn_size(em_unit(parent) * mirror_btn_width, em_unit(parent) * mirror_btn_width); + auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off", wxEmptyString, btn_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); + btn->SetToolTip(wxString::Format(_(L("Toggle %c axis mirroring")), (int)label)); + btn->SetBitmapDisabled_(m_mirror_bitmap_hidden); + + m_mirror_buttons[axis_idx].first = btn; + m_mirror_buttons[axis_idx].second = mbShown; + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn); + + btn->Bind(wxEVT_BUTTON, [this, axis_idx](wxCommandEvent &e) { + Axis axis = (Axis)(axis_idx + X); + if (m_mirror_buttons[axis_idx].second == mbHidden) + return; + + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + Selection& selection = canvas->get_selection(); + + if (selection.is_single_volume() || selection.is_single_modifier()) { + GLVolume* volume = const_cast(selection.get_volume(*selection.get_volume_idxs().begin())); + volume->set_volume_mirror(axis, -volume->get_volume_mirror(axis)); + } + else if (selection.is_single_full_instance()) { + for (unsigned int idx : selection.get_volume_idxs()){ + GLVolume* volume = const_cast(selection.get_volume(idx)); + volume->set_instance_mirror(axis, -volume->get_instance_mirror(axis)); + } + } + else + return; + + // Update mirroring at the GLVolumes. + selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); + selection.synchronize_unselected_volumes(); + // Copy mirroring values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. + canvas->do_mirror(L("Set Mirror")); + UpdateAndShow(true); + }); + + return sizer; + }; + + option.side_widget = mirror_button; line.append_option(option); } line.near_label_widget = [this](wxWindow* parent) { @@ -190,8 +270,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : def.set_default_value(new ConfigOptionFloat(0.0)); def.width = field_width/*50*/; - // Add "uniform scaling" button in front of "Scale" option if (option_name == "Scale") { + // Add "uniform scaling" button in front of "Scale" option line.near_label_widget = [this](wxWindow* parent) { auto btn = new LockButton(parent, wxID_ANY); btn->Bind(wxEVT_BUTTON, [btn, this](wxCommandEvent &event){ @@ -201,8 +281,89 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : m_lock_bnt = btn; return btn; }; + // Add reset scale button + auto reset_scale_button = [this](wxWindow* parent) { + auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); + btn->SetToolTip(_(L("Reset scale"))); + m_reset_scale_button = btn; + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn, wxBU_EXACTFIT); + btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Reset scale"))); + change_scale_value(0, 100.); + change_scale_value(1, 100.); + change_scale_value(2, 100.); + }); + return sizer; + }; + line.append_widget(reset_scale_button); } + else if (option_name == "Rotation") { + // Add reset rotation button + auto reset_rotation_button = [this](wxWindow* parent) { + auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); + btn->SetToolTip(_(L("Reset rotation"))); + m_reset_rotation_button = btn; + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn, wxBU_EXACTFIT); + btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + Selection& selection = canvas->get_selection(); + if (selection.is_single_volume() || selection.is_single_modifier()) { + GLVolume* volume = const_cast(selection.get_volume(*selection.get_volume_idxs().begin())); + volume->set_volume_rotation(Vec3d::Zero()); + } + else if (selection.is_single_full_instance()) { + for (unsigned int idx : selection.get_volume_idxs()){ + GLVolume* volume = const_cast(selection.get_volume(idx)); + volume->set_instance_rotation(Vec3d::Zero()); + } + } + else + return; + + // Update rotation at the GLVolumes. + selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); + selection.synchronize_unselected_volumes(); + // Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. + canvas->do_rotate(L("Reset Rotation")); + + UpdateAndShow(true); + }); + return sizer; + }; + line.append_widget(reset_rotation_button); + } + else if (option_name == "Position") { + // Add drop to bed button + auto drop_to_bed_button = [=](wxWindow* parent) { + auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "drop_to_bed")); + btn->SetToolTip(_(L("Drop to bed"))); + m_drop_to_bed_button = btn; + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn, wxBU_EXACTFIT); + btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { + // ??? + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + Selection& selection = canvas->get_selection(); + + if (selection.is_single_volume() || selection.is_single_modifier()) { + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + + const Geometry::Transformation& instance_trafo = volume->get_instance_transformation(); + Vec3d diff = m_cache.position - instance_trafo.get_matrix(true).inverse() * Vec3d(0., 0., get_volume_min_z(volume)); + + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Drop to bed"))); + change_position_value(0, diff.x()); + change_position_value(1, diff.y()); + change_position_value(2, diff.z()); + } + }); + return sizer; + }; + line.append_widget(drop_to_bed_button); + } // Add empty bmp (Its size have to be equal to PrusaLockButton) in front of "Size" option to label alignment else if (option_name == "Size") { line.near_label_widget = [this](wxWindow* parent) { @@ -224,8 +385,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : return line; }; - // Settings table + m_og->sidetext_width = 3; m_og->append_line(add_og_to_object_settings(L("Position"), L("mm")), &m_move_Label); m_og->append_line(add_og_to_object_settings(L("Rotation"), "°"), &m_rotate_Label); m_og->append_line(add_og_to_object_settings(L("Scale"), "%"), &m_scale_Label); @@ -233,12 +394,23 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : // call back for a rescale of button "Set uniform scale" m_og->rescale_near_label_widget = [this](wxWindow* win) { + // rescale lock icon auto *ctrl = dynamic_cast(win); - if (ctrl == nullptr) + if (ctrl != nullptr) { + ctrl->msw_rescale(); return; - ctrl->msw_rescale(); + } + + if (win == m_fix_throught_netfab_bitmap) + return; + + // rescale "place" of the empty icon (to correct layout of the "Size" and "Scale") + if (dynamic_cast(win) != nullptr) + win->SetMinSize(create_scaled_bitmap(m_parent, "one_layer_lock_on.png").GetSize()); }; } + + void ObjectManipulation::Show(const bool show) { @@ -308,7 +480,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_scale = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.; } else { m_new_rotation = volume->get_instance_rotation() * (180. / M_PI); - m_new_size = volume->get_instance_transformation().get_scaling_factor().cwiseProduct((*wxGetApp().model_objects())[volume->object_idx()]->raw_mesh_bounding_box().size()); + m_new_size = volume->get_instance_transformation().get_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size()); m_new_scale = volume->get_instance_scaling_factor() * 100.; } @@ -332,7 +504,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_position = volume->get_volume_offset(); m_new_rotation = volume->get_volume_rotation() * (180. / M_PI); m_new_scale = volume->get_volume_scaling_factor() * 100.; - m_new_size = volume->get_volume_transformation().get_scaling_factor().cwiseProduct(volume->bounding_box.size()); + m_new_size = volume->get_volume_transformation().get_scaling_factor().cwiseProduct(volume->bounding_box().size()); m_new_enabled = true; } else if (obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot)) @@ -390,11 +562,13 @@ void ObjectManipulation::update_if_dirty() if (selection.requires_uniform_scale()) { m_lock_bnt->SetLock(true); - m_lock_bnt->Disable(); + m_lock_bnt->SetToolTip(_(L("You cann't use non-uniform scaling mode for multiple objects/parts selection"))); + m_lock_bnt->disable(); } else { m_lock_bnt->SetLock(m_uniform_scale); - m_lock_bnt->Enable(); + m_lock_bnt->SetToolTip(wxEmptyString); + m_lock_bnt->enable(); } { @@ -408,9 +582,100 @@ void ObjectManipulation::update_if_dirty() else m_og->disable(); + update_reset_buttons_visibility(); + update_mirror_buttons_visibility(); + m_dirty = false; } + + +void ObjectManipulation::update_reset_buttons_visibility() +{ + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + if (!canvas) + return; + const Selection& selection = canvas->get_selection(); + + bool show_rotation = false; + bool show_scale = false; + bool show_drop_to_bed = false; + + if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + Vec3d rotation; + Vec3d scale; + double min_z = 0.; + + if (selection.is_single_full_instance()) { + rotation = volume->get_instance_rotation(); + scale = volume->get_instance_scaling_factor(); + } + else { + rotation = volume->get_volume_rotation(); + scale = volume->get_volume_scaling_factor(); + min_z = get_volume_min_z(volume); + } + show_rotation = !rotation.isApprox(Vec3d::Zero()); + show_scale = !scale.isApprox(Vec3d::Ones()); + show_drop_to_bed = (std::abs(min_z) > EPSILON); + } + + wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed]{ + m_reset_rotation_button->Show(show_rotation); + m_reset_scale_button->Show(show_scale); + m_drop_to_bed_button->Show(show_drop_to_bed); + }); +} + + + +void ObjectManipulation::update_mirror_buttons_visibility() +{ + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + Selection& selection = canvas->get_selection(); + std::array new_states = {mbHidden, mbHidden, mbHidden}; + + if (!m_world_coordinates) { + if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + Vec3d mirror; + + if (selection.is_single_full_instance()) + mirror = volume->get_instance_mirror(); + else + mirror = volume->get_volume_mirror(); + + for (unsigned char i=0; i<3; ++i) + new_states[i] = (mirror[i] < 0. ? mbActive : mbShown); + } + } + else { + // the mirroring buttons should be hidden in world coordinates, + // unless we make it actually mirror in world coords. + } + + // Hiding the buttons through Hide() always messed up the sizers. As a workaround, the button + // is assigned a transparent bitmap. We must of course remember the actual state. + wxGetApp().CallAfter([this, new_states]{ + for (int i=0; i<3; ++i) { + if (new_states[i] != m_mirror_buttons[i].second) { + const ScalableBitmap* bmp; + switch (new_states[i]) { + case mbHidden : bmp = &m_mirror_bitmap_hidden; m_mirror_buttons[i].first->Enable(false); break; + case mbShown : bmp = &m_mirror_bitmap_off; m_mirror_buttons[i].first->Enable(true); break; + case mbActive : bmp = &m_mirror_bitmap_on; m_mirror_buttons[i].first->Enable(true); break; + } + m_mirror_buttons[i].first->SetBitmap_(*bmp); + m_mirror_buttons[i].second = new_states[i]; + } + } + }); +} + + + + #ifndef __APPLE__ void ObjectManipulation::emulate_kill_focus() { @@ -431,6 +696,7 @@ void ObjectManipulation::emulate_kill_focus() void ObjectManipulation::update_warning_icon_state(const wxString& tooltip) { m_fix_throught_netfab_bitmap->SetBitmap(tooltip.IsEmpty() ? wxNullBitmap : m_manifold_warning_bmp.bmp()); + m_fix_throught_netfab_bitmap->SetMinSize(tooltip.IsEmpty() ? wxSize(0,0) : m_manifold_warning_bmp.bmp().GetSize()); m_fix_throught_netfab_bitmap->SetToolTip(tooltip); } @@ -458,7 +724,7 @@ void ObjectManipulation::change_position_value(int axis, double value) Selection& selection = canvas->get_selection(); selection.start_dragging(); selection.translate(position - m_cache.position, selection.requires_local_axes()); - canvas->do_move(); + canvas->do_move(L("Set Position")); m_cache.position = position; m_cache.position_rounded(axis) = DBL_MAX; @@ -489,11 +755,11 @@ void ObjectManipulation::change_rotation_value(int axis, double value) selection.rotate( (M_PI / 180.0) * (transformation_type.absolute() ? rotation : rotation - m_cache.rotation), transformation_type); - canvas->do_rotate(); + canvas->do_rotate(L("Set Orientation")); m_cache.rotation = rotation; m_cache.rotation_rounded(axis) = DBL_MAX; - this->UpdateAndShow(true); + this->UpdateAndShow(true); } void ObjectManipulation::change_scale_value(int axis, double value) @@ -511,6 +777,7 @@ void ObjectManipulation::change_scale_value(int axis, double value) this->UpdateAndShow(true); } + void ObjectManipulation::change_size_value(int axis, double value) { if (std::abs(m_cache.size_rounded(axis) - value) < EPSILON) @@ -523,11 +790,11 @@ void ObjectManipulation::change_size_value(int axis, double value) Vec3d ref_size = m_cache.size; if (selection.is_single_volume() || selection.is_single_modifier()) - ref_size = selection.get_volume(*selection.get_volume_idxs().begin())->bounding_box.size(); - else if (selection.is_single_full_instance()) + ref_size = selection.get_volume(*selection.get_volume_idxs().begin())->bounding_box().size(); + else if (selection.is_single_full_instance()) ref_size = m_world_coordinates ? selection.get_unscaled_instance_bounding_box().size() : - (*wxGetApp().model_objects())[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size(); + wxGetApp().model().objects[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size(); this->do_scale(axis, 100. * Vec3d(size(0) / ref_size(0), size(1) / ref_size(1), size(2) / ref_size(2))); @@ -553,7 +820,7 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const selection.start_dragging(); selection.scale(scaling_factor * 0.01, transformation_type); - wxGetApp().plater()->canvas3D()->do_scale(); + wxGetApp().plater()->canvas3D()->do_scale(L("Set Scale")); } void ObjectManipulation::on_change(t_config_option_key opt_key, const boost::any& value) @@ -650,7 +917,7 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value) return; } // Bake the rotation into the meshes of the object. - (*wxGetApp().model_objects())[volume->composite_id.object_id]->bake_xy_rotation_into_meshes(volume->composite_id.instance_id); + wxGetApp().model().objects[volume->composite_id.object_id]->bake_xy_rotation_into_meshes(volume->composite_id.instance_id); // Update the 3D scene, selections etc. wxGetApp().plater()->update(); // Recalculate cached values at this panel, refresh the screen. @@ -664,7 +931,20 @@ void ObjectManipulation::msw_rescale() { msw_rescale_word_local_combo(m_word_local_combo); m_manifold_warning_bmp.msw_rescale(); - m_fix_throught_netfab_bitmap->SetBitmap(m_manifold_warning_bmp.bmp()); + + const wxString& tooltip = m_fix_throught_netfab_bitmap->GetToolTipText(); + m_fix_throught_netfab_bitmap->SetBitmap(tooltip.IsEmpty() ? wxNullBitmap : m_manifold_warning_bmp.bmp()); + m_fix_throught_netfab_bitmap->SetMinSize(tooltip.IsEmpty() ? wxSize(0, 0) : m_manifold_warning_bmp.bmp().GetSize()); + + m_mirror_bitmap_on.msw_rescale(); + m_mirror_bitmap_off.msw_rescale(); + m_mirror_bitmap_hidden.msw_rescale(); + m_reset_scale_button->msw_rescale(); + m_reset_rotation_button->msw_rescale(); + m_drop_to_bed_button->msw_rescale(); + + for (int id = 0; id < 3; ++id) + m_mirror_buttons[id].first->msw_rescale(); get_og()->msw_rescale(); } diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index 7c359f3d72..e4e190b5bc 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -53,6 +53,24 @@ class ObjectManipulation : public OG_Settings wxStaticText* m_scale_Label = nullptr; wxStaticText* m_rotate_Label = nullptr; + // Non-owning pointers to the reset buttons, so we can hide and show them. + ScalableButton* m_reset_scale_button = nullptr; + ScalableButton* m_reset_rotation_button = nullptr; + ScalableButton* m_drop_to_bed_button = nullptr; + + // Mirroring buttons and their current state + enum MirrorButtonState { + mbHidden, + mbShown, + mbActive + }; + std::array, 3> m_mirror_buttons; + + // Bitmaps for the mirroring buttons. + ScalableBitmap m_mirror_bitmap_on; + ScalableBitmap m_mirror_bitmap_off; + ScalableBitmap m_mirror_bitmap_hidden; + // Needs to be updated from OnIdle? bool m_dirty = false; // Cached labels for the delayed update, not localized! @@ -111,10 +129,10 @@ private: void reset_settings_value(); void update_settings_value(const Selection& selection); - // update size values after scale unit changing or "gizmos" - void update_size_value(const Vec3d& size); - // update rotation value after "gizmos" - void update_rotation_value(const Vec3d& rotation); + // Show or hide scale/rotation reset buttons if needed + void update_reset_buttons_visibility(); + //Show or hide mirror buttons + void update_mirror_buttons_visibility(); // change values void change_position_value(int axis, double value); diff --git a/src/slic3r/GUI/GUI_ObjectSettings.cpp b/src/slic3r/GUI/GUI_ObjectSettings.cpp index 4107e872a3..19a0e785ce 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.cpp @@ -61,20 +61,31 @@ ObjectSettings::ObjectSettings(wxWindow* parent) : m_og->sizer->Add(m_settings_list_sizer, 1, wxEXPAND | wxLEFT, 5); m_bmp_delete = ScalableBitmap(parent, "cross"); + m_bmp_delete_focus = ScalableBitmap(parent, "cross_focus"); } -void ObjectSettings::update_settings_list() +bool ObjectSettings::update_settings_list() { m_settings_list_sizer->Clear(true); + m_og_settings.resize(0); auto objects_ctrl = wxGetApp().obj_list(); - auto objects_model = wxGetApp().obj_list()->m_objects_model; - auto config = wxGetApp().obj_list()->m_config; + auto objects_model = wxGetApp().obj_list()->GetModel(); + auto config = wxGetApp().obj_list()->config(); const auto item = objects_ctrl->GetSelection(); - if (item && !objects_ctrl->multiple_selection() && - config && objects_model->IsSettingsItem(item)) - { + + if (!item || !objects_model->IsSettingsItem(item) || !config || objects_ctrl->multiple_selection()) + return false; + + const bool is_object_settings = objects_model->GetItemType(objects_model->GetParent(item)) == itObject; + SettingsBundle cat_options = objects_ctrl->get_item_settings_bundle(config, is_object_settings); + + if (!cat_options.empty()) + { + std::vector categories; + categories.reserve(cat_options.size()); + auto extra_column = [config, this](wxWindow* parent, const Line& line) { auto opt_key = (line.get_options())[0].opt_id; //we assume that we have one option per line @@ -82,7 +93,11 @@ void ObjectSettings::update_settings_list() auto btn = new ScalableButton(parent, wxID_ANY, m_bmp_delete); btn->SetToolTip(_(L("Remove parameter"))); + btn->SetBitmapFocus(m_bmp_delete_focus.bmp()); + btn->SetBitmapHover(m_bmp_delete_focus.bmp()); + btn->Bind(wxEVT_BUTTON, [opt_key, config, this](wxEvent &event) { + wxGetApp().plater()->take_snapshot(wxString::Format(_(L("Delete Option %s")), opt_key)); config->erase(opt_key); wxGetApp().obj_list()->changed_object(); wxTheApp->CallAfter([this]() { @@ -94,90 +109,70 @@ void ObjectSettings::update_settings_list() return btn; }; - std::map> cat_options; - auto opt_keys = config->keys(); - objects_ctrl->update_opt_keys(opt_keys); // update options list according to print technology - - m_og_settings.resize(0); - std::vector categories; - if (!(opt_keys.size() == 1 && opt_keys[0] == "extruder"))// return; + for (auto& cat : cat_options) { - const int extruders_cnt = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA ? 1 : - wxGetApp().preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter")->values.size(); + categories.push_back(cat.first); - for (auto& opt_key : opt_keys) { - auto category = config->def()->get(opt_key)->category; - if (category.empty() || - (category == "Extruders" && extruders_cnt == 1)) continue; + auto optgroup = std::make_shared(m_og->ctrl_parent(), _(cat.first), config, false, extra_column); + optgroup->label_width = 15; + optgroup->sidetext_width = 5.5; - std::vector< std::string > new_category; + optgroup->m_on_change = [](const t_config_option_key& opt_id, const boost::any& value) { + wxGetApp().obj_list()->changed_object(); }; - auto& cat_opt = cat_options.find(category) == cat_options.end() ? new_category : cat_options.at(category); - cat_opt.push_back(opt_key); - if (cat_opt.size() == 1) - cat_options[category] = cat_opt; - } + // call back for rescaling of the extracolumn control + optgroup->rescale_extra_column_item = [this](wxWindow* win) { + auto *ctrl = dynamic_cast(win); + if (ctrl == nullptr) + return; + ctrl->SetBitmap_(m_bmp_delete); + ctrl->SetBitmapFocus(m_bmp_delete_focus.bmp()); + ctrl->SetBitmapHover(m_bmp_delete_focus.bmp()); + }; - for (auto& cat : cat_options) { - if (cat.second.size() == 1 && cat.second[0] == "extruder") - continue; + const bool is_extruders_cat = cat.first == "Extruders"; + for (auto& opt : cat.second) + { + Option option = optgroup->get_option(opt); + option.opt.width = 12; + if (is_extruders_cat) + option.opt.max = wxGetApp().extruders_edited_cnt(); + optgroup->append_single_option_line(option); - auto optgroup = std::make_shared(m_og->ctrl_parent(), _(cat.first), config, false, extra_column); - optgroup->label_width = 15; - optgroup->sidetext_width = 5.5; - - optgroup->m_on_change = [](const t_config_option_key& opt_id, const boost::any& value) { - wxGetApp().obj_list()->changed_object(); }; - - const bool is_extriders_cat = cat.first == "Extruders"; - for (auto& opt : cat.second) - { - if (opt == "extruder") - continue; - Option option = optgroup->get_option(opt); - option.opt.width = 12; - if (is_extriders_cat) - option.opt.max = wxGetApp().extruders_cnt(); - optgroup->append_single_option_line(option); - } - optgroup->reload_config(); - m_settings_list_sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 0); - - // call back for rescaling of the extracolumn control - optgroup->rescale_extra_column_item = [this](wxWindow* win) { - auto *ctrl = dynamic_cast(win); - if (ctrl == nullptr) - return; - ctrl->SetBitmap_(m_bmp_delete); + optgroup->get_field(opt)->m_on_change = [optgroup](const std::string& opt_id, const boost::any& value) { + // first of all take a snapshot and then change value in configuration + wxGetApp().plater()->take_snapshot(wxString::Format(_(L("Change Option %s")), opt_id)); + optgroup->on_change_OG(opt_id, value); }; - m_og_settings.push_back(optgroup); - - categories.push_back(cat.first); } + optgroup->reload_config(); + + m_settings_list_sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 0); + m_og_settings.push_back(optgroup); } - if (m_og_settings.empty()) { - objects_ctrl->select_item(objects_model->Delete(item)); - } - else { - if (!categories.empty()) - objects_model->UpdateSettingsDigest(item, categories); - } - } + if (!categories.empty()) + objects_model->UpdateSettingsDigest(item, categories); + } + else + { + objects_ctrl->select_item(objects_model->Delete(item)); + return false; + } + + return true; } void ObjectSettings::UpdateAndShow(const bool show) { - if (show) - update_settings_list(); - - OG_Settings::UpdateAndShow(show); + OG_Settings::UpdateAndShow(show ? update_settings_list() : false); } void ObjectSettings::msw_rescale() { m_bmp_delete.msw_rescale(); + m_bmp_delete_focus.msw_rescale(); for (auto group : m_og_settings) group->msw_rescale(); diff --git a/src/slic3r/GUI/GUI_ObjectSettings.hpp b/src/slic3r/GUI/GUI_ObjectSettings.hpp index 3d49f13b7f..2a0c19c5c3 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.hpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.hpp @@ -39,12 +39,13 @@ class ObjectSettings : public OG_Settings std::vector > m_og_settings; ScalableBitmap m_bmp_delete; + ScalableBitmap m_bmp_delete_focus; public: ObjectSettings(wxWindow* parent); ~ObjectSettings() {} - void update_settings_list(); + bool update_settings_list(); void UpdateAndShow(const bool show) override; void msw_rescale(); }; diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index d28b921d92..64218d08f6 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -63,7 +63,8 @@ bool View3D::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_ m_canvas->set_config(config); m_canvas->enable_gizmos(true); m_canvas->enable_selection(true); - m_canvas->enable_toolbar(true); + m_canvas->enable_main_toolbar(true); + m_canvas->enable_undoredo_toolbar(true); wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); main_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0); @@ -175,6 +176,7 @@ Preview::Preview( , m_checkbox_retractions(nullptr) , m_checkbox_unretractions(nullptr) , m_checkbox_shells(nullptr) + , m_checkbox_legend(nullptr) , m_config(config) , m_process(process) , m_gcode_preview_data(gcode_preview_data) @@ -251,6 +253,9 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view m_checkbox_unretractions = new wxCheckBox(this, wxID_ANY, _(L("Unretractions"))); m_checkbox_shells = new wxCheckBox(this, wxID_ANY, _(L("Shells"))); + m_checkbox_legend = new wxCheckBox(this, wxID_ANY, _(L("Legend"))); + m_checkbox_legend->SetValue(true); + wxBoxSizer* top_sizer = new wxBoxSizer(wxHORIZONTAL); top_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0); top_sizer->Add(m_double_slider_sizer, 0, wxEXPAND, 0); @@ -269,6 +274,8 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view bottom_sizer->Add(m_checkbox_unretractions, 0, wxEXPAND | wxALL, 5); bottom_sizer->AddSpacer(10); bottom_sizer->Add(m_checkbox_shells, 0, wxEXPAND | wxALL, 5); + bottom_sizer->AddSpacer(20); + bottom_sizer->Add(m_checkbox_legend, 0, wxEXPAND | wxALL, 5); wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); main_sizer->Add(top_sizer, 1, wxALL | wxEXPAND, 0); @@ -313,6 +320,12 @@ Preview::~Preview() } } +void Preview::set_as_dirty() +{ + if (m_canvas != nullptr) + m_canvas->set_as_dirty(); +} + void Preview::set_number_extruders(unsigned int number_extruders) { if (m_number_extruders != number_extruders) @@ -414,6 +427,18 @@ void Preview::msw_rescale() refresh_print(); } +void Preview::move_double_slider(wxKeyEvent& evt) +{ + if (m_slider) + m_slider->OnKeyDown(evt); +} + +void Preview::edit_double_slider(wxKeyEvent& evt) +{ + if (m_slider) + m_slider->OnChar(evt); +} + void Preview::bind_event_handlers() { this->Bind(wxEVT_SIZE, &Preview::on_size, this); @@ -423,6 +448,7 @@ void Preview::bind_event_handlers() m_checkbox_retractions->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_retractions, this); m_checkbox_unretractions->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_unretractions, this); m_checkbox_shells->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_shells, this); + m_checkbox_legend->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_legend, this); } void Preview::unbind_event_handlers() @@ -434,6 +460,7 @@ void Preview::unbind_event_handlers() m_checkbox_retractions->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_retractions, this); m_checkbox_unretractions->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_unretractions, this); m_checkbox_shells->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_shells, this); + m_checkbox_legend->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_legend, this); } void Preview::show_hide_ui_elements(const std::string& what) @@ -445,6 +472,7 @@ void Preview::show_hide_ui_elements(const std::string& what) m_checkbox_retractions->Enable(enable); m_checkbox_unretractions->Enable(enable); m_checkbox_shells->Enable(enable); + m_checkbox_legend->Enable(enable); enable = (what != "none"); m_label_view_type->Enable(enable); @@ -457,6 +485,7 @@ void Preview::show_hide_ui_elements(const std::string& what) m_checkbox_retractions->Show(visible); m_checkbox_unretractions->Show(visible); m_checkbox_shells->Show(visible); + m_checkbox_legend->Show(visible); m_label_view_type->Show(visible); m_choice_view_type->Show(visible); } @@ -523,6 +552,32 @@ void Preview::on_checkbox_shells(wxCommandEvent& evt) refresh_print(); } +void Preview::on_checkbox_legend(wxCommandEvent& evt) +{ + m_canvas->enable_legend_texture(m_checkbox_legend->IsChecked()); + m_canvas_widget->Refresh(); +} + +void Preview::update_view_type() +{ + const DynamicPrintConfig& config = wxGetApp().preset_bundle->project_config; + + const wxString& choice = !config.option("colorprint_heights")->values.empty() && + wxGetApp().extruders_edited_cnt()==1 ? + _(L("Color Print")) : + config.option("wiping_volumes_matrix")->values.size() > 1 ? + _(L("Tool")) : + _(L("Feature type")); + + int type = m_choice_view_type->FindString(choice); + if (m_choice_view_type->GetSelection() != type) { + m_choice_view_type->SetSelection(type); + if (0 <= type && type < (int)GCodePreviewData::Extrusion::Num_View_Types) + m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type; + m_preferred_color_mode = "feature"; + } +} + void Preview::create_double_slider() { m_slider = new DoubleSlider(this, wxID_ANY, 0, 0, 0, 100); @@ -535,23 +590,13 @@ void Preview::create_double_slider() Bind(wxCUSTOMEVT_TICKSCHANGED, [this](wxEvent&) { - auto& config = wxGetApp().preset_bundle->project_config; - ((config.option("colorprint_heights"))->values) = (m_slider->GetTicksValues()); - m_schedule_background_process(); + wxGetApp().preset_bundle->project_config.option("colorprint_heights")->values = m_slider->GetTicksValues(); + m_schedule_background_process(); - const wxString& choise = !config.option("colorprint_heights")->values.empty() ? _(L("Color Print")) : - config.option("wiping_volumes_matrix")->values.size() > 1 ? - _(L("Tool")) : _(L("Feature type")); + update_view_type(); - int type = m_choice_view_type->FindString(choise); - if (m_choice_view_type->GetSelection() != type) { - m_choice_view_type->SetSelection(type); - if ((0 <= type) && (type < (int)GCodePreviewData::Extrusion::Num_View_Types)) - m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type; - m_preferred_color_mode = "feature"; - } - reload_print(); - }); + reload_print(); + }); } // Find an index of a value in a sorted vector, which is in . @@ -673,6 +718,11 @@ void Preview::update_double_slider_from_canvas(wxKeyEvent& event) m_slider->SetHigherValue(new_pos); if (event.ShiftDown() || m_slider->is_one_layer()) m_slider->SetLowerValue(m_slider->GetHigherValue()); } + else if (key == 'L') { + m_checkbox_legend->SetValue(!m_checkbox_legend->GetValue()); + auto evt = wxCommandEvent(); + on_checkbox_legend(evt); + } else if (key == 'S') m_slider->ChangeOneLayerLock(); else @@ -769,9 +819,14 @@ void Preview::load_print_as_fff(bool keep_z_range) // Load the real G-code preview. m_canvas->load_gcode_preview(*m_gcode_preview_data, colors); m_loaded = true; - } else + } else { + // disable color change information for multi-material presets + if (wxGetApp().extruders_edited_cnt() > 1) + color_print_values.clear(); + // Load the initial preview based on slices, not the final G-code. m_canvas->load_preview(colors, color_print_values); + } show_hide_ui_elements(gcode_preview_data_valid ? "full" : "simple"); // recalculates zs and update sliders accordingly std::vector zs = m_canvas->get_current_print_zs(true); diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 1838082c38..401b17a4b2 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -80,6 +80,7 @@ class Preview : public wxPanel wxCheckBox* m_checkbox_retractions; wxCheckBox* m_checkbox_unretractions; wxCheckBox* m_checkbox_shells; + wxCheckBox* m_checkbox_legend; DynamicPrintConfig* m_config; BackgroundSlicingProcess* m_process; @@ -110,6 +111,8 @@ public: wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; } GLCanvas3D* get_canvas3d() { return m_canvas; } + void set_as_dirty(); + void set_number_extruders(unsigned int number_extruders); void set_canvas_as_dirty(); void set_enabled(bool enabled); @@ -122,6 +125,12 @@ public: void refresh_print(); void msw_rescale(); + void move_double_slider(wxKeyEvent& evt); + void edit_double_slider(wxKeyEvent& evt); + + void update_view_type(); + + bool is_loaded() const { return m_loaded; } private: bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model); @@ -141,6 +150,7 @@ private: void on_checkbox_retractions(wxCommandEvent& evt); void on_checkbox_unretractions(wxCommandEvent& evt); void on_checkbox_shells(wxCommandEvent& evt); + void on_checkbox_legend(wxCommandEvent& evt); // Create/Update/Reset double slider on 3dPreview void create_double_slider(); diff --git a/src/slic3r/GUI/GUI_Utils.cpp b/src/slic3r/GUI/GUI_Utils.cpp index 74e70c5546..d5753f2ccf 100644 --- a/src/slic3r/GUI/GUI_Utils.cpp +++ b/src/slic3r/GUI/GUI_Utils.cpp @@ -62,7 +62,7 @@ template typename F::FN winapi_get_function(const wchar_t *dll, const c static HINSTANCE dll_handle = LoadLibraryExW(dll, nullptr, 0); if (dll_handle == nullptr) { return nullptr; } - return (F::FN)GetProcAddress(dll_handle, fn_name); + return (typename F::FN)GetProcAddress(dll_handle, fn_name); } #endif diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index a17bbf6d33..c47714e464 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -64,6 +64,12 @@ public: m_prev_scale_factor = m_scale_factor; m_normal_font = get_default_font_for_dpi(dpi); + /* Because of default window font is a primary display font, + * We should set correct font for window before getting em_unit value. + */ +#ifndef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList + this->SetFont(m_normal_font); +#endif // initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window. m_em_unit = std::max(10, this->GetTextExtent("m").x - 1); @@ -72,6 +78,8 @@ public: this->Bind(EVT_DPI_CHANGED, [this](const DpiChangedEvent &evt) { m_scale_factor = (float)evt.dpi / (float)DPI_DEFAULT; + m_new_font_point_size = get_default_font_for_dpi(evt.dpi).GetPointSize(); + if (!m_can_rescale) return; @@ -124,6 +132,8 @@ private: float m_prev_scale_factor; bool m_can_rescale{ true }; + int m_new_font_point_size; + // void recalc_font() // { // wxClientDC dc(this); @@ -135,14 +145,22 @@ private: // check if new scale is differ from previous bool is_new_scale_factor() const { return fabs(m_scale_factor - m_prev_scale_factor) > 0.001; } + // function for a font scaling of the window + void scale_win_font(wxWindow *window, const int font_point_size) + { + wxFont new_font(window->GetFont()); + new_font.SetPointSize(font_point_size); + window->SetFont(new_font); + } + // recursive function for scaling fonts for all controls in Window - void scale_controls_fonts(wxWindow *window, const float scale_f) + void scale_controls_fonts(wxWindow *window, const int font_point_size) { auto children = window->GetChildren(); for (auto child : children) { - scale_controls_fonts(child, scale_f); - child->SetFont(child->GetFont().Scaled(scale_f)); + scale_controls_fonts(child, font_point_size); + scale_win_font(child, font_point_size); } window->Layout(); @@ -151,18 +169,18 @@ private: void rescale(const wxRect &suggested_rect) { this->Freeze(); - const float relative_scale_factor = m_scale_factor / m_prev_scale_factor; // rescale fonts of all controls - scale_controls_fonts(this, relative_scale_factor); - this->SetFont(this->GetFont().Scaled(relative_scale_factor)); + scale_controls_fonts(this, m_new_font_point_size); + // rescale current window font + scale_win_font(this, m_new_font_point_size); - // rescale normal_font value - m_normal_font = m_normal_font.Scaled(relative_scale_factor); + // set normal application font as a current window font + m_normal_font = this->GetFont(); - // An analog of em_unit value from GUI_App. - m_em_unit = std::max(10, 10 * m_scale_factor); + // update em_unit value for new window font + m_em_unit = std::max(10, this->GetTextExtent("m").x - 1); // rescale missed controls sizes and images on_dpi_changed(suggested_rect); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index 030bd0146e..29e5e5686d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -29,19 +29,21 @@ GLGizmoBase::Grabber::Grabber() color[0] = 1.0f; color[1] = 1.0f; color[2] = 1.0f; + color[3] = 1.0f; } void GLGizmoBase::Grabber::render(bool hover, float size) const { - float render_color[3]; + float render_color[4]; if (hover) { render_color[0] = 1.0f - color[0]; render_color[1] = 1.0f - color[1]; render_color[2] = 1.0f - color[2]; + render_color[3] = color[3]; } else - ::memcpy((void*)render_color, (const void*)color, 3 * sizeof(float)); + ::memcpy((void*)render_color, (const void*)color, 4 * sizeof(float)); render(size, render_color, true); } @@ -63,7 +65,7 @@ void GLGizmoBase::Grabber::render(float size, const float* render_color, bool us if (use_lighting) glsafe(::glEnable(GL_LIGHTING)); - glsafe(::glColor3fv(render_color)); + glsafe(::glColor4fv(render_color)); glsafe(::glPushMatrix()); glsafe(::glTranslated(center(0), center(1), center(2))); @@ -132,26 +134,21 @@ void GLGizmoBase::Grabber::render_face(float half_size) const glsafe(::glEnd()); } -#if ENABLE_SVG_ICONS + GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) -#else -GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, unsigned int sprite_id) -#endif // ENABLE_SVG_ICONS : m_parent(parent) , m_group_id(-1) , m_state(Off) , m_shortcut_key(0) -#if ENABLE_SVG_ICONS , m_icon_filename(icon_filename) -#endif // ENABLE_SVG_ICONS , m_sprite_id(sprite_id) , m_hover_id(-1) , m_dragging(false) , m_imgui(wxGetApp().imgui()) { - ::memcpy((void*)m_base_color, (const void*)DEFAULT_BASE_COLOR, 3 * sizeof(float)); - ::memcpy((void*)m_drag_color, (const void*)DEFAULT_DRAG_COLOR, 3 * sizeof(float)); - ::memcpy((void*)m_highlight_color, (const void*)DEFAULT_HIGHLIGHT_COLOR, 3 * sizeof(float)); + ::memcpy((void*)m_base_color, (const void*)DEFAULT_BASE_COLOR, 4 * sizeof(float)); + ::memcpy((void*)m_drag_color, (const void*)DEFAULT_DRAG_COLOR, 4 * sizeof(float)); + ::memcpy((void*)m_highlight_color, (const void*)DEFAULT_HIGHLIGHT_COLOR, 4 * sizeof(float)); } void GLGizmoBase::set_hover_id(int id) @@ -166,12 +163,12 @@ void GLGizmoBase::set_hover_id(int id) void GLGizmoBase::set_highlight_color(const float* color) { if (color != nullptr) - ::memcpy((void*)m_highlight_color, (const void*)color, 3 * sizeof(float)); + ::memcpy((void*)m_highlight_color, (const void*)color, 4 * sizeof(float)); } void GLGizmoBase::enable_grabber(unsigned int id) { - if ((0 <= id) && (id < (unsigned int)m_grabbers.size())) + if (id < m_grabbers.size()) m_grabbers[id].enabled = true; on_enable_grabber(id); @@ -179,13 +176,13 @@ void GLGizmoBase::enable_grabber(unsigned int id) void GLGizmoBase::disable_grabber(unsigned int id) { - if ((0 <= id) && (id < (unsigned int)m_grabbers.size())) + if (id < m_grabbers.size()) m_grabbers[id].enabled = false; on_disable_grabber(id); } -void GLGizmoBase::start_dragging(const Selection& selection) +void GLGizmoBase::start_dragging() { m_dragging = true; @@ -194,7 +191,7 @@ void GLGizmoBase::start_dragging(const Selection& selection) m_grabbers[i].dragging = (m_hover_id == i); } - on_start_dragging(selection); + on_start_dragging(); } void GLGizmoBase::stop_dragging() @@ -209,13 +206,13 @@ void GLGizmoBase::stop_dragging() on_stop_dragging(); } -void GLGizmoBase::update(const UpdateData& data, const Selection& selection) +void GLGizmoBase::update(const UpdateData& data) { if (m_hover_id != -1) - on_update(data, selection); + on_update(data); } -std::array GLGizmoBase::picking_color_component(unsigned int id) const +std::array GLGizmoBase::picking_color_component(unsigned int id) const { static const float INV_255 = 1.0f / 255.0f; @@ -225,9 +222,12 @@ std::array GLGizmoBase::picking_color_component(unsigned int id) const id -= m_group_id; // color components are encoded to match the calculation of volume_id made into GLCanvas3D::_picking_pass() - return std::array { (float)((id >> 0) & 0xff) * INV_255, // red - (float)((id >> 8) & 0xff) * INV_255, // green - (float)((id >> 16) & 0xff) * INV_255 }; // blue + return std::array { + float((id >> 0) & 0xff) * INV_255, // red + float((id >> 8) & 0xff) * INV_255, // green + float((id >> 16) & 0xff) * INV_255, // blue + float(picking_checksum_alpha_channel(id & 0xff, (id >> 8) & 0xff, (id >> 16) & 0xff))* INV_255 // checksum for validating against unwanted alpha blending and multi sampling + }; } void GLGizmoBase::render_grabbers(const BoundingBoxf3& box) const @@ -252,10 +252,11 @@ void GLGizmoBase::render_grabbers_for_picking(const BoundingBoxf3& box) const { if (m_grabbers[i].enabled) { - std::array color = picking_color_component(i); + std::array color = picking_color_component(i); m_grabbers[i].color[0] = color[0]; m_grabbers[i].color[1] = color[1]; m_grabbers[i].color[2] = color[2]; + m_grabbers[i].color[3] = color[3]; m_grabbers[i].render_for_picking(mean_size); } } @@ -272,5 +273,20 @@ std::string GLGizmoBase::format(float value, unsigned int decimals) const return Slic3r::string_printf("%.*f", decimals, value); } +// Produce an alpha channel checksum for the red green blue components. The alpha channel may then be used to verify, whether the rgb components +// were not interpolated by alpha blending or multi sampling. +unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue) +{ + // 8 bit hash for the color + unsigned char b = ((((37 * red) + green) & 0x0ff) * 37 + blue) & 0x0ff; + // Increase enthropy by a bit reversal + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; + b = (b & 0xAA) >> 1 | (b & 0x55) << 1; + // Flip every second bit to increase the enthropy even more. + b ^= 0x55; + return b; +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index a29aaed3fe..7b73c62c25 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -6,6 +6,7 @@ #include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/Selection.hpp" +#include class wxWindow; class GLUquadric; @@ -20,11 +21,11 @@ class ModelObject; namespace GUI { -static const float DEFAULT_BASE_COLOR[3] = { 0.625f, 0.625f, 0.625f }; -static const float DEFAULT_DRAG_COLOR[3] = { 1.0f, 1.0f, 1.0f }; -static const float DEFAULT_HIGHLIGHT_COLOR[3] = { 1.0f, 0.38f, 0.0f }; -static const float AXES_COLOR[3][3] = { { 0.75f, 0.0f, 0.0f }, { 0.0f, 0.75f, 0.0f }, { 0.0f, 0.0f, 0.75f } }; -static const float CONSTRAINED_COLOR[3] = { 0.5f, 0.5f, 0.5f }; +static const float DEFAULT_BASE_COLOR[4] = { 0.625f, 0.625f, 0.625f, 1.0f }; +static const float DEFAULT_DRAG_COLOR[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; +static const float DEFAULT_HIGHLIGHT_COLOR[4] = { 1.0f, 0.38f, 0.0f, 1.0f }; +static const float AXES_COLOR[][4] = { { 0.75f, 0.0f, 0.0f, 1.0f }, { 0.0f, 0.75f, 0.0f, 1.0f }, { 0.0f, 0.0f, 0.75f, 1.0f } }; +static const float CONSTRAINED_COLOR[4] = { 0.5f, 0.5f, 0.5f, 1.0f }; @@ -47,7 +48,7 @@ protected: Vec3d center; Vec3d angles; - float color[3]; + float color[4]; bool enabled; bool dragging; @@ -75,10 +76,10 @@ public: struct UpdateData { - const Linef3 mouse_ray; - const Point* mouse_pos; + const Linef3& mouse_ray; + const Point& mouse_pos; - UpdateData(const Linef3& mouse_ray, const Point* mouse_pos = nullptr) + UpdateData(const Linef3& mouse_ray, const Point& mouse_pos) : mouse_ray(mouse_ray), mouse_pos(mouse_pos) {} }; @@ -89,28 +90,25 @@ protected: int m_group_id; EState m_state; int m_shortcut_key; -#if ENABLE_SVG_ICONS std::string m_icon_filename; -#endif // ENABLE_SVG_ICONS unsigned int m_sprite_id; int m_hover_id; bool m_dragging; - float m_base_color[3]; - float m_drag_color[3]; - float m_highlight_color[3]; + float m_base_color[4]; + float m_drag_color[4]; + float m_highlight_color[4]; mutable std::vector m_grabbers; ImGuiWrapper* m_imgui; public: -#if ENABLE_SVG_ICONS GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoBase(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS virtual ~GLGizmoBase() {} bool init() { return on_init(); } + void load(cereal::BinaryInputArchive& ar) { m_state = On; on_load(ar); } + void save(cereal::BinaryOutputArchive& ar) const { on_save(ar); } + std::string get_name() const { return on_get_name(); } int get_group_id() const { return m_group_id; } @@ -122,11 +120,9 @@ public: int get_shortcut_key() const { return m_shortcut_key; } void set_shortcut_key(int key) { m_shortcut_key = key; } -#if ENABLE_SVG_ICONS const std::string& get_icon_filename() const { return m_icon_filename; } -#endif // ENABLE_SVG_ICONS - bool is_activable(const Selection& selection) const { return on_is_activable(selection); } + bool is_activable() const { return on_is_activable(); } bool is_selectable() const { return on_is_selectable(); } unsigned int get_sprite_id() const { return m_sprite_id; } @@ -139,36 +135,38 @@ public: void enable_grabber(unsigned int id); void disable_grabber(unsigned int id); - void start_dragging(const Selection& selection); + void start_dragging(); void stop_dragging(); bool is_dragging() const { return m_dragging; } - void update(const UpdateData& data, const Selection& selection); + void update(const UpdateData& data); - void render(const Selection& selection) const { on_render(selection); } - void render_for_picking(const Selection& selection) const { on_render_for_picking(selection); } - void render_input_window(float x, float y, float bottom_limit, const Selection& selection) { on_render_input_window(x, y, bottom_limit, selection); } + void render() const { on_render(); } + void render_for_picking() const { on_render_for_picking(); } + void render_input_window(float x, float y, float bottom_limit) { on_render_input_window(x, y, bottom_limit); } protected: virtual bool on_init() = 0; + virtual void on_load(cereal::BinaryInputArchive& ar) {} + virtual void on_save(cereal::BinaryOutputArchive& ar) const {} virtual std::string on_get_name() const = 0; virtual void on_set_state() {} virtual void on_set_hover_id() {} - virtual bool on_is_activable(const Selection& selection) const { return true; } + virtual bool on_is_activable() const { return true; } virtual bool on_is_selectable() const { return true; } virtual void on_enable_grabber(unsigned int id) {} virtual void on_disable_grabber(unsigned int id) {} - virtual void on_start_dragging(const Selection& selection) {} + virtual void on_start_dragging() {} virtual void on_stop_dragging() {} - virtual void on_update(const UpdateData& data, const Selection& selection) = 0; - virtual void on_render(const Selection& selection) const = 0; - virtual void on_render_for_picking(const Selection& selection) const = 0; - virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) {} + virtual void on_update(const UpdateData& data) {} + virtual void on_render() const = 0; + virtual void on_render_for_picking() const = 0; + virtual void on_render_input_window(float x, float y, float bottom_limit) {} // Returns the picking color for the given id, based on the BASE_ID constant // No check is made for clashing with other picking color (i.e. GLVolumes) - std::array picking_color_component(unsigned int id) const; + std::array picking_color_component(unsigned int id) const; void render_grabbers(const BoundingBoxf3& box) const; void render_grabbers(float size) const; void render_grabbers_for_picking(const BoundingBoxf3& box) const; @@ -177,6 +175,10 @@ protected: std::string format(float value, unsigned int decimals) const; }; +// Produce an alpha channel checksum for the red green blue components. The alpha channel may then be used to verify, whether the rgb components +// were not interpolated by alpha blending or multi sampling. +extern unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue); + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 8934bc52b4..481bec9562 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -15,59 +15,12 @@ namespace Slic3r { namespace GUI { - -class GLGizmoCutPanel : public wxPanel -{ -public: - GLGizmoCutPanel(wxWindow *parent); - - void display(bool display); -private: - bool m_active; - wxCheckBox *m_cb_rotate; - wxButton *m_btn_cut; - wxButton *m_btn_cancel; -}; - -GLGizmoCutPanel::GLGizmoCutPanel(wxWindow *parent) - : wxPanel(parent) - , m_active(false) - , m_cb_rotate(new wxCheckBox(this, wxID_ANY, _(L("Rotate lower part upwards")))) - , m_btn_cut(new wxButton(this, wxID_OK, _(L("Perform cut")))) - , m_btn_cancel(new wxButton(this, wxID_CANCEL, _(L("Cancel")))) -{ - enum { MARGIN = 5 }; - - auto *sizer = new wxBoxSizer(wxHORIZONTAL); - - auto *label = new wxStaticText(this, wxID_ANY, _(L("Cut object:"))); - sizer->Add(label, 0, wxALL | wxALIGN_CENTER, MARGIN); - sizer->Add(m_cb_rotate, 0, wxALL | wxALIGN_CENTER, MARGIN); - sizer->AddStretchSpacer(); - sizer->Add(m_btn_cut, 0, wxALL | wxALIGN_CENTER, MARGIN); - sizer->Add(m_btn_cancel, 0, wxALL | wxALIGN_CENTER, MARGIN); - - SetSizer(sizer); -} - -void GLGizmoCutPanel::display(bool display) -{ - Show(display); - GetParent()->Layout(); -} - - const double GLGizmoCut::Offset = 10.0; const double GLGizmoCut::Margin = 20.0; -const std::array GLGizmoCut::GrabberColor = { 1.0, 0.5, 0.0 }; +const std::array GLGizmoCut::GrabberColor = { 1.0, 0.5, 0.0, 1.0 }; -#if ENABLE_SVG_ICONS GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) -#else -GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, unsigned int sprite_id) - : GLGizmoBase(parent, sprite_id) -#endif // ENABLE_SVG_ICONS , m_cut_z(0.0) , m_max_z(0.0) , m_keep_upper(true) @@ -75,7 +28,6 @@ GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, unsigned int sprite_id) , m_rotate_lower(false) {} - bool GLGizmoCut::on_init() { m_grabbers.emplace_back(); @@ -96,15 +48,18 @@ void GLGizmoCut::on_set_state() } } -bool GLGizmoCut::on_is_activable(const Selection& selection) const +bool GLGizmoCut::on_is_activable() const { + const Selection& selection = m_parent.get_selection(); return selection.is_single_full_instance() && !selection.is_wipe_tower(); } -void GLGizmoCut::on_start_dragging(const Selection& selection) +void GLGizmoCut::on_start_dragging() { - if (m_hover_id == -1) { return; } + if (m_hover_id == -1) + return; + const Selection& selection = m_parent.get_selection(); const BoundingBoxf3& box = selection.get_bounding_box(); m_start_z = m_cut_z; update_max_z(selection); @@ -113,19 +68,21 @@ void GLGizmoCut::on_start_dragging(const Selection& selection) m_drag_center(2) = m_cut_z; } -void GLGizmoCut::on_update(const UpdateData& data, const Selection& selection) +void GLGizmoCut::on_update(const UpdateData& data) { if (m_hover_id != -1) { set_cut_z(m_start_z + calc_projection(data.mouse_ray)); } } -void GLGizmoCut::on_render(const Selection& selection) const +void GLGizmoCut::on_render() const { if (m_grabbers[0].dragging) { set_tooltip("Z: " + format(m_cut_z, 2)); } + const Selection& selection = m_parent.get_selection(); + update_max_z(selection); const BoundingBoxf3& box = selection.get_bounding_box(); @@ -171,14 +128,13 @@ void GLGizmoCut::on_render(const Selection& selection) const m_grabbers[0].render(m_hover_id == 0, (float)((box.size()(0) + box.size()(1) + box.size()(2)) / 3.0)); } -void GLGizmoCut::on_render_for_picking(const Selection& selection) const +void GLGizmoCut::on_render_for_picking() const { glsafe(::glDisable(GL_DEPTH_TEST)); - - render_grabbers_for_picking(selection.get_bounding_box()); + render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); } -void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) +void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit) { const float approx_height = m_imgui->scaled(11.0f); y = std::min(y, bottom_limit - approx_height); @@ -188,7 +144,7 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit, co m_imgui->begin(_(L("Cut")), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); ImGui::PushItemWidth(m_imgui->scaled(5.0f)); - bool _value_changed = ImGui::InputDouble("Z", &m_cut_z, 0.0f, 0.0f, "%.2f"); + ImGui::InputDouble("Z", &m_cut_z, 0.0f, 0.0f, "%.2f"); m_imgui->checkbox(_(L("Keep upper part")), m_keep_upper); m_imgui->checkbox(_(L("Keep lower part")), m_keep_lower); @@ -201,7 +157,7 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit, co m_imgui->end(); if (cut_clicked && (m_keep_upper || m_keep_lower)) { - perform_cut(selection); + perform_cut(m_parent.get_selection()); } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index fd4e8d8dc0..6e5738a422 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -11,7 +11,7 @@ class GLGizmoCut : public GLGizmoBase { static const double Offset; static const double Margin; - static const std::array GrabberColor; + static const std::array GrabberColor; mutable double m_cut_z; double m_start_z; @@ -23,22 +23,20 @@ class GLGizmoCut : public GLGizmoBase bool m_rotate_lower; public: -#if ENABLE_SVG_ICONS GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoCut(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS protected: virtual bool on_init(); + virtual void on_load(cereal::BinaryInputArchive& ar) { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); } + virtual void on_save(cereal::BinaryOutputArchive& ar) const { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); } virtual std::string on_get_name() const; virtual void on_set_state(); - virtual bool on_is_activable(const Selection& selection) const; - virtual void on_start_dragging(const Selection& selection); - virtual void on_update(const UpdateData& data, const Selection& selection); - virtual void on_render(const Selection& selection) const; - virtual void on_render_for_picking(const Selection& selection) const; - virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection); + virtual bool on_is_activable() const; + virtual void on_start_dragging(); + virtual void on_update(const UpdateData& data); + virtual void on_render() const; + virtual void on_render_for_picking() const; + virtual void on_render_input_window(float x, float y, float bottom_limit); private: void update_max_z(const Selection& selection) const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index ec991a2413..9fae8893ac 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -1,5 +1,7 @@ // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. #include "GLGizmoFlatten.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/GUI_App.hpp" #include @@ -9,13 +11,8 @@ namespace Slic3r { namespace GUI { -#if ENABLE_SVG_ICONS GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) -#else -GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, unsigned int sprite_id) - : GLGizmoBase(parent, sprite_id) -#endif // ENABLE_SVG_ICONS , m_normal(Vec3d::Zero()) , m_starting_center(Vec3d::Zero()) { @@ -27,28 +24,46 @@ bool GLGizmoFlatten::on_init() return true; } +void GLGizmoFlatten::on_set_state() +{ + // m_model_object pointer can be invalid (for instance because of undo/redo action), + // we should recover it from the object id + m_model_object = nullptr; + for (const auto mo : wxGetApp().model().objects) { + if (mo->id() == m_model_object_id) { + m_model_object = mo; + break; + } + } + + if (m_state == On && is_plane_update_necessary()) + update_planes(); +} + std::string GLGizmoFlatten::on_get_name() const { return (_(L("Place on face")) + " [F]").ToUTF8().data(); } -bool GLGizmoFlatten::on_is_activable(const Selection& selection) const +bool GLGizmoFlatten::on_is_activable() const { - return selection.is_single_full_instance(); + return m_parent.get_selection().is_single_full_instance(); } -void GLGizmoFlatten::on_start_dragging(const Selection& selection) +void GLGizmoFlatten::on_start_dragging() { if (m_hover_id != -1) { assert(m_planes_valid); m_normal = m_planes[m_hover_id].normal; - m_starting_center = selection.get_bounding_box().center(); + m_starting_center = m_parent.get_selection().get_bounding_box().center(); } } -void GLGizmoFlatten::on_render(const Selection& selection) const +void GLGizmoFlatten::on_render() const { + const Selection& selection = m_parent.get_selection(); + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); glsafe(::glEnable(GL_DEPTH_TEST)); @@ -83,8 +98,10 @@ void GLGizmoFlatten::on_render(const Selection& selection) const glsafe(::glDisable(GL_BLEND)); } -void GLGizmoFlatten::on_render_for_picking(const Selection& selection) const +void GLGizmoFlatten::on_render_for_picking() const { + const Selection& selection = m_parent.get_selection(); + glsafe(::glDisable(GL_DEPTH_TEST)); glsafe(::glDisable(GL_BLEND)); @@ -98,7 +115,7 @@ void GLGizmoFlatten::on_render_for_picking(const Selection& selection) const const_cast(this)->update_planes(); for (int i = 0; i < (int)m_planes.size(); ++i) { - glsafe(::glColor3fv(picking_color_component(i).data())); + glsafe(::glColor4fv(picking_color_component(i).data())); ::glBegin(GL_POLYGON); for (const Vec3d& vertex : m_planes[i].vertices) { @@ -120,6 +137,7 @@ void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object) m_planes_valid = false; } m_model_object = model_object; + m_model_object_id = model_object ? model_object->id() : 0; } void GLGizmoFlatten::update_planes() diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp index 1bd17e5ef1..c69d641343 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp @@ -31,17 +31,14 @@ private: bool m_planes_valid = false; mutable Vec3d m_starting_center; const ModelObject* m_model_object = nullptr; + ObjectID m_model_object_id = 0; std::vector instances_matrices; void update_planes(); bool is_plane_update_necessary() const; public: -#if ENABLE_SVG_ICONS GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoFlatten(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS void set_flattening_data(const ModelObject* model_object); Vec3d get_flattening_normal() const; @@ -49,16 +46,11 @@ public: protected: virtual bool on_init(); virtual std::string on_get_name() const; - virtual bool on_is_activable(const Selection& selection) const; - virtual void on_start_dragging(const Selection& selection); - virtual void on_update(const UpdateData& data, const Selection& selection) {} - virtual void on_render(const Selection& selection) const; - virtual void on_render_for_picking(const Selection& selection) const; - virtual void on_set_state() - { - if (m_state == On && is_plane_update_necessary()) - update_planes(); - } + virtual bool on_is_activable() const; + virtual void on_start_dragging(); + virtual void on_render() const; + virtual void on_render_for_picking() const; + virtual void on_set_state() override; }; } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index a2ea738f5f..862ffe41af 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -1,5 +1,6 @@ // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. #include "GLGizmoMove.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" #include @@ -10,13 +11,8 @@ namespace GUI { const double GLGizmoMove3D::Offset = 10.0; -#if ENABLE_SVG_ICONS GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) -#else -GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, unsigned int sprite_id) - : GLGizmoBase(parent, sprite_id) -#endif // ENABLE_SVG_ICONS , m_displacement(Vec3d::Zero()) , m_snap_step(1.0) , m_starting_drag_position(Vec3d::Zero()) @@ -52,12 +48,12 @@ std::string GLGizmoMove3D::on_get_name() const return (_(L("Move")) + " [M]").ToUTF8().data(); } -void GLGizmoMove3D::on_start_dragging(const Selection& selection) +void GLGizmoMove3D::on_start_dragging() { if (m_hover_id != -1) { m_displacement = Vec3d::Zero(); - const BoundingBoxf3& box = selection.get_bounding_box(); + const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); m_starting_drag_position = m_grabbers[m_hover_id].center; m_starting_box_center = box.center(); m_starting_box_bottom_center = box.center(); @@ -70,7 +66,7 @@ void GLGizmoMove3D::on_stop_dragging() m_displacement = Vec3d::Zero(); } -void GLGizmoMove3D::on_update(const UpdateData& data, const Selection& selection) +void GLGizmoMove3D::on_update(const UpdateData& data) { if (m_hover_id == 0) m_displacement(0) = calc_projection(data); @@ -80,8 +76,10 @@ void GLGizmoMove3D::on_update(const UpdateData& data, const Selection& selection m_displacement(2) = calc_projection(data); } -void GLGizmoMove3D::on_render(const Selection& selection) const +void GLGizmoMove3D::on_render() const { + const Selection& selection = m_parent.get_selection(); + bool show_position = selection.is_single_full_instance(); const Vec3d& position = selection.get_bounding_box().center(); @@ -106,15 +104,15 @@ void GLGizmoMove3D::on_render(const Selection& selection) const // x axis m_grabbers[0].center = Vec3d(box.max(0) + Offset, center(1), center(2)); - ::memcpy((void*)m_grabbers[0].color, (const void*)&AXES_COLOR[0], 3 * sizeof(float)); + ::memcpy((void*)m_grabbers[0].color, (const void*)&AXES_COLOR[0], 4 * sizeof(float)); // y axis m_grabbers[1].center = Vec3d(center(0), box.max(1) + Offset, center(2)); - ::memcpy((void*)m_grabbers[1].color, (const void*)&AXES_COLOR[1], 3 * sizeof(float)); + ::memcpy((void*)m_grabbers[1].color, (const void*)&AXES_COLOR[1], 4 * sizeof(float)); // z axis m_grabbers[2].center = Vec3d(center(0), center(1), box.max(2) + Offset); - ::memcpy((void*)m_grabbers[2].color, (const void*)&AXES_COLOR[2], 3 * sizeof(float)); + ::memcpy((void*)m_grabbers[2].color, (const void*)&AXES_COLOR[2], 4 * sizeof(float)); glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); @@ -125,7 +123,7 @@ void GLGizmoMove3D::on_render(const Selection& selection) const { if (m_grabbers[i].enabled) { - glsafe(::glColor3fv(AXES_COLOR[i])); + glsafe(::glColor4fv(AXES_COLOR[i])); ::glBegin(GL_LINES); ::glVertex3dv(center.data()); ::glVertex3dv(m_grabbers[i].center.data()); @@ -144,7 +142,7 @@ void GLGizmoMove3D::on_render(const Selection& selection) const else { // draw axis - glsafe(::glColor3fv(AXES_COLOR[m_hover_id])); + glsafe(::glColor4fv(AXES_COLOR[m_hover_id])); ::glBegin(GL_LINES); ::glVertex3dv(center.data()); ::glVertex3dv(m_grabbers[m_hover_id].center.data()); @@ -157,20 +155,21 @@ void GLGizmoMove3D::on_render(const Selection& selection) const } } -void GLGizmoMove3D::on_render_for_picking(const Selection& selection) const +void GLGizmoMove3D::on_render_for_picking() const { glsafe(::glDisable(GL_DEPTH_TEST)); - const BoundingBoxf3& box = selection.get_bounding_box(); + const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); render_grabbers_for_picking(box); render_grabber_extension(X, box, true); render_grabber_extension(Y, box, true); render_grabber_extension(Z, box, true); } -void GLGizmoMove3D::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) -{ #if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI +void GLGizmoMove3D::on_render_input_window(float x, float y, float bottom_limit) +{ + const Selection& selection = m_parent.get_selection(); bool show_position = selection.is_single_full_instance(); const Vec3d& position = selection.get_bounding_box().center(); @@ -183,8 +182,8 @@ void GLGizmoMove3D::on_render_input_window(float x, float y, float bottom_limit, m_imgui->input_vec3("", displacement, 100.0f, "%.2f"); m_imgui->end(); -#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI } +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI double GLGizmoMove3D::calc_projection(const UpdateData& data) const { @@ -221,19 +220,20 @@ void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box float mean_size = (float)((box.size()(0) + box.size()(1) + box.size()(2)) / 3.0); double size = m_dragging ? (double)m_grabbers[axis].get_dragging_half_size(mean_size) : (double)m_grabbers[axis].get_half_size(mean_size); - float color[3]; - ::memcpy((void*)color, (const void*)m_grabbers[axis].color, 3 * sizeof(float)); + float color[4]; + ::memcpy((void*)color, (const void*)m_grabbers[axis].color, 4 * sizeof(float)); if (!picking && (m_hover_id != -1)) { color[0] = 1.0f - color[0]; color[1] = 1.0f - color[1]; color[2] = 1.0f - color[2]; + color[3] = color[3]; } if (!picking) glsafe(::glEnable(GL_LIGHTING)); - glsafe(::glColor3fv(color)); + glsafe(::glColor4fv(color)); glsafe(::glPushMatrix()); glsafe(::glTranslated(m_grabbers[axis].center(0), m_grabbers[axis].center(1), m_grabbers[axis].center(2))); if (axis == X) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index ddab2b777d..21b1d397be 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -22,11 +22,7 @@ class GLGizmoMove3D : public GLGizmoBase GLUquadricObj* m_quadric; public: -#if ENABLE_SVG_ICONS GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoMove3D(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS virtual ~GLGizmoMove3D(); double get_snap_step(double step) const { return m_snap_step; } @@ -37,12 +33,14 @@ public: protected: virtual bool on_init(); virtual std::string on_get_name() const; - virtual void on_start_dragging(const Selection& selection); + virtual void on_start_dragging(); virtual void on_stop_dragging(); - virtual void on_update(const UpdateData& data, const Selection& selection); - virtual void on_render(const Selection& selection) const; - virtual void on_render_for_picking(const Selection& selection) const; - virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection); + virtual void on_update(const UpdateData& data); + virtual void on_render() const; + virtual void on_render_for_picking() const; +#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI + virtual void on_render_input_window(float x, float y, float bottom_limit); +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI private: double calc_projection(const UpdateData& data) const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 68b9a8c332..9a2c72633e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -1,5 +1,6 @@ // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. #include "GLGizmoRotate.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" #include @@ -19,11 +20,7 @@ const unsigned int GLGizmoRotate::SnapRegionsCount = 8; const float GLGizmoRotate::GrabberOffset = 0.15f; // in percent of radius GLGizmoRotate::GLGizmoRotate(GLCanvas3D& parent, GLGizmoRotate::Axis axis) -#if ENABLE_SVG_ICONS : GLGizmoBase(parent, "", -1) -#else - : GLGizmoBase(parent, -1) -#endif // ENABLE_SVG_ICONS , m_axis(axis) , m_angle(0.0) , m_quadric(nullptr) @@ -40,11 +37,7 @@ GLGizmoRotate::GLGizmoRotate(GLCanvas3D& parent, GLGizmoRotate::Axis axis) } GLGizmoRotate::GLGizmoRotate(const GLGizmoRotate& other) -#if ENABLE_SVG_ICONS : GLGizmoBase(other.m_parent, other.m_icon_filename, other.m_sprite_id) -#else - : GLGizmoBase(other.m_parent, other.m_sprite_id) -#endif // ENABLE_SVG_ICONS , m_axis(other.m_axis) , m_angle(other.m_angle) , m_quadric(nullptr) @@ -80,9 +73,9 @@ bool GLGizmoRotate::on_init() return true; } -void GLGizmoRotate::on_start_dragging(const Selection& selection) +void GLGizmoRotate::on_start_dragging() { - const BoundingBoxf3& box = selection.get_bounding_box(); + const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); m_center = box.center(); m_radius = Offset + box.radius(); m_snap_coarse_in_radius = m_radius / 3.0f; @@ -91,9 +84,9 @@ void GLGizmoRotate::on_start_dragging(const Selection& selection) m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth; } -void GLGizmoRotate::on_update(const UpdateData& data, const Selection& selection) +void GLGizmoRotate::on_update(const UpdateData& data) { - Vec2d mouse_pos = to_2d(mouse_position_in_local_plane(data.mouse_ray, selection)); + Vec2d mouse_pos = to_2d(mouse_position_in_local_plane(data.mouse_ray, m_parent.get_selection())); Vec2d orig_dir = Vec2d::UnitX(); Vec2d new_dir = mouse_pos.normalized(); @@ -126,11 +119,12 @@ void GLGizmoRotate::on_update(const UpdateData& data, const Selection& selection m_angle = theta; } -void GLGizmoRotate::on_render(const Selection& selection) const +void GLGizmoRotate::on_render() const { if (!m_grabbers[0].enabled) return; + const Selection& selection = m_parent.get_selection(); const BoundingBoxf3& box = selection.get_bounding_box(); std::string axis; @@ -161,7 +155,7 @@ void GLGizmoRotate::on_render(const Selection& selection) const transform_to_local(selection); glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); - glsafe(::glColor3fv((m_hover_id != -1) ? m_drag_color : m_highlight_color)); + glsafe(::glColor4fv((m_hover_id != -1) ? m_drag_color : m_highlight_color)); render_circle(); @@ -172,7 +166,7 @@ void GLGizmoRotate::on_render(const Selection& selection) const render_reference_radius(); } - glsafe(::glColor3fv(m_highlight_color)); + glsafe(::glColor4fv(m_highlight_color)); if (m_hover_id != -1) render_angle(); @@ -183,8 +177,10 @@ void GLGizmoRotate::on_render(const Selection& selection) const glsafe(::glPopMatrix()); } -void GLGizmoRotate::on_render_for_picking(const Selection& selection) const +void GLGizmoRotate::on_render_for_picking() const { + const Selection& selection = m_parent.get_selection(); + glsafe(::glDisable(GL_DEPTH_TEST)); glsafe(::glPushMatrix()); @@ -291,14 +287,14 @@ void GLGizmoRotate::render_grabber(const BoundingBoxf3& box) const m_grabbers[0].center = Vec3d(::cos(m_angle) * grabber_radius, ::sin(m_angle) * grabber_radius, 0.0); m_grabbers[0].angles(2) = m_angle; - glsafe(::glColor3fv((m_hover_id != -1) ? m_drag_color : m_highlight_color)); + glsafe(::glColor4fv((m_hover_id != -1) ? m_drag_color : m_highlight_color)); ::glBegin(GL_LINES); ::glVertex3f(0.0f, 0.0f, 0.0f); ::glVertex3dv(m_grabbers[0].center.data()); glsafe(::glEnd()); - ::memcpy((void*)m_grabbers[0].color, (const void*)m_highlight_color, 3 * sizeof(float)); + ::memcpy((void*)m_grabbers[0].color, (const void*)m_highlight_color, 4 * sizeof(float)); render_grabbers(box); } @@ -310,8 +306,8 @@ void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool pick float mean_size = (float)((box.size()(0) + box.size()(1) + box.size()(2)) / 3.0); double size = m_dragging ? (double)m_grabbers[0].get_dragging_half_size(mean_size) : (double)m_grabbers[0].get_half_size(mean_size); - float color[3]; - ::memcpy((void*)color, (const void*)m_grabbers[0].color, 3 * sizeof(float)); + float color[4]; + ::memcpy((void*)color, (const void*)m_grabbers[0].color, 4 * sizeof(float)); if (!picking && (m_hover_id != -1)) { color[0] = 1.0f - color[0]; @@ -322,7 +318,7 @@ void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool pick if (!picking) glsafe(::glEnable(GL_LIGHTING)); - glsafe(::glColor3fv(color)); + glsafe(::glColor4fv(color)); glsafe(::glPushMatrix()); glsafe(::glTranslated(m_grabbers[0].center(0), m_grabbers[0].center(1), m_grabbers[0].center(2))); glsafe(::glRotated(Geometry::rad2deg(m_angle), 0.0, 0.0, 1.0)); @@ -417,13 +413,8 @@ Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray, cons return transform(mouse_ray, m).intersect_plane(0.0); } -#if ENABLE_SVG_ICONS GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) -#else -GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, unsigned int sprite_id) - : GLGizmoBase(parent, sprite_id) -#endif // ENABLE_SVG_ICONS { m_gizmos.emplace_back(parent, GLGizmoRotate::X); m_gizmos.emplace_back(parent, GLGizmoRotate::Y); @@ -458,10 +449,10 @@ std::string GLGizmoRotate3D::on_get_name() const return (_(L("Rotate")) + " [R]").ToUTF8().data(); } -void GLGizmoRotate3D::on_start_dragging(const Selection& selection) +void GLGizmoRotate3D::on_start_dragging() { if ((0 <= m_hover_id) && (m_hover_id < 3)) - m_gizmos[m_hover_id].start_dragging(selection); + m_gizmos[m_hover_id].start_dragging(); } void GLGizmoRotate3D::on_stop_dragging() @@ -470,23 +461,23 @@ void GLGizmoRotate3D::on_stop_dragging() m_gizmos[m_hover_id].stop_dragging(); } -void GLGizmoRotate3D::on_render(const Selection& selection) const +void GLGizmoRotate3D::on_render() const { glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); if ((m_hover_id == -1) || (m_hover_id == 0)) - m_gizmos[X].render(selection); + m_gizmos[X].render(); if ((m_hover_id == -1) || (m_hover_id == 1)) - m_gizmos[Y].render(selection); + m_gizmos[Y].render(); if ((m_hover_id == -1) || (m_hover_id == 2)) - m_gizmos[Z].render(selection); + m_gizmos[Z].render(); } -void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) -{ #if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI +void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limit) +{ Vec3d rotation(Geometry::rad2deg(m_gizmos[0].get_angle()), Geometry::rad2deg(m_gizmos[1].get_angle()), Geometry::rad2deg(m_gizmos[2].get_angle())); wxString label = _(L("Rotation (deg)")); @@ -495,8 +486,8 @@ void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limi m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); m_imgui->input_vec3("", rotation, 100.0f, "%.2f"); m_imgui->end(); -#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI } +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index c65dee4d89..8733c9a118 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -52,10 +52,10 @@ public: protected: virtual bool on_init(); virtual std::string on_get_name() const { return ""; } - virtual void on_start_dragging(const Selection& selection); - virtual void on_update(const UpdateData& data, const Selection& selection); - virtual void on_render(const Selection& selection) const; - virtual void on_render_for_picking(const Selection& selection) const; + virtual void on_start_dragging(); + virtual void on_update(const UpdateData& data); + virtual void on_render() const; + virtual void on_render_for_picking() const; private: void render_circle() const; @@ -76,11 +76,7 @@ class GLGizmoRotate3D : public GLGizmoBase std::vector m_gizmos; public: -#if ENABLE_SVG_ICONS GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoRotate3D(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); } void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation(0)); m_gizmos[Y].set_angle(rotation(1)); m_gizmos[Z].set_angle(rotation(2)); } @@ -97,41 +93,41 @@ protected: } virtual void on_set_hover_id() { - for (unsigned int i = 0; i < 3; ++i) + for (int i = 0; i < 3; ++i) { m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1); } } - virtual bool on_is_activable(const Selection& selection) const { return true; } virtual void on_enable_grabber(unsigned int id) { - if ((0 <= id) && (id < 3)) + if (id < 3) m_gizmos[id].enable_grabber(0); } virtual void on_disable_grabber(unsigned int id) { - if ((0 <= id) && (id < 3)) + if (id < 3) m_gizmos[id].disable_grabber(0); } - virtual void on_start_dragging(const Selection& selection); + virtual void on_start_dragging(); virtual void on_stop_dragging(); - virtual void on_update(const UpdateData& data, const Selection& selection) + virtual void on_update(const UpdateData& data) { for (GLGizmoRotate& g : m_gizmos) { - g.update(data, selection); + g.update(data); } } - virtual void on_render(const Selection& selection) const; - virtual void on_render_for_picking(const Selection& selection) const + virtual void on_render() const; + virtual void on_render_for_picking() const { for (const GLGizmoRotate& g : m_gizmos) { - g.render_for_picking(selection); + g.render_for_picking(); } } - - virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection); +#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI + virtual void on_render_input_window(float x, float y, float bottom_limit); +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index e8027c871d..bf540cb00c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -1,7 +1,6 @@ - - // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. #include "GLGizmoScale.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" #include @@ -13,13 +12,8 @@ namespace GUI { const float GLGizmoScale3D::Offset = 5.0f; -#if ENABLE_SVG_ICONS GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) -#else -GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, unsigned int sprite_id) - : GLGizmoBase(parent, sprite_id) -#endif // ENABLE_SVG_ICONS , m_scale(Vec3d::Ones()) , m_offset(Vec3d::Zero()) , m_snap_step(0.05) @@ -53,13 +47,18 @@ std::string GLGizmoScale3D::on_get_name() const return (_(L("Scale")) + " [S]").ToUTF8().data(); } -void GLGizmoScale3D::on_start_dragging(const Selection& selection) +bool GLGizmoScale3D::on_is_activable() const +{ + return !m_parent.get_selection().is_wipe_tower(); +} + +void GLGizmoScale3D::on_start_dragging() { if (m_hover_id != -1) { m_starting.drag_position = m_grabbers[m_hover_id].center; m_starting.ctrl_down = wxGetKeyState(WXK_CONTROL); - m_starting.box = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_box : selection.get_bounding_box(); + m_starting.box = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_box : m_parent.get_selection().get_bounding_box(); const Vec3d& center = m_starting.box.center(); m_starting.pivots[0] = m_transform * Vec3d(m_starting.box.max(0), center(1), center(2)); @@ -71,7 +70,7 @@ void GLGizmoScale3D::on_start_dragging(const Selection& selection) } } -void GLGizmoScale3D::on_update(const UpdateData& data, const Selection& selection) +void GLGizmoScale3D::on_update(const UpdateData& data) { if ((m_hover_id == 0) || (m_hover_id == 1)) do_scale_along_axis(X, data); @@ -83,8 +82,10 @@ void GLGizmoScale3D::on_update(const UpdateData& data, const Selection& selectio do_scale_uniform(data); } -void GLGizmoScale3D::on_render(const Selection& selection) const +void GLGizmoScale3D::on_render() const { + const Selection& selection = m_parent.get_selection(); + bool single_instance = selection.is_single_full_instance(); bool single_volume = selection.is_single_modifier() || selection.is_single_volume(); bool single_selection = single_instance || single_volume; @@ -136,7 +137,7 @@ void GLGizmoScale3D::on_render(const Selection& selection) const for (unsigned int idx : idxs) { const GLVolume* vol = selection.get_volume(idx); - m_box.merge(vol->bounding_box.transformed(vol->get_volume_transformation().get_matrix())); + m_box.merge(vol->bounding_box().transformed(vol->get_volume_transformation().get_matrix())); } // gets transform from first selected volume @@ -151,7 +152,7 @@ void GLGizmoScale3D::on_render(const Selection& selection) const else if (single_volume) { const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); - m_box = v->bounding_box; + m_box = v->bounding_box(); m_transform = v->world_matrix(); angles = Geometry::extract_euler_angles(m_transform); // consider rotation+mirror only components of the transform for offsets @@ -171,20 +172,20 @@ void GLGizmoScale3D::on_render(const Selection& selection) const // x axis m_grabbers[0].center = m_transform * Vec3d(m_box.min(0), center(1), center(2)) - offset_x; m_grabbers[1].center = m_transform * Vec3d(m_box.max(0), center(1), center(2)) + offset_x; - ::memcpy((void*)m_grabbers[0].color, (ctrl_down && (m_hover_id == 1)) ? (const void*)CONSTRAINED_COLOR : (const void*)&AXES_COLOR[0], 3 * sizeof(float)); - ::memcpy((void*)m_grabbers[1].color, (ctrl_down && (m_hover_id == 0)) ? (const void*)CONSTRAINED_COLOR : (const void*)&AXES_COLOR[0], 3 * sizeof(float)); + ::memcpy((void*)m_grabbers[0].color, (ctrl_down && (m_hover_id == 1)) ? (const void*)CONSTRAINED_COLOR : (const void*)&AXES_COLOR[0], 4 * sizeof(float)); + ::memcpy((void*)m_grabbers[1].color, (ctrl_down && (m_hover_id == 0)) ? (const void*)CONSTRAINED_COLOR : (const void*)&AXES_COLOR[0], 4 * sizeof(float)); // y axis m_grabbers[2].center = m_transform * Vec3d(center(0), m_box.min(1), center(2)) - offset_y; m_grabbers[3].center = m_transform * Vec3d(center(0), m_box.max(1), center(2)) + offset_y; - ::memcpy((void*)m_grabbers[2].color, (ctrl_down && (m_hover_id == 3)) ? (const void*)CONSTRAINED_COLOR : (const void*)&AXES_COLOR[1], 3 * sizeof(float)); - ::memcpy((void*)m_grabbers[3].color, (ctrl_down && (m_hover_id == 2)) ? (const void*)CONSTRAINED_COLOR : (const void*)&AXES_COLOR[1], 3 * sizeof(float)); + ::memcpy((void*)m_grabbers[2].color, (ctrl_down && (m_hover_id == 3)) ? (const void*)CONSTRAINED_COLOR : (const void*)&AXES_COLOR[1], 4 * sizeof(float)); + ::memcpy((void*)m_grabbers[3].color, (ctrl_down && (m_hover_id == 2)) ? (const void*)CONSTRAINED_COLOR : (const void*)&AXES_COLOR[1], 4 * sizeof(float)); // z axis m_grabbers[4].center = m_transform * Vec3d(center(0), center(1), m_box.min(2)) - offset_z; m_grabbers[5].center = m_transform * Vec3d(center(0), center(1), m_box.max(2)) + offset_z; - ::memcpy((void*)m_grabbers[4].color, (ctrl_down && (m_hover_id == 5)) ? (const void*)CONSTRAINED_COLOR : (const void*)&AXES_COLOR[2], 3 * sizeof(float)); - ::memcpy((void*)m_grabbers[5].color, (ctrl_down && (m_hover_id == 4)) ? (const void*)CONSTRAINED_COLOR : (const void*)&AXES_COLOR[2], 3 * sizeof(float)); + ::memcpy((void*)m_grabbers[4].color, (ctrl_down && (m_hover_id == 5)) ? (const void*)CONSTRAINED_COLOR : (const void*)&AXES_COLOR[2], 4 * sizeof(float)); + ::memcpy((void*)m_grabbers[5].color, (ctrl_down && (m_hover_id == 4)) ? (const void*)CONSTRAINED_COLOR : (const void*)&AXES_COLOR[2], 4 * sizeof(float)); // uniform m_grabbers[6].center = m_transform * Vec3d(m_box.min(0), m_box.min(1), center(2)) - offset_x - offset_y; @@ -193,7 +194,7 @@ void GLGizmoScale3D::on_render(const Selection& selection) const m_grabbers[9].center = m_transform * Vec3d(m_box.min(0), m_box.max(1), center(2)) - offset_x + offset_y; for (int i = 6; i < 10; ++i) { - ::memcpy((void*)m_grabbers[i].color, (const void*)m_highlight_color, 3 * sizeof(float)); + ::memcpy((void*)m_grabbers[i].color, (const void*)m_highlight_color, 4 * sizeof(float)); } // sets grabbers orientation @@ -213,20 +214,20 @@ void GLGizmoScale3D::on_render(const Selection& selection) const // draw connections if (m_grabbers[0].enabled && m_grabbers[1].enabled) { - glsafe(::glColor3fv(m_grabbers[0].color)); + glsafe(::glColor4fv(m_grabbers[0].color)); render_grabbers_connection(0, 1); } if (m_grabbers[2].enabled && m_grabbers[3].enabled) { - glsafe(::glColor3fv(m_grabbers[2].color)); + glsafe(::glColor4fv(m_grabbers[2].color)); render_grabbers_connection(2, 3); } if (m_grabbers[4].enabled && m_grabbers[5].enabled) { - glsafe(::glColor3fv(m_grabbers[4].color)); + glsafe(::glColor4fv(m_grabbers[4].color)); render_grabbers_connection(4, 5); } - glsafe(::glColor3fv(m_base_color)); + glsafe(::glColor4fv(m_base_color)); render_grabbers_connection(6, 7); render_grabbers_connection(7, 8); render_grabbers_connection(8, 9); @@ -237,7 +238,7 @@ void GLGizmoScale3D::on_render(const Selection& selection) const else if ((m_hover_id == 0) || (m_hover_id == 1)) { // draw connection - glsafe(::glColor3fv(m_grabbers[0].color)); + glsafe(::glColor4fv(m_grabbers[0].color)); render_grabbers_connection(0, 1); // draw grabbers m_grabbers[0].render(true, grabber_mean_size); @@ -246,7 +247,7 @@ void GLGizmoScale3D::on_render(const Selection& selection) const else if ((m_hover_id == 2) || (m_hover_id == 3)) { // draw connection - glsafe(::glColor3fv(m_grabbers[2].color)); + glsafe(::glColor4fv(m_grabbers[2].color)); render_grabbers_connection(2, 3); // draw grabbers m_grabbers[2].render(true, grabber_mean_size); @@ -255,7 +256,7 @@ void GLGizmoScale3D::on_render(const Selection& selection) const else if ((m_hover_id == 4) || (m_hover_id == 5)) { // draw connection - glsafe(::glColor3fv(m_grabbers[4].color)); + glsafe(::glColor4fv(m_grabbers[4].color)); render_grabbers_connection(4, 5); // draw grabbers m_grabbers[4].render(true, grabber_mean_size); @@ -264,7 +265,7 @@ void GLGizmoScale3D::on_render(const Selection& selection) const else if (m_hover_id >= 6) { // draw connection - glsafe(::glColor3fv(m_drag_color)); + glsafe(::glColor4fv(m_drag_color)); render_grabbers_connection(6, 7); render_grabbers_connection(7, 8); render_grabbers_connection(8, 9); @@ -277,16 +278,16 @@ void GLGizmoScale3D::on_render(const Selection& selection) const } } -void GLGizmoScale3D::on_render_for_picking(const Selection& selection) const +void GLGizmoScale3D::on_render_for_picking() const { glsafe(::glDisable(GL_DEPTH_TEST)); - - render_grabbers_for_picking(selection.get_bounding_box()); + render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); } -void GLGizmoScale3D::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) -{ #if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI +void GLGizmoScale3D::on_render_input_window(float x, float y, float bottom_limit) +{ + const Selection& selection = m_parent.get_selection(); bool single_instance = selection.is_single_full_instance(); wxString label = _(L("Scale (%)")); @@ -295,8 +296,8 @@ void GLGizmoScale3D::on_render_input_window(float x, float y, float bottom_limit m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); m_imgui->input_vec3("", m_scale * 100.f, 100.0f, "%.2f"); m_imgui->end(); -#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI } +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2) const { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 3b0717f04f..7b77fe5599 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -32,11 +32,7 @@ class GLGizmoScale3D : public GLGizmoBase StartingData m_starting; public: -#if ENABLE_SVG_ICONS GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoScale3D(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS double get_snap_step(double step) const { return m_snap_step; } void set_snap_step(double step) { m_snap_step = step; } @@ -49,12 +45,14 @@ public: protected: virtual bool on_init(); virtual std::string on_get_name() const; - virtual bool on_is_activable(const Selection& selection) const { return !selection.is_wipe_tower(); } - virtual void on_start_dragging(const Selection& selection); - virtual void on_update(const UpdateData& data, const Selection& selection); - virtual void on_render(const Selection& selection) const; - virtual void on_render_for_picking(const Selection& selection) const; - virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection); + virtual bool on_is_activable() const; + virtual void on_start_dragging(); + virtual void on_update(const UpdateData& data); + virtual void on_render() const; + virtual void on_render_for_picking() const; +#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI + virtual void on_render_input_window(float x, float y, float bottom_limit); +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI private: void render_grabbers_connection(unsigned int id_1, unsigned int id_2) const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index d118a6877f..693c3c074d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -19,14 +19,10 @@ namespace Slic3r { namespace GUI { -#if ENABLE_SVG_ICONS GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) -#else -GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, unsigned int sprite_id) - : GLGizmoBase(parent, sprite_id) -#endif // ENABLE_SVG_ICONS , m_quadric(nullptr) + , m_its(nullptr) { m_quadric = ::gluNewQuadric(); if (m_quadric != nullptr) @@ -63,15 +59,16 @@ bool GLGizmoSlaSupports::on_init() void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const Selection& selection) { - if (selection.is_empty()) { + if (! model_object || selection.is_empty()) { m_model_object = nullptr; return; } - if (m_model_object != model_object) + if (m_model_object != model_object || m_model_object_id != model_object->id()) { + m_model_object = model_object; m_print_object_idx = -1; + } - m_model_object = model_object; m_active_instance = selection.get_instance_idx(); if (model_object && selection.is_from_single_instance()) @@ -83,10 +80,11 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S if (is_mesh_update_necessary()) { update_mesh(); - editing_mode_reload_cache(); + reload_cache(); } - if (m_editing_mode_cache.empty() && m_model_object->sla_points_status != sla::PointsStatus::UserModified) + // If we triggered autogeneration before, check backend and fetch results if they are there + if (m_model_object->sla_points_status == sla::PointsStatus::Generating) get_data_from_backend(); if (m_state == On) { @@ -98,16 +96,24 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S } } -void GLGizmoSlaSupports::on_render(const Selection& selection) const + + +void GLGizmoSlaSupports::on_render() const { + const Selection& selection = m_parent.get_selection(); + // If current m_model_object does not match selection, ask GLCanvas3D to turn us off if (m_state == On && (m_model_object != selection.get_model()->objects[selection.get_object_idx()] - || m_active_instance != selection.get_instance_idx())) { + || m_active_instance != selection.get_instance_idx() + || m_model_object_id != m_model_object->id())) { m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS)); return; } + if (! m_its || ! m_mesh) + const_cast(this)->update_mesh(); + glsafe(::glEnable(GL_BLEND)); glsafe(::glEnable(GL_DEPTH_TEST)); @@ -256,8 +262,13 @@ void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection) const } -void GLGizmoSlaSupports::on_render_for_picking(const Selection& selection) const +void GLGizmoSlaSupports::on_render_for_picking() const { + const Selection& selection = m_parent.get_selection(); +#if ENABLE_RENDER_PICKING_PASS + m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z(); +#endif + glsafe(::glEnable(GL_DEPTH_TEST)); render_points(selection, true); } @@ -275,30 +286,33 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) glsafe(::glTranslated(0.0, 0.0, m_z_shift)); glsafe(::glMultMatrixd(instance_matrix.data())); - float render_color[3]; - for (int i = 0; i < (int)m_editing_mode_cache.size(); ++i) + float render_color[4]; + size_t cache_size = m_editing_mode ? m_editing_cache.size() : m_normal_cache.size(); + for (size_t i = 0; i < cache_size; ++i) { - const sla::SupportPoint& support_point = m_editing_mode_cache[i].support_point; - const bool& point_selected = m_editing_mode_cache[i].selected; + const sla::SupportPoint& support_point = m_editing_mode ? m_editing_cache[i].support_point : m_normal_cache[i]; + const bool& point_selected = m_editing_mode ? m_editing_cache[i].selected : false; if (is_point_clipped(support_point.pos.cast())) continue; // First decide about the color of the point. if (picking) { - std::array color = picking_color_component(i); + std::array color = picking_color_component(i); render_color[0] = color[0]; render_color[1] = color[1]; render_color[2] = color[2]; + render_color[3] = color[3]; } else { + render_color[3] = 1.f; if ((m_hover_id == i && m_editing_mode)) { // ignore hover state unless editing mode is active render_color[0] = 0.f; render_color[1] = 1.0f; render_color[2] = 1.0f; } else { // neigher hover nor picking - bool supports_new_island = m_lock_unique_islands && m_editing_mode_cache[i].support_point.is_new_island; + bool supports_new_island = m_lock_unique_islands && support_point.is_new_island; if (m_editing_mode) { render_color[0] = point_selected ? 1.0f : (supports_new_island ? 0.3f : 0.7f); render_color[1] = point_selected ? 0.3f : (supports_new_island ? 0.3f : 0.7f); @@ -308,7 +322,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) for (unsigned char i=0; i<3; ++i) render_color[i] = 0.5f; } } - glsafe(::glColor3fv(render_color)); + glsafe(::glColor4fv(render_color)); float render_color_emissive[4] = { 0.5f * render_color[0], 0.5f * render_color[1], 0.5f * render_color[2], 1.f}; glsafe(::glMaterialfv(GL_FRONT, GL_EMISSION, render_color_emissive)); @@ -323,24 +337,24 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) // Matrices set, we can render the point mark now. // If in editing mode, we'll also render a cone pointing to the sphere. if (m_editing_mode) { - if (m_editing_mode_cache[i].normal == Vec3f::Zero()) + if (m_editing_cache[i].normal == Vec3f::Zero()) update_cache_entry_normal(i); // in case the normal is not yet cached, find and cache it Eigen::Quaterniond q; - q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * m_editing_mode_cache[i].normal.cast()); + q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast()); Eigen::AngleAxisd aa(q); glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2))); const float cone_radius = 0.25f; // mm const float cone_height = 0.75f; glsafe(::glPushMatrix()); - glsafe(::glTranslatef(0.f, 0.f, m_editing_mode_cache[i].support_point.head_front_radius * RenderPointScale)); + glsafe(::glTranslatef(0.f, 0.f, support_point.head_front_radius * RenderPointScale)); ::gluCylinder(m_quadric, 0.f, cone_radius, cone_height, 24, 1); glsafe(::glTranslatef(0.f, 0.f, cone_height)); ::gluDisk(m_quadric, 0.0, cone_radius, 24, 1); glsafe(::glPopMatrix()); } - ::gluSphere(m_quadric, m_editing_mode_cache[i].support_point.head_front_radius * RenderPointScale, 24, 12); + ::gluSphere(m_quadric, support_point.head_front_radius * RenderPointScale, 24, 12); if (vol->is_left_handed()) glFrontFace(GL_CCW); @@ -379,44 +393,43 @@ bool GLGizmoSlaSupports::is_point_clipped(const Vec3d& point) const bool GLGizmoSlaSupports::is_mesh_update_necessary() const { return ((m_state == On) && (m_model_object != nullptr) && !m_model_object->instances.empty()) - && ((m_model_object->id() != m_current_mesh_model_id) || m_V.size()==0); + && ((m_model_object->id() != m_model_object_id) || m_its == nullptr); } + + void GLGizmoSlaSupports::update_mesh() { + if (! m_model_object) + return; + wxBusyCursor wait; - Eigen::MatrixXf& V = m_V; - Eigen::MatrixXi& F = m_F; - // We rely on SLA model object having a single volume, // this way we can use that mesh directly. // This mesh does not account for the possible Z up SLA offset. - m_mesh = &m_model_object->volumes.front()->mesh; - const_cast(m_mesh)->require_shared_vertices(); // TriangleMeshSlicer needs this - const stl_file& stl = m_mesh->stl; - V.resize(3 * stl.stats.number_of_facets, 3); - F.resize(stl.stats.number_of_facets, 3); - for (unsigned int i=0; ivertex[0](0); V(3*i+0, 1) = facet->vertex[0](1); V(3*i+0, 2) = facet->vertex[0](2); - V(3*i+1, 0) = facet->vertex[1](0); V(3*i+1, 1) = facet->vertex[1](1); V(3*i+1, 2) = facet->vertex[1](2); - V(3*i+2, 0) = facet->vertex[2](0); V(3*i+2, 1) = facet->vertex[2](1); V(3*i+2, 2) = facet->vertex[2](2); - F(i, 0) = 3*i+0; - F(i, 1) = 3*i+1; - F(i, 2) = 3*i+2; - } - m_current_mesh_model_id = m_model_object->id(); - m_editing_mode = false; + m_mesh = &m_model_object->volumes.front()->mesh(); + m_its = &m_mesh->its; - m_AABB = igl::AABB(); - m_AABB.init(m_V, m_F); + // If this is different mesh than last time or if the AABB tree is uninitialized, recalculate it. + if (m_model_object_id != m_model_object->id() || (m_AABB.m_left == NULL && m_AABB.m_right == NULL)) + { + m_AABB.deinit(); + m_AABB.init( + MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3), + MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3)); + } + + m_model_object_id = m_model_object->id(); + disable_editing_mode(); } -// Unprojects the mouse position on the mesh and return the hit point and normal of the facet. -// The function throws if no intersection if found. -std::pair GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) + + +// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal +// Return false if no intersection was found, true otherwise. +bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal) { // if the gizmo doesn't have the V, F structures for igl, calculate them first: - if (m_V.size() == 0) + if (m_its == nullptr) update_mesh(); const Camera& camera = m_parent.get_camera(); @@ -442,8 +455,11 @@ std::pair GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse point1 = inv * point1; point2 = inv * point2; - if (!m_AABB.intersect_ray(m_V, m_F, point1.cast(), (point2-point1).cast(), hits)) - throw std::invalid_argument("unproject_on_mesh(): No intersection found."); + if (!m_AABB.intersect_ray( + MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3), + MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3), + point1.cast(), (point2-point1).cast(), hits)) + return false; // no intersection found std::sort(hits.begin(), hits.end(), [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; }); @@ -457,9 +473,9 @@ std::pair GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse igl::Hit& hit = hits[i]; int fid = hit.id; // facet id bc = Vec3f(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit - a = (m_V.row(m_F(fid, 1)) - m_V.row(m_F(fid, 0))); - b = (m_V.row(m_F(fid, 2)) - m_V.row(m_F(fid, 0))); - result = bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2)); + a = (m_its->vertices[m_its->indices[fid](1)] - m_its->vertices[m_its->indices[fid](0)]); + b = (m_its->vertices[m_its->indices[fid](2)] - m_its->vertices[m_its->indices[fid](0)]); + result = bc(0) * m_its->vertices[m_its->indices[fid](0)] + bc(1) * m_its->vertices[m_its->indices[fid](1)] + bc(2)*m_its->vertices[m_its->indices[fid](2)]; if (m_clipping_plane_distance == 0.f || !is_point_clipped(result.cast())) break; } @@ -467,14 +483,12 @@ std::pair GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse if (i==hits.size() || (hits.size()-i) % 2 != 0) { // All hits are either clipped, or there is an odd number of unclipped // hits - meaning the nearest must be from inside the mesh. - throw std::invalid_argument("unproject_on_mesh(): No intersection found."); + return false; } // Calculate and return both the point and the facet normal. - return std::make_pair( - result, - a.cross(b) - ); + pos_and_normal = std::make_pair(result, a.cross(b)); + return true; } // Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. @@ -493,7 +507,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous } } else { - if (m_editing_mode_cache[m_hover_id].selected) + if (m_editing_cache[m_hover_id].selected) unselect_point(m_hover_id); else { if (!alt_down) @@ -512,16 +526,15 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous // If there is some selection, don't add new point and deselect everything instead. if (m_selection_empty) { - try { - std::pair pos_and_normal = unproject_on_mesh(mouse_position); // don't create anything if this throws - m_editing_mode_cache.emplace_back(sla::SupportPoint(pos_and_normal.first, m_new_point_head_diameter/2.f, false), false, pos_and_normal.second); - m_unsaved_changes = true; + std::pair pos_and_normal; + if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add support point"))); + m_editing_cache.emplace_back(sla::SupportPoint(pos_and_normal.first, m_new_point_head_diameter/2.f, false), false, pos_and_normal.second); m_parent.set_as_dirty(); m_wait_for_up_event = true; } - catch (...) { // not clicked on object + else return false; - } } else select_point(NoPoints); @@ -537,8 +550,8 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous // First collect positions of all the points in world coordinates. const Transform3d& instance_matrix = m_model_object->instances[m_active_instance]->get_transformation().get_matrix(); std::vector points; - for (unsigned int i=0; i()); points.back()(2) += m_z_shift; } @@ -550,7 +563,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous const Selection& selection = m_parent.get_selection(); const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); const Transform3d& instance_matrix_no_translation_no_scaling = volume->get_instance_transformation().get_matrix(true,false,true); - Vec3f direction_to_camera = camera.get_dir_forward().cast(); + Vec3f direction_to_camera = -camera.get_dir_forward().cast(); Vec3f direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse().cast() * direction_to_camera).normalized().eval(); Vec3f scaling = volume->get_instance_scaling_factor().cast(); direction_to_camera_mesh = Vec3f(direction_to_camera_mesh(0)*scaling(0), direction_to_camera_mesh(1)*scaling(1), direction_to_camera_mesh(2)*scaling(2)); @@ -558,21 +571,24 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous // Iterate over all points in the rectangle and check that they are neither clipped by the // clipping plane nor obscured by the mesh. for (const unsigned int i : selected_idxs) { - const sla::SupportPoint &support_point = m_editing_mode_cache[i].support_point; + const sla::SupportPoint &support_point = m_editing_cache[i].support_point; if (!is_point_clipped(support_point.pos.cast())) { bool is_obscured = false; // Cast a ray in the direction of the camera and look for intersection with the mesh: std::vector hits; // Offset the start of the ray to the front of the ball + EPSILON to account for numerical inaccuracies. - if (m_AABB.intersect_ray(m_V, m_F, support_point.pos + direction_to_camera_mesh * (support_point.head_front_radius + EPSILON), direction_to_camera_mesh, hits)) { + if (m_AABB.intersect_ray( + MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3), + MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3), + support_point.pos + direction_to_camera_mesh * (support_point.head_front_radius + EPSILON), direction_to_camera_mesh, hits)) { std::sort(hits.begin(), hits.end(), [](const igl::Hit& h1, const igl::Hit& h2) { return h1.t < h2.t; }); if (m_clipping_plane_distance != 0.f) { // If the closest hit facet normal points in the same direction as the ray, // we are looking through the mesh and should therefore discard the point: int fid = hits.front().id; // facet id - Vec3f a = (m_V.row(m_F(fid, 1)) - m_V.row(m_F(fid, 0))); - Vec3f b = (m_V.row(m_F(fid, 2)) - m_V.row(m_F(fid, 0))); + Vec3f a = (m_its->vertices[m_its->indices[fid](1)] - m_its->vertices[m_its->indices[fid](0)]); + Vec3f b = (m_its->vertices[m_its->indices[fid](2)] - m_its->vertices[m_its->indices[fid](0)]); if ((a.cross(b)).dot(direction_to_camera_mesh) > 0.f) is_obscured = true; @@ -582,7 +598,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous int fid = hit.id; // facet id Vec3f bc = Vec3f(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit - Vec3f hit_pos = bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2)); + Vec3f hit_pos = bc(0) * m_its->vertices[m_its->indices[fid](0)] + bc(1) * m_its->vertices[m_its->indices[fid](1)] + bc(2)*m_its->vertices[m_its->indices[fid](2)]; if (is_point_clipped(hit_pos.cast())) { hits.erase(hits.begin()+j); --j; @@ -695,13 +711,17 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous void GLGizmoSlaSupports::delete_selected_points(bool force) { - for (unsigned int idx=0; idxreslice_SLA_supports(*m_model_object); } select_point(NoPoints); @@ -709,20 +729,21 @@ void GLGizmoSlaSupports::delete_selected_points(bool force) //m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } -void GLGizmoSlaSupports::on_update(const UpdateData& data, const Selection& selection) +void GLGizmoSlaSupports::on_update(const UpdateData& data) { - if (m_editing_mode && m_hover_id != -1 && data.mouse_pos && (!m_editing_mode_cache[m_hover_id].support_point.is_new_island || !m_lock_unique_islands)) { - std::pair pos_and_normal; - try { - pos_and_normal = unproject_on_mesh(Vec2d((*data.mouse_pos)(0), (*data.mouse_pos)(1))); + if (! m_editing_mode) + return; + else { + if (m_hover_id != -1 && (! m_editing_cache[m_hover_id].support_point.is_new_island || !m_lock_unique_islands)) { + std::pair pos_and_normal; + if (! unproject_on_mesh(data.mouse_pos.cast(), pos_and_normal)) + return; + m_editing_cache[m_hover_id].support_point.pos = pos_and_normal.first; + m_editing_cache[m_hover_id].support_point.is_new_island = false; + m_editing_cache[m_hover_id].normal = pos_and_normal.second; + // Do not update immediately, wait until the mouse is released. + // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } - catch (...) { return; } - m_editing_mode_cache[m_hover_id].support_point.pos = pos_and_normal.first; - m_editing_mode_cache[m_hover_id].support_point.is_new_island = false; - m_editing_mode_cache[m_hover_id].normal = pos_and_normal.second; - m_unsaved_changes = true; - // Do not update immediately, wait until the mouse is released. - // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } } @@ -757,12 +778,15 @@ std::vector GLGizmoSlaSupports::get_config_options(const st void GLGizmoSlaSupports::update_cache_entry_normal(unsigned int i) const { int idx = 0; - Eigen::Matrix pp = m_editing_mode_cache[i].support_point.pos; + Eigen::Matrix pp = m_editing_cache[i].support_point.pos; Eigen::Matrix cc; - m_AABB.squared_distance(m_V, m_F, pp, idx, cc); - Vec3f a = (m_V.row(m_F(idx, 1)) - m_V.row(m_F(idx, 0))); - Vec3f b = (m_V.row(m_F(idx, 2)) - m_V.row(m_F(idx, 0))); - m_editing_mode_cache[i].normal = a.cross(b); + m_AABB.squared_distance( + MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3), + MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3), + pp, idx, cc); + Vec3f a = (m_its->vertices[m_its->indices[idx](1)] - m_its->vertices[m_its->indices[idx](0)]); + Vec3f b = (m_its->vertices[m_its->indices[idx](2)] - m_its->vertices[m_its->indices[idx](0)]); + m_editing_cache[i].normal = a.cross(b); } @@ -826,7 +850,7 @@ void GLGizmoSlaSupports::make_line_segments() const */ -void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) +void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit) { if (!m_model_object) return; @@ -871,13 +895,34 @@ RENDER_AGAIN: ImGui::SameLine(diameter_slider_left); ImGui::PushItemWidth(window_width - diameter_slider_left); - if (ImGui::SliderFloat("", &m_new_point_head_diameter, 0.1f, diameter_upper_cap, "%.1f")) { - // value was changed - for (auto& cache_entry : m_editing_mode_cache) - if (cache_entry.selected) { + // Following is a nasty way to: + // - save the initial value of the slider before one starts messing with it + // - keep updating the head radius during sliding so it is continuosly refreshed in 3D scene + // - take correct undo/redo snapshot after the user is done with moving the slider + float initial_value = m_new_point_head_diameter; + ImGui::SliderFloat("", &m_new_point_head_diameter, 0.1f, diameter_upper_cap, "%.1f"); + if (ImGui::IsItemClicked()) { + if (m_old_point_head_diameter == 0.f) + m_old_point_head_diameter = initial_value; + } + if (ImGui::IsItemEdited()) { + for (auto& cache_entry : m_editing_cache) + if (cache_entry.selected) cache_entry.support_point.head_front_radius = m_new_point_head_diameter / 2.f; - m_unsaved_changes = true; - } + } + if (ImGui::IsItemDeactivatedAfterEdit()) { + // momentarily restore the old value to take snapshot + for (auto& cache_entry : m_editing_cache) + if (cache_entry.selected) + cache_entry.support_point.head_front_radius = m_old_point_head_diameter / 2.f; + float backup = m_new_point_head_diameter; + m_new_point_head_diameter = m_old_point_head_diameter; + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Change point head diameter"))); + m_new_point_head_diameter = backup; + for (auto& cache_entry : m_editing_cache) + if (cache_entry.selected) + cache_entry.support_point.head_front_radius = m_new_point_head_diameter / 2.f; + m_old_point_head_diameter = 0.f; } bool changed = m_lock_unique_islands; @@ -888,7 +933,7 @@ RENDER_AGAIN: remove_selected = m_imgui->button(m_desc.at("remove_selected")); m_imgui->disabled_end(); - m_imgui->disabled_begin(m_editing_mode_cache.empty()); + m_imgui->disabled_begin(m_editing_cache.empty()); remove_all = m_imgui->button(m_desc.at("remove_all")); m_imgui->disabled_end(); @@ -914,23 +959,34 @@ RENDER_AGAIN: float density = static_cast(opts[0])->value; float minimal_point_distance = static_cast(opts[1])->value; - bool value_changed = ImGui::SliderFloat("", &minimal_point_distance, 0.f, 20.f, "%.f mm"); - if (value_changed) - m_model_object->config.opt("support_points_minimal_distance", true)->value = minimal_point_distance; + ImGui::SliderFloat("", &minimal_point_distance, 0.f, 20.f, "%.f mm"); + bool slider_clicked = ImGui::IsItemClicked(); // someone clicked the slider + bool slider_edited = ImGui::IsItemEdited(); // someone is dragging the slider + bool slider_released = ImGui::IsItemDeactivatedAfterEdit(); // someone has just released the slider m_imgui->text(m_desc.at("points_density")); ImGui::SameLine(settings_sliders_left); - if (ImGui::SliderFloat(" ", &density, 0.f, 200.f, "%.f %%")) { - value_changed = true; + ImGui::SliderFloat(" ", &density, 0.f, 200.f, "%.f %%"); + slider_clicked |= ImGui::IsItemClicked(); + slider_edited |= ImGui::IsItemEdited(); + slider_released |= ImGui::IsItemDeactivatedAfterEdit(); + + if (slider_clicked) { // stash the values of the settings so we know what to revert to after undo + m_minimal_point_distance_stash = minimal_point_distance; + m_density_stash = density; + } + if (slider_edited) { + m_model_object->config.opt("support_points_minimal_distance", true)->value = minimal_point_distance; m_model_object->config.opt("support_points_density_relative", true)->value = (int)density; } - - if (value_changed) { // Update side panel - wxTheApp->CallAfter([]() { - wxGetApp().obj_settings()->UpdateAndShow(true); - wxGetApp().obj_list()->update_settings_items(); - }); + if (slider_released) { + m_model_object->config.opt("support_points_minimal_distance", true)->value = m_minimal_point_distance_stash; + m_model_object->config.opt("support_points_density_relative", true)->value = (int)m_density_stash; + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Support parameter change"))); + m_model_object->config.opt("support_points_minimal_distance", true)->value = minimal_point_distance; + m_model_object->config.opt("support_points_density_relative", true)->value = (int)density; + wxGetApp().obj_list()->update_and_show_object_settings_item(); } bool generate = m_imgui->button(m_desc.at("auto_generate")); @@ -942,12 +998,12 @@ RENDER_AGAIN: if (m_imgui->button(m_desc.at("manual_editing"))) switch_to_editing_mode(); - m_imgui->disabled_begin(m_editing_mode_cache.empty()); + m_imgui->disabled_begin(m_normal_cache.empty()); remove_all = m_imgui->button(m_desc.at("remove_all")); m_imgui->disabled_end(); // m_imgui->text(""); - // m_imgui->text(m_model_object->sla_points_status == sla::PointsStatus::None ? _(L("No points (will be autogenerated)")) : + // m_imgui->text(m_model_object->sla_points_status == sla::PointsStatus::NoPoints ? _(L("No points (will be autogenerated)")) : // (m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated ? _(L("Autogenerated points (no modifications)")) : // (m_model_object->sla_points_status == sla::PointsStatus::UserModified ? _(L("User-modified points")) : // (m_model_object->sla_points_status == sla::PointsStatus::Generating ? _(L("Generation in progress...")) : "UNKNOWN STATUS")))); @@ -989,11 +1045,18 @@ RENDER_AGAIN: if (remove_selected || remove_all) { force_refresh = false; m_parent.set_as_dirty(); - if (remove_all) + bool was_in_editing = m_editing_mode; + if (! was_in_editing) + switch_to_editing_mode(); + if (remove_all) { select_point(AllPoints); - delete_selected_points(remove_all); - if (remove_all && !m_editing_mode) + delete_selected_points(true); // true - delete regardless of locked status + } + if (remove_selected) + delete_selected_points(false); // leave locked points + if (! was_in_editing) editing_mode_apply_changes(); + if (first_run) { first_run = false; goto RENDER_AGAIN; @@ -1004,11 +1067,13 @@ RENDER_AGAIN: m_parent.set_as_dirty(); } -bool GLGizmoSlaSupports::on_is_activable(const Selection& selection) const +bool GLGizmoSlaSupports::on_is_activable() const { + const Selection& selection = m_parent.get_selection(); + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA || !selection.is_from_single_instance()) - return false; + return false; // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside. const Selection::IndicesList& list = selection.get_volume_idxs(); @@ -1029,88 +1094,166 @@ std::string GLGizmoSlaSupports::on_get_name() const return (_(L("SLA Support Points")) + " [L]").ToUTF8().data(); } + + void GLGizmoSlaSupports::on_set_state() { - if (m_state == On && m_old_state != On) { // the gizmo was just turned on - if (is_mesh_update_necessary()) - update_mesh(); + if (m_state == Hover) + return; - // we'll now reload support points: - if (m_model_object) - editing_mode_reload_cache(); - - m_parent.toggle_model_objects_visibility(false); - if (m_model_object) - m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance); - - // Set default head diameter from config. - const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; - m_new_point_head_diameter = static_cast(cfg.option("support_head_front_diameter"))->value; + // m_model_object pointer can be invalid (for instance because of undo/redo action), + // we should recover it from the object id + m_model_object = nullptr; + for (const auto mo : wxGetApp().model().objects) { + if (mo->id() == m_model_object_id) { + m_model_object = mo; + break; } - if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off + } + + if (m_state == On && m_old_state != On) { // the gizmo was just turned on + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned on"))); + if (is_mesh_update_necessary()) + update_mesh(); + + // we'll now reload support points: + if (m_model_object) + reload_cache(); + + m_parent.toggle_model_objects_visibility(false); + if (m_model_object) + m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance); + + // Set default head diameter from config. + const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; + m_new_point_head_diameter = static_cast(cfg.option("support_head_front_diameter"))->value; + } + if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off + bool will_ask = m_model_object && m_editing_mode && unsaved_changes(); + if (will_ask) { wxGetApp().CallAfter([this]() { // Following is called through CallAfter, because otherwise there was a problem // on OSX with the wxMessageDialog being shown several times when clicked into. - if (m_model_object) { - if (m_unsaved_changes) { - wxMessageDialog dlg(GUI::wxGetApp().mainframe, _(L("Do you want to save your manually edited support points?")) + "\n", - _(L("Save changes?")), wxICON_QUESTION | wxYES | wxNO); - if (dlg.ShowModal() == wxID_YES) - editing_mode_apply_changes(); - else - editing_mode_discard_changes(); - } - } - m_parent.toggle_model_objects_visibility(true); - m_editing_mode = false; // so it is not active next time the gizmo opens - m_editing_mode_cache.clear(); - m_clipping_plane_distance = 0.f; - // Release triangle mesh slicer and the AABB spatial search structure. - m_AABB.deinit(); - m_V = Eigen::MatrixXf(); - m_F = Eigen::MatrixXi(); - m_tms.reset(); - m_supports_tms.reset(); + wxMessageDialog dlg(GUI::wxGetApp().mainframe, _(L("Do you want to save your manually " + "edited support points?")) + "\n",_(L("Save changes?")), wxICON_QUESTION | wxYES | wxNO); + if (dlg.ShowModal() == wxID_YES) + editing_mode_apply_changes(); + else + editing_mode_discard_changes(); }); + // refuse to be turned off so the gizmo is active when the CallAfter is executed + m_state = m_old_state; } - m_old_state = m_state; + else { + // we are actually shutting down + disable_editing_mode(); // so it is not active next time the gizmo opens + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned off"))); + m_parent.toggle_model_objects_visibility(true); + m_normal_cache.clear(); + m_clipping_plane_distance = 0.f; + // Release triangle mesh slicer and the AABB spatial search structure. + m_AABB.deinit(); + m_its = nullptr; + m_tms.reset(); + m_supports_tms.reset(); + } + } + m_old_state = m_state; } -void GLGizmoSlaSupports::on_start_dragging(const Selection& selection) +void GLGizmoSlaSupports::on_start_dragging() { if (m_hover_id != -1) { select_point(NoPoints); select_point(m_hover_id); + m_point_before_drag = m_editing_cache[m_hover_id]; } + else + m_point_before_drag = CacheEntry(); +} + + +void GLGizmoSlaSupports::on_stop_dragging() +{ + if (m_hover_id != -1) { + CacheEntry backup = m_editing_cache[m_hover_id]; + + if (m_point_before_drag.support_point.pos != Vec3f::Zero() // some point was touched + && backup.support_point.pos != m_point_before_drag.support_point.pos) // and it was moved, not just selected + { + m_editing_cache[m_hover_id] = m_point_before_drag; + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Move support point"))); + m_editing_cache[m_hover_id] = backup; + } + } + m_point_before_drag = CacheEntry(); +} + + + +void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar) +{ + ar(m_clipping_plane_distance, + m_clipping_plane_normal, + m_model_object_id, + m_new_point_head_diameter, + m_normal_cache, + m_editing_cache, + m_selection_empty + ); +} + + + +void GLGizmoSlaSupports::on_save(cereal::BinaryOutputArchive& ar) const +{ + ar(m_clipping_plane_distance, + m_clipping_plane_normal, + m_model_object_id, + m_new_point_head_diameter, + m_normal_cache, + m_editing_cache, + m_selection_empty + ); } void GLGizmoSlaSupports::select_point(int i) { + if (! m_editing_mode) { + std::cout << "DEBUGGING: select_point called when out of editing mode!" << std::endl; + std::abort(); + } + if (i == AllPoints || i == NoPoints) { - for (auto& point_and_selection : m_editing_mode_cache) + for (auto& point_and_selection : m_editing_cache) point_and_selection.selected = ( i == AllPoints ); m_selection_empty = (i == NoPoints); if (i == AllPoints) - m_new_point_head_diameter = m_editing_mode_cache[0].support_point.head_front_radius * 2.f; + m_new_point_head_diameter = m_editing_cache[0].support_point.head_front_radius * 2.f; } else { - m_editing_mode_cache[i].selected = true; + m_editing_cache[i].selected = true; m_selection_empty = false; - m_new_point_head_diameter = m_editing_mode_cache[i].support_point.head_front_radius * 2.f; + m_new_point_head_diameter = m_editing_cache[i].support_point.head_front_radius * 2.f; } } void GLGizmoSlaSupports::unselect_point(int i) { - m_editing_mode_cache[i].selected = false; + if (! m_editing_mode) { + std::cout << "DEBUGGING: unselect_point called when out of editing mode!" << std::endl; + std::abort(); + } + + m_editing_cache[i].selected = false; m_selection_empty = true; - for (const CacheEntry& ce : m_editing_mode_cache) { + for (const CacheEntry& ce : m_editing_cache) { if (ce.selected) { m_selection_empty = false; break; @@ -1120,21 +1263,15 @@ void GLGizmoSlaSupports::unselect_point(int i) + void GLGizmoSlaSupports::editing_mode_discard_changes() { - // If the points were autogenerated, they may not be on the ModelObject yet. - // Because the user probably messed with the cache, we will get the data - // from the backend again. - - if (m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated) - get_data_from_backend(); - else { - m_editing_mode_cache.clear(); - for (const sla::SupportPoint& point : m_model_object->sla_support_points) - m_editing_mode_cache.emplace_back(point, false); + if (! m_editing_mode) { + std::cout << "DEBUGGING: editing_mode_discard_changes called when out of editing mode!" << std::endl; + std::abort(); } - m_editing_mode = false; - m_unsaved_changes = false; + select_point(NoPoints); + disable_editing_mode(); } @@ -1143,51 +1280,69 @@ void GLGizmoSlaSupports::editing_mode_apply_changes() { // If there are no changes, don't touch the front-end. The data in the cache could have been // taken from the backend and copying them to ModelObject would needlessly invalidate them. - if (m_unsaved_changes) { + disable_editing_mode(); // this leaves the editing mode undo/redo stack and must be done before the snapshot is taken + + if (unsaved_changes()) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Support points edit"))); + + m_normal_cache.clear(); + for (const CacheEntry& ce : m_editing_cache) + m_normal_cache.push_back(ce.support_point); + m_model_object->sla_points_status = sla::PointsStatus::UserModified; m_model_object->sla_support_points.clear(); - for (const CacheEntry& cache_entry : m_editing_mode_cache) - m_model_object->sla_support_points.push_back(cache_entry.support_point); + m_model_object->sla_support_points = m_normal_cache; - // Recalculate support structures once the editing mode is left. - // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - wxGetApp().CallAfter([this]() { wxGetApp().plater()->reslice_SLA_supports(*m_model_object); }); + reslice_SLA_supports(); } - m_editing_mode = false; - m_unsaved_changes = false; } -void GLGizmoSlaSupports::editing_mode_reload_cache() +void GLGizmoSlaSupports::reload_cache() { - m_editing_mode_cache.clear(); - for (const sla::SupportPoint& point : m_model_object->sla_support_points) - m_editing_mode_cache.emplace_back(point, false); - - m_unsaved_changes = false; + m_normal_cache.clear(); + if (m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated || m_model_object->sla_points_status == sla::PointsStatus::Generating) + get_data_from_backend(); + else + for (const sla::SupportPoint& point : m_model_object->sla_support_points) + m_normal_cache.emplace_back(point); } +bool GLGizmoSlaSupports::has_backend_supports() const +{ + // find SlaPrintObject with this ID + for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { + if (po->model_object()->id() == m_model_object->id()) + return po->is_step_done(slaposSupportPoints); + } + return false; +} + +void GLGizmoSlaSupports::reslice_SLA_supports() const +{ + wxGetApp().CallAfter([this]() { wxGetApp().plater()->reslice_SLA_supports(*m_model_object); }); +} void GLGizmoSlaSupports::get_data_from_backend() { + if (! has_backend_supports()) + return; + + // find the respective SLAPrintObject, we need a pointer to it for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { - if (po->model_object()->id() == m_model_object->id() && po->is_step_done(slaposSupportPoints)) { - m_editing_mode_cache.clear(); + if (po->model_object()->id() == m_model_object->id()) { + m_normal_cache.clear(); const std::vector& points = po->get_support_points(); auto mat = po->trafo().inverse().cast(); for (unsigned int i=0; isla_points_status != sla::PointsStatus::UserModified) - m_model_object->sla_points_status = sla::PointsStatus::AutoGenerated; + m_normal_cache.emplace_back(sla::SupportPoint(mat * points[i].pos, points[i].head_front_radius, points[i].is_new_island)); + m_model_object->sla_points_status = sla::PointsStatus::AutoGenerated; break; } } - m_unsaved_changes = false; // We don't copy the data into ModelObject, as this would stop the background processing. } @@ -1201,11 +1356,10 @@ void GLGizmoSlaSupports::auto_generate() "Are you sure you want to do it?\n" )), _(L("Warning")), wxICON_WARNING | wxYES | wxNO); - if (m_model_object->sla_points_status != sla::PointsStatus::UserModified || m_editing_mode_cache.empty() || dlg.ShowModal() == wxID_YES) { - m_model_object->sla_support_points.clear(); + if (m_model_object->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Autogenerate support points"))); + wxGetApp().CallAfter([this]() { reslice_SLA_supports(); }); m_model_object->sla_points_status = sla::PointsStatus::Generating; - m_editing_mode_cache.clear(); - wxGetApp().CallAfter([this]() { wxGetApp().plater()->reslice_SLA_supports(*m_model_object); }); } } @@ -1213,13 +1367,37 @@ void GLGizmoSlaSupports::auto_generate() void GLGizmoSlaSupports::switch_to_editing_mode() { - if (m_model_object->sla_points_status != sla::PointsStatus::AutoGenerated) - editing_mode_reload_cache(); - m_unsaved_changes = false; + wxGetApp().plater()->enter_gizmos_stack(); m_editing_mode = true; + m_editing_cache.clear(); + for (const sla::SupportPoint& sp : m_normal_cache) + m_editing_cache.emplace_back(sp); + select_point(NoPoints); } +void GLGizmoSlaSupports::disable_editing_mode() +{ + if (m_editing_mode) { + m_editing_mode = false; + wxGetApp().plater()->leave_gizmos_stack(); + } +} + + + +bool GLGizmoSlaSupports::unsaved_changes() const +{ + if (m_editing_cache.size() != m_normal_cache.size()) + return true; + + for (size_t i=0; i +#include + namespace Slic3r { namespace GUI { @@ -26,19 +28,20 @@ class GLGizmoSlaSupports : public GLGizmoBase { private: ModelObject* m_model_object = nullptr; - ModelID m_current_mesh_model_id = 0; + ObjectID m_model_object_id = 0; int m_active_instance = -1; float m_active_instance_bb_radius; // to cache the bb mutable float m_z_shift = 0.f; - std::pair unproject_on_mesh(const Vec2d& mouse_pos); + bool unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal); const float RenderPointScale = 1.f; GLUquadricObj* m_quadric; - Eigen::MatrixXf m_V; // vertices - Eigen::MatrixXi m_F; // facets indices - igl::AABB m_AABB; + typedef Eigen::Map> MapMatrixXfUnaligned; + typedef Eigen::Map> MapMatrixXiUnaligned; + igl::AABB m_AABB; const TriangleMesh* m_mesh; + const indexed_triangle_set* m_its; mutable const TriangleMesh* m_supports_mesh; mutable std::vector m_triangles; mutable std::vector m_supports_triangles; @@ -48,20 +51,33 @@ private: class CacheEntry { public: - CacheEntry(const sla::SupportPoint& point, bool sel, const Vec3f& norm = Vec3f::Zero()) : + CacheEntry() : + support_point(sla::SupportPoint()), selected(false), normal(Vec3f::Zero()) {} + + CacheEntry(const sla::SupportPoint& point, bool sel = false, const Vec3f& norm = Vec3f::Zero()) : support_point(point), selected(sel), normal(norm) {} + bool operator==(const CacheEntry& rhs) const { + return (support_point == rhs.support_point); + } + + bool operator!=(const CacheEntry& rhs) const { + return ! ((*this) == rhs); + } + sla::SupportPoint support_point; bool selected; // whether the point is selected Vec3f normal; + + template + void serialize(Archive & ar) + { + ar(support_point, selected, normal); + } }; public: -#if ENABLE_SVG_ICONS GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoSlaSupports(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS virtual ~GLGizmoSlaSupports(); void set_sla_support_data(ModelObject* model_object, const Selection& selection); bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); @@ -70,12 +86,14 @@ public: bool is_in_editing_mode() const { return m_editing_mode; } bool is_selection_rectangle_dragging() const { return m_selection_rectangle.is_dragging(); } + bool has_backend_supports() const; + void reslice_SLA_supports() const; private: bool on_init(); - void on_update(const UpdateData& data, const Selection& selection); - virtual void on_render(const Selection& selection) const; - virtual void on_render_for_picking(const Selection& selection) const; + void on_update(const UpdateData& data); + virtual void on_render() const; + virtual void on_render_for_picking() const; //void render_selection_rectangle() const; void render_points(const Selection& selection, bool picking = false) const; @@ -83,13 +101,21 @@ private: bool is_mesh_update_necessary() const; void update_mesh(); void update_cache_entry_normal(unsigned int i) const; + bool unsaved_changes() const; + EState m_no_hover_state = Off; + EState m_no_hover_old_state = Off; bool m_lock_unique_islands = false; bool m_editing_mode = false; // Is editing mode active? bool m_old_editing_state = false; // To keep track of whether the user toggled between the modes (needed for imgui refreshes). float m_new_point_head_diameter; // Size of a new point. - float m_minimal_point_distance = 20.f; - mutable std::vector m_editing_mode_cache; // a support point and whether it is currently selected + CacheEntry m_point_before_drag; // undo/redo - so we know what state was edited + float m_old_point_head_diameter = 0.; // the same + float m_minimal_point_distance_stash = 0.f; // and again + float m_density_stash = 0.f; // and again + mutable std::vector m_editing_cache; // a support point and whether it is currently selected + std::vector m_normal_cache; // to restore after discarding changes or undo/redo + float m_clipping_plane_distance = 0.f; mutable float m_old_clipping_plane_distance = 0.f; mutable Vec3d m_old_clipping_plane_normal; @@ -102,7 +128,6 @@ private: GLSelectionRectangle m_selection_rectangle; bool m_wait_for_up_event = false; - bool m_unsaved_changes = false; // Are there unsaved changes in manual mode? bool m_selection_empty = true; EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state) @@ -123,20 +148,29 @@ private: void unselect_point(int i); void editing_mode_apply_changes(); void editing_mode_discard_changes(); - void editing_mode_reload_cache(); + void reload_cache(); void get_data_from_backend(); void auto_generate(); void switch_to_editing_mode(); + void disable_editing_mode(); void reset_clipping_plane_normal() const; protected: void on_set_state() override; - void on_start_dragging(const Selection& selection) override; - virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) override; + virtual void on_set_hover_id() + { + if (! m_editing_mode || (int)m_editing_cache.size() <= m_hover_id) + m_hover_id = -1; + } + void on_start_dragging() override; + void on_stop_dragging() override; + virtual void on_render_input_window(float x, float y, float bottom_limit) override; virtual std::string on_get_name() const; - virtual bool on_is_activable(const Selection& selection) const; + virtual bool on_is_activable() const; virtual bool on_is_selectable() const; + virtual void on_load(cereal::BinaryInputArchive& ar) override; + virtual void on_save(cereal::BinaryOutputArchive& ar) const override; }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 1006d2bd1e..c21af45f0d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -5,6 +5,7 @@ #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_ObjectManipulation.hpp" #include "slic3r/GUI/PresetBundle.hpp" +#include "slic3r/Utils/UndoRedo.hpp" #include #include @@ -12,51 +13,29 @@ namespace Slic3r { namespace GUI { -#if ENABLE_SVG_ICONS - const float GLGizmosManager::Default_Icons_Size = 64; -#endif // ENABLE_SVG_ICONS +const float GLGizmosManager::Default_Icons_Size = 64; -GLGizmosManager::GLGizmosManager() - : m_enabled(false) -#if ENABLE_SVG_ICONS +GLGizmosManager::GLGizmosManager(GLCanvas3D& parent) + : m_parent(parent) + , m_enabled(false) , m_icons_texture_dirty(true) -#endif // ENABLE_SVG_ICONS , m_current(Undefined) -#if ENABLE_SVG_ICONS , m_overlay_icons_size(Default_Icons_Size) , m_overlay_scale(1.0f) , m_overlay_border(5.0f) , m_overlay_gap_y(5.0f) , m_tooltip("") + , m_serializing(false) { } -#else -{ - set_overlay_scale(1.0); -} -#endif // ENABLE_SVG_ICONS GLGizmosManager::~GLGizmosManager() { reset(); } -bool GLGizmosManager::init(GLCanvas3D& parent) +bool GLGizmosManager::init() { -#if !ENABLE_SVG_ICONS - m_icons_texture.metadata.filename = "gizmos.png"; - m_icons_texture.metadata.icon_size = 64; - - if (!m_icons_texture.metadata.filename.empty()) - { - if (!m_icons_texture.texture.load_from_file(resources_dir() + "/icons/" + m_icons_texture.metadata.filename, false)) - { - reset(); - return false; - } - } -#endif // !ENABLE_SVG_ICONS - m_background_texture.metadata.filename = "toolbar_background.png"; m_background_texture.metadata.left = 16; m_background_texture.metadata.top = 16; @@ -65,18 +44,14 @@ bool GLGizmosManager::init(GLCanvas3D& parent) if (!m_background_texture.metadata.filename.empty()) { - if (!m_background_texture.texture.load_from_file(resources_dir() + "/icons/" + m_background_texture.metadata.filename, false)) + if (!m_background_texture.texture.load_from_file(resources_dir() + "/icons/" + m_background_texture.metadata.filename, false, GLTexture::SingleThreaded, false)) { reset(); return false; } } -#if ENABLE_SVG_ICONS - GLGizmoBase* gizmo = new GLGizmoMove3D(parent, "move.svg", 0); -#else - GLGizmoBase* gizmo = new GLGizmoMove3D(parent, 0); -#endif // ENABLE_SVG_ICONS + GLGizmoBase* gizmo = new GLGizmoMove3D(m_parent, "move.svg", 0); if (gizmo == nullptr) return false; @@ -85,11 +60,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) m_gizmos.insert(GizmosMap::value_type(Move, gizmo)); -#if ENABLE_SVG_ICONS - gizmo = new GLGizmoScale3D(parent, "scale.svg", 1); -#else - gizmo = new GLGizmoScale3D(parent, 1); -#endif // ENABLE_SVG_ICONS + gizmo = new GLGizmoScale3D(m_parent, "scale.svg", 1); if (gizmo == nullptr) return false; @@ -98,11 +69,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) m_gizmos.insert(GizmosMap::value_type(Scale, gizmo)); -#if ENABLE_SVG_ICONS - gizmo = new GLGizmoRotate3D(parent, "rotate.svg", 2); -#else - gizmo = new GLGizmoRotate3D(parent, 2); -#endif // ENABLE_SVG_ICONS + gizmo = new GLGizmoRotate3D(m_parent, "rotate.svg", 2); if (gizmo == nullptr) { reset(); @@ -117,11 +84,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) m_gizmos.insert(GizmosMap::value_type(Rotate, gizmo)); -#if ENABLE_SVG_ICONS - gizmo = new GLGizmoFlatten(parent, "place.svg", 3); -#else - gizmo = new GLGizmoFlatten(parent, 3); -#endif // ENABLE_SVG_ICONS + gizmo = new GLGizmoFlatten(m_parent, "place.svg", 3); if (gizmo == nullptr) return false; @@ -132,11 +95,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) m_gizmos.insert(GizmosMap::value_type(Flatten, gizmo)); -#if ENABLE_SVG_ICONS - gizmo = new GLGizmoCut(parent, "cut.svg", 4); -#else - gizmo = new GLGizmoCut(parent, 4); -#endif // ENABLE_SVG_ICONS + gizmo = new GLGizmoCut(m_parent, "cut.svg", 4); if (gizmo == nullptr) return false; @@ -147,11 +106,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) m_gizmos.insert(GizmosMap::value_type(Cut, gizmo)); -#if ENABLE_SVG_ICONS - gizmo = new GLGizmoSlaSupports(parent, "sla_supports.svg", 5); -#else - gizmo = new GLGizmoSlaSupports(parent, 5); -#endif // ENABLE_SVG_ICONS + gizmo = new GLGizmoSlaSupports(m_parent, "sla_supports.svg", 5); if (gizmo == nullptr) return false; @@ -165,7 +120,6 @@ bool GLGizmosManager::init(GLCanvas3D& parent) return true; } -#if ENABLE_SVG_ICONS void GLGizmosManager::set_overlay_icon_size(float size) { if (m_overlay_icons_size != size) @@ -174,29 +128,25 @@ void GLGizmosManager::set_overlay_icon_size(float size) m_icons_texture_dirty = true; } } -#endif // ENABLE_SVG_ICONS void GLGizmosManager::set_overlay_scale(float scale) { -#if ENABLE_SVG_ICONS if (m_overlay_scale != scale) { m_overlay_scale = scale; m_icons_texture_dirty = true; } -#else - m_overlay_icons_scale = scale; - m_overlay_border = 5.0f * scale; - m_overlay_gap_y = 5.0f * scale; -#endif // ENABLE_SVG_ICONS } -void GLGizmosManager::refresh_on_off_state(const Selection& selection) +void GLGizmosManager::refresh_on_off_state() { + if (m_serializing) + return; + GizmosMap::iterator it = m_gizmos.find(m_current); if ((it != m_gizmos.end()) && (it->second != nullptr)) { - if (!it->second->is_activable(selection)) + if (!it->second->is_activable()) { it->second->set_state(GLGizmoBase::Off); m_current = Undefined; @@ -209,6 +159,9 @@ void GLGizmosManager::reset_all_states() if (!m_enabled) return; + if (m_serializing) + return; + for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if (it->second != nullptr) @@ -248,22 +201,22 @@ void GLGizmosManager::enable_grabber(EType type, unsigned int id, bool enable) } } -void GLGizmosManager::update(const Linef3& mouse_ray, const Selection& selection, const Point* mouse_pos) +void GLGizmosManager::update(const Linef3& mouse_ray, const Point& mouse_pos) { if (!m_enabled) return; GLGizmoBase* curr = get_current(); if (curr != nullptr) - curr->update(GLGizmoBase::UpdateData(mouse_ray, mouse_pos), selection); + curr->update(GLGizmoBase::UpdateData(mouse_ray, mouse_pos)); } -void GLGizmosManager::update_data(GLCanvas3D& canvas) +void GLGizmosManager::update_data() { if (!m_enabled) return; - const Selection& selection = canvas.get_selection(); + const Selection& selection = m_parent.get_selection(); bool is_wipe_tower = selection.is_wipe_tower(); enable_grabber(Move, 2, !is_wipe_tower); @@ -271,7 +224,7 @@ void GLGizmosManager::update_data(GLCanvas3D& canvas) enable_grabber(Rotate, 1, !is_wipe_tower); bool enable_scale_xyz = selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier(); - for (int i = 0; i < 6; ++i) + for (unsigned int i = 0; i < 6; ++i) { enable_grabber(Scale, i, enable_scale_xyz); } @@ -284,7 +237,7 @@ void GLGizmosManager::update_data(GLCanvas3D& canvas) set_rotation(Vec3d::Zero()); ModelObject* model_object = selection.get_model()->objects[selection.get_object_idx()]; set_flattening_data(model_object); - set_sla_support_data(model_object, selection); + set_sla_support_data(model_object); } else if (selection.is_single_volume() || selection.is_single_modifier()) { @@ -292,7 +245,7 @@ void GLGizmosManager::update_data(GLCanvas3D& canvas) set_scale(volume->get_volume_scaling_factor()); set_rotation(Vec3d::Zero()); set_flattening_data(nullptr); - set_sla_support_data(nullptr, selection); + set_sla_support_data(nullptr); } else if (is_wipe_tower) { @@ -300,14 +253,14 @@ void GLGizmosManager::update_data(GLCanvas3D& canvas) set_scale(Vec3d::Ones()); set_rotation(Vec3d(0., 0., (M_PI/180.) * dynamic_cast(config.option("wipe_tower_rotation_angle"))->value)); set_flattening_data(nullptr); - set_sla_support_data(nullptr, selection); + set_sla_support_data(nullptr); } else { set_scale(Vec3d::Ones()); set_rotation(Vec3d::Zero()); set_flattening_data(selection.is_from_single_object() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr); - set_sla_support_data(nullptr, selection); + set_sla_support_data(nullptr); } } @@ -320,13 +273,16 @@ bool GLGizmosManager::is_running() const return (curr != nullptr) ? (curr->get_state() == GLGizmoBase::On) : false; } -bool GLGizmosManager::handle_shortcut(int key, const Selection& selection) +bool GLGizmosManager::handle_shortcut(int key) { - if (!m_enabled || selection.is_empty()) + if (!m_enabled) + return false; + + if (m_parent.get_selection().is_empty()) return false; - EType old_current = m_current; bool handled = false; + for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) @@ -334,30 +290,39 @@ bool GLGizmosManager::handle_shortcut(int key, const Selection& selection) int it_key = it->second->get_shortcut_key(); - if (it->second->is_activable(selection) && ((it_key == key - 64) || (it_key == key - 96))) + if (it->second->is_activable() && ((it_key == key - 64) || (it_key == key - 96))) { if ((it->second->get_state() == GLGizmoBase::On)) { it->second->set_state(GLGizmoBase::Off); - m_current = Undefined; + if (it->second->get_state() == GLGizmoBase::Off) { + m_current = Undefined; + } handled = true; } else if ((it->second->get_state() == GLGizmoBase::Off)) { - it->second->set_state(GLGizmoBase::On); - m_current = it->first; + // Before turning anything on, turn everything else off to see if + // nobody refuses. Only then activate the gizmo. + bool can_open = true; + for (GizmosMap::iterator i = m_gizmos.begin(); i != m_gizmos.end(); ++i) { + if (i->first != it->first) { + if (m_current == i->first && i->second->get_state() != GLGizmoBase::Off ) { + i->second->set_state(GLGizmoBase::Off); + if (i->second->get_state() != GLGizmoBase::Off) + can_open = false; + } + } + } + if (can_open) { + it->second->set_state(GLGizmoBase::On); + m_current = it->first; + } handled = true; } } } - if (handled && (old_current != Undefined) && (old_current != m_current)) - { - GizmosMap::const_iterator it = m_gizmos.find(old_current); - if (it != m_gizmos.end()) - it->second->set_state(GLGizmoBase::Off); - } - return handled; } @@ -370,14 +335,14 @@ bool GLGizmosManager::is_dragging() const return (curr != nullptr) ? curr->is_dragging() : false; } -void GLGizmosManager::start_dragging(const Selection& selection) +void GLGizmosManager::start_dragging() { if (!m_enabled) return; GLGizmoBase* curr = get_current(); if (curr != nullptr) - curr->start_dragging(selection); + curr->start_dragging(); } void GLGizmosManager::stop_dragging() @@ -465,14 +430,14 @@ void GLGizmosManager::set_flattening_data(const ModelObject* model_object) reinterpret_cast(it->second)->set_flattening_data(model_object); } -void GLGizmosManager::set_sla_support_data(ModelObject* model_object, const Selection& selection) +void GLGizmosManager::set_sla_support_data(ModelObject* model_object) { if (!m_enabled) return; GizmosMap::const_iterator it = m_gizmos.find(SlaSupports); if (it != m_gizmos.end()) - reinterpret_cast(it->second)->set_sla_support_data(model_object, selection); + reinterpret_cast(it->second)->set_sla_support_data(model_object, m_parent.get_selection()); } // Returns true if the gizmo used the event to do something, false otherwise. @@ -500,50 +465,44 @@ ClippingPlane GLGizmosManager::get_sla_clipping_plane() const return ClippingPlane::ClipsNothing(); } +bool GLGizmosManager::wants_reslice_supports_on_undo() const +{ + return (m_current == SlaSupports + && dynamic_cast(m_gizmos.at(SlaSupports))->has_backend_supports()); +} -void GLGizmosManager::render_current_gizmo(const Selection& selection) const +void GLGizmosManager::render_current_gizmo() const { if (!m_enabled) return; GLGizmoBase* curr = get_current(); if (curr != nullptr) - curr->render(selection); + curr->render(); } -void GLGizmosManager::render_current_gizmo_for_picking_pass(const Selection& selection) const +void GLGizmosManager::render_current_gizmo_for_picking_pass() const { if (!m_enabled) return; GLGizmoBase* curr = get_current(); if (curr != nullptr) - curr->render_for_picking(selection); + curr->render_for_picking(); } -void GLGizmosManager::render_overlay(const GLCanvas3D& canvas, const Selection& selection) const +void GLGizmosManager::render_overlay() const { if (!m_enabled) return; -#if ENABLE_SVG_ICONS if (m_icons_texture_dirty) generate_icons_texture(); -#endif // ENABLE_SVG_ICONS - glsafe(::glDisable(GL_DEPTH_TEST)); - - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); - - do_render_overlay(canvas, selection); - - glsafe(::glPopMatrix()); + do_render_overlay(); } - - -bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt, GLCanvas3D& canvas) +bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt) { bool processed = false; @@ -556,14 +515,12 @@ bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt, GLCanvas3D& canvas) return processed; } - - -bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) +bool GLGizmosManager::on_mouse(wxMouseEvent& evt) { Point pos(evt.GetX(), evt.GetY()); Vec2d mouse_pos((double)evt.GetX(), (double)evt.GetY()); - Selection& selection = canvas.get_selection(); + Selection& selection = m_parent.get_selection(); int selected_object_idx = selection.get_object_idx(); bool processed = false; @@ -579,7 +536,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) // mouse anywhere if (evt.Moving()) - m_tooltip = update_hover_state(canvas, mouse_pos); + m_tooltip = update_hover_state(mouse_pos); else if (evt.LeftUp()) m_mouse_capture.left = false; else if (evt.MiddleUp()) @@ -590,7 +547,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) // if the button down was done on this toolbar, prevent from dragging into the scene processed = true; - if (!overlay_contains_mouse(canvas, mouse_pos)) + if (!overlay_contains_mouse(mouse_pos)) { // mouse is outside the toolbar m_tooltip = ""; @@ -602,41 +559,40 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) processed = true; else if (!selection.is_empty() && grabber_contains_mouse()) { - update_data(canvas); + update_data(); selection.start_dragging(); - start_dragging(selection); + start_dragging(); if (m_current == Flatten) { // Rotate the object so the normal points downward: - selection.flattening_rotate(get_flattening_normal()); - canvas.do_flatten(); + m_parent.do_flatten(get_flattening_normal(), L("Gizmo-Place on Face")); wxGetApp().obj_manipul()->set_dirty(); } - canvas.set_as_dirty(); + m_parent.set_as_dirty(); processed = true; } } else if (evt.RightDown() && (selected_object_idx != -1) && (m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::RightDown)) // event was taken care of by the SlaSupports gizmo processed = true; - else if (evt.Dragging() && (canvas.get_move_volume_id() != -1) && (m_current == SlaSupports)) - // don't allow dragging objects with the Sla gizmo on + else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1) && (m_current == SlaSupports)) + // don't allow dragging objects with the Sla gizmo on processed = true; else if (evt.Dragging() && (m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown())) { // the gizmo got the event and took some action, no need to do anything more here - canvas.set_as_dirty(); + m_parent.set_as_dirty(); processed = true; } else if (evt.Dragging() && is_dragging()) { - if (!canvas.get_wxglcanvas()->HasCapture()) - canvas.get_wxglcanvas()->CaptureMouse(); + if (!m_parent.get_wxglcanvas()->HasCapture()) + m_parent.get_wxglcanvas()->CaptureMouse(); - canvas.set_mouse_as_dragging(); - update(canvas.mouse_ray(pos), selection, &pos); + m_parent.set_mouse_as_dragging(); + update(m_parent.mouse_ray(pos), pos); switch (m_current) { @@ -673,7 +629,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) break; } - canvas.set_as_dirty(); + m_parent.set_as_dirty(); processed = true; } else if (evt.LeftUp() && is_dragging()) @@ -682,18 +638,17 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) { case Move: { - canvas.disable_regenerate_volumes(); - canvas.do_move(); + m_parent.do_move(L("Gizmo-Move")); break; } case Scale: { - canvas.do_scale(); + m_parent.do_scale(L("Gizmo-Scale")); break; } case Rotate: { - canvas.do_rotate(); + m_parent.do_rotate(L("Gizmo-Rotate")); break; } default: @@ -701,25 +656,25 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) } stop_dragging(); - update_data(canvas); + update_data(); wxGetApp().obj_manipul()->set_dirty(); // Let the platter know that the dragging finished, so a delayed refresh // of the scene with the background processing data should be performed. - canvas.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); // updates camera target constraints - canvas.refresh_camera_scene_box(); + m_parent.refresh_camera_scene_box(); processed = true; } - else if (evt.LeftUp() && (m_current == SlaSupports) && !canvas.is_mouse_dragging()) + else if (evt.LeftUp() && (m_current == SlaSupports) && !m_parent.is_mouse_dragging()) { // in case SLA gizmo is selected, we just pass the LeftUp event and stop processing - neither // object moving or selecting is suppressed in that case gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()); processed = true; } - else if (evt.LeftUp() && (m_current == Flatten) && ((canvas.get_first_hover_volume_idx() != -1) || grabber_contains_mouse())) + else if (evt.LeftUp() && (m_current == Flatten) && ((m_parent.get_first_hover_volume_idx() != -1) || grabber_contains_mouse())) { // to avoid to loose the selection when user clicks an object while the Flatten gizmo is active processed = true; @@ -731,24 +686,24 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) if (evt.LeftDown() || evt.LeftDClick()) { m_mouse_capture.left = true; - m_mouse_capture.parent = &canvas; + m_mouse_capture.parent = &m_parent; processed = true; if (!selection.is_empty()) { - update_on_off_state(canvas, mouse_pos, selection); - update_data(canvas); - canvas.set_as_dirty(); + update_on_off_state(mouse_pos); + update_data(); + m_parent.set_as_dirty(); } } else if (evt.MiddleDown()) { m_mouse_capture.middle = true; - m_mouse_capture.parent = &canvas; + m_mouse_capture.parent = &m_parent; } else if (evt.RightDown()) { m_mouse_capture.right = true; - m_mouse_capture.parent = &canvas; + m_mouse_capture.parent = &m_parent; } else if (evt.LeftUp()) processed = true; @@ -757,7 +712,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) return processed; } -bool GLGizmosManager::on_char(wxKeyEvent& evt, GLCanvas3D& canvas) +bool GLGizmosManager::on_char(wxKeyEvent& evt) { // see include/wx/defs.h enum wxKeyCode int keyCode = evt.GetKeyCode(); @@ -865,20 +820,20 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt, GLCanvas3D& canvas) if (!processed && !evt.HasModifiers()) { - if (handle_shortcut(keyCode, canvas.get_selection())) + if (handle_shortcut(keyCode)) { - update_data(canvas); + update_data(); processed = true; } } if (processed) - canvas.set_as_dirty(); + m_parent.set_as_dirty(); return processed; } -bool GLGizmosManager::on_key(wxKeyEvent& evt, GLCanvas3D& canvas) +bool GLGizmosManager::on_key(wxKeyEvent& evt) { const int keyCode = evt.GetKeyCode(); bool processed = false; @@ -904,23 +859,32 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt, GLCanvas3D& canvas) } // if (processed) -// canvas.set_cursor(GLCanvas3D::Standard); +// m_parent.set_cursor(GLCanvas3D::Standard); } else if (evt.GetEventType() == wxEVT_KEY_DOWN) { if ((m_current == SlaSupports) && ((keyCode == WXK_SHIFT) || (keyCode == WXK_ALT)) && reinterpret_cast(get_current())->is_in_editing_mode()) { -// canvas.set_cursor(GLCanvas3D::Cross); +// m_parent.set_cursor(GLCanvas3D::Cross); processed = true; } } if (processed) - canvas.set_as_dirty(); + m_parent.set_as_dirty(); return processed; } +void GLGizmosManager::update_after_undo_redo(const UndoRedo::Snapshot& snapshot) +{ + update_data(); + m_serializing = false; + if (m_current == SlaSupports + && snapshot.snapshot_data.flags & UndoRedo::SnapshotData::RECALCULATE_SLA_SUPPORTS) + dynamic_cast(m_gizmos[SlaSupports])->reslice_SLA_supports(); +} + void GLGizmosManager::reset() { for (GizmosMap::value_type& gizmo : m_gizmos) @@ -932,23 +896,73 @@ void GLGizmosManager::reset() m_gizmos.clear(); } -void GLGizmosManager::do_render_overlay(const GLCanvas3D& canvas, const Selection& selection) const +void GLGizmosManager::render_background(float left, float top, float right, float bottom, float border) const +{ + unsigned int tex_id = m_background_texture.texture.get_id(); + float tex_width = (float)m_background_texture.texture.get_width(); + float tex_height = (float)m_background_texture.texture.get_height(); + if ((tex_id != 0) && (tex_width > 0) && (tex_height > 0)) + { + float inv_tex_width = (tex_width != 0.0f) ? 1.0f / tex_width : 0.0f; + float inv_tex_height = (tex_height != 0.0f) ? 1.0f / tex_height : 0.0f; + + float internal_left = left + border; + float internal_right = right - border; + float internal_top = top - border; + float internal_bottom = bottom + border; + + float left_uv = 0.0f; + float right_uv = 1.0f; + float top_uv = 1.0f; + float bottom_uv = 0.0f; + + float internal_left_uv = (float)m_background_texture.metadata.left * inv_tex_width; + float internal_right_uv = 1.0f - (float)m_background_texture.metadata.right * inv_tex_width; + float internal_top_uv = 1.0f - (float)m_background_texture.metadata.top * inv_tex_height; + float internal_bottom_uv = (float)m_background_texture.metadata.bottom * inv_tex_height; + + // top-left corner + GLTexture::render_sub_texture(tex_id, left, internal_left, internal_top, top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + + // top edge + GLTexture::render_sub_texture(tex_id, internal_left, internal_right, internal_top, top, { { internal_left_uv, internal_top_uv }, { internal_right_uv, internal_top_uv }, { internal_right_uv, top_uv }, { internal_left_uv, top_uv } }); + + // top-right corner + GLTexture::render_sub_texture(tex_id, internal_right, right, internal_top, top, { { internal_right_uv, internal_top_uv }, { right_uv, internal_top_uv }, { right_uv, top_uv }, { internal_right_uv, top_uv } }); + + // center-left edge + GLTexture::render_sub_texture(tex_id, left, internal_left, internal_bottom, internal_top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + + // center + GLTexture::render_sub_texture(tex_id, internal_left, internal_right, internal_bottom, internal_top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + + // center-right edge + GLTexture::render_sub_texture(tex_id, internal_right, right, internal_bottom, internal_top, { { internal_right_uv, internal_bottom_uv }, { right_uv, internal_bottom_uv }, { right_uv, internal_top_uv }, { internal_right_uv, internal_top_uv } }); + + // bottom-left corner + GLTexture::render_sub_texture(tex_id, left, internal_left, bottom, internal_bottom, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + + // bottom edge + GLTexture::render_sub_texture(tex_id, internal_left, internal_right, bottom, internal_bottom, { { internal_left_uv, bottom_uv }, { internal_right_uv, bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_left_uv, internal_bottom_uv } }); + + // bottom-right corner + GLTexture::render_sub_texture(tex_id, internal_right, right, bottom, internal_bottom, { { internal_right_uv, bottom_uv }, { right_uv, bottom_uv }, { right_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv } }); + } +} + +void GLGizmosManager::do_render_overlay() const { if (m_gizmos.empty()) return; - float cnv_w = (float)canvas.get_canvas_size().get_width(); - float cnv_h = (float)canvas.get_canvas_size().get_height(); - float zoom = canvas.get_camera().zoom; + float cnv_w = (float)m_parent.get_canvas_size().get_width(); + float cnv_h = (float)m_parent.get_canvas_size().get_height(); + float zoom = (float)m_parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; float height = get_total_overlay_height(); float width = get_total_overlay_width(); -#if ENABLE_SVG_ICONS float scaled_border = m_overlay_border * m_overlay_scale * inv_zoom; -#else - float scaled_border = m_overlay_border * inv_zoom; -#endif // ENABLE_SVG_ICONS float top_x = (-0.5f * cnv_w) * inv_zoom; float top_y = (0.5f * height) * inv_zoom; @@ -958,73 +972,8 @@ void GLGizmosManager::do_render_overlay(const GLCanvas3D& canvas, const Selectio float right = left + width * inv_zoom; float bottom = top - height * inv_zoom; - // renders background - unsigned int bg_tex_id = m_background_texture.texture.get_id(); - float bg_tex_width = (float)m_background_texture.texture.get_width(); - float bg_tex_height = (float)m_background_texture.texture.get_height(); - if ((bg_tex_id != 0) && (bg_tex_width > 0) && (bg_tex_height > 0)) - { - float inv_bg_tex_width = (bg_tex_width != 0.0f) ? 1.0f / bg_tex_width : 0.0f; - float inv_bg_tex_height = (bg_tex_height != 0.0f) ? 1.0f / bg_tex_height : 0.0f; + render_background(left, top, right, bottom, scaled_border); - float bg_uv_left = 0.0f; - float bg_uv_right = 1.0f; - float bg_uv_top = 1.0f; - float bg_uv_bottom = 0.0f; - - float bg_left = left; - float bg_right = right; - float bg_top = top; - float bg_bottom = bottom; - float bg_width = right - left; - float bg_height = top - bottom; - float bg_min_size = std::min(bg_width, bg_height); - - float bg_uv_i_left = (float)m_background_texture.metadata.left * inv_bg_tex_width; - float bg_uv_i_right = 1.0f - (float)m_background_texture.metadata.right * inv_bg_tex_width; - float bg_uv_i_top = 1.0f - (float)m_background_texture.metadata.top * inv_bg_tex_height; - float bg_uv_i_bottom = (float)m_background_texture.metadata.bottom * inv_bg_tex_height; - - float bg_i_left = bg_left + scaled_border; - float bg_i_right = bg_right - scaled_border; - float bg_i_top = bg_top - scaled_border; - float bg_i_bottom = bg_bottom + scaled_border; - - bg_uv_left = bg_uv_i_left; - bg_i_left = bg_left; - - if ((m_overlay_border > 0) && (bg_uv_top != bg_uv_i_top)) - { - if (bg_uv_left != bg_uv_i_left) - GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_top, bg_top, { { bg_uv_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_top }, { bg_uv_left, bg_uv_top } }); - - GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_top, bg_top, { { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_top }, { bg_uv_i_left, bg_uv_top } }); - - if (bg_uv_right != bg_uv_i_right) - GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_top, bg_top, { { bg_uv_i_right, bg_uv_i_top }, { bg_uv_right, bg_uv_i_top }, { bg_uv_right, bg_uv_top }, { bg_uv_i_right, bg_uv_top } }); - } - - if ((m_overlay_border > 0) && (bg_uv_left != bg_uv_i_left)) - GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_bottom, bg_i_top, { { bg_uv_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_left, bg_uv_i_top } }); - - GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_bottom, bg_i_top, { { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top } }); - - if ((m_overlay_border > 0) && (bg_uv_right != bg_uv_i_right)) - GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_bottom, bg_i_top, { { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top } }); - - if ((m_overlay_border > 0) && (bg_uv_bottom != bg_uv_i_bottom)) - { - if (bg_uv_left != bg_uv_i_left) - GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_bottom, bg_i_bottom, { { bg_uv_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_left, bg_uv_i_bottom } }); - - GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_bottom, bg_i_bottom, { { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_right, bg_uv_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom } }); - - if (bg_uv_right != bg_uv_i_right) - GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_bottom, bg_i_bottom, { { bg_uv_i_right, bg_uv_bottom }, { bg_uv_right, bg_uv_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom } }); - } - } - -#if ENABLE_SVG_ICONS top_x += scaled_border; top_y -= scaled_border; float scaled_gap_y = m_overlay_gap_y * m_overlay_scale * inv_zoom; @@ -1036,21 +985,9 @@ void GLGizmosManager::do_render_overlay(const GLCanvas3D& canvas, const Selectio unsigned int tex_height = m_icons_texture.get_height(); float inv_tex_width = (tex_width != 0) ? 1.0f / (float)tex_width : 0.0f; float inv_tex_height = (tex_height != 0) ? 1.0f / (float)tex_height : 0.0f; -#else - top_x += m_overlay_border * inv_zoom; - top_y -= m_overlay_border * inv_zoom; - float scaled_gap_y = m_overlay_gap_y * inv_zoom; - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale * inv_zoom; - unsigned int icons_texture_id = m_icons_texture.texture.get_id(); - unsigned int texture_size = m_icons_texture.texture.get_width(); - float inv_texture_size = (texture_size != 0) ? 1.0f / (float)texture_size : 0.0f; -#endif // ENABLE_SVG_ICONS - -#if ENABLE_SVG_ICONS if ((icons_texture_id == 0) || (tex_width <= 0) || (tex_height <= 0)) return; -#endif // ENABLE_SVG_ICONS for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { @@ -1060,78 +997,44 @@ void GLGizmosManager::do_render_overlay(const GLCanvas3D& canvas, const Selectio unsigned int sprite_id = it->second->get_sprite_id(); GLGizmoBase::EState state = it->second->get_state(); -#if ENABLE_SVG_ICONS float u_icon_size = m_overlay_icons_size * m_overlay_scale * inv_tex_width; float v_icon_size = m_overlay_icons_size * m_overlay_scale * inv_tex_height; float v_top = sprite_id * v_icon_size; float u_left = state * u_icon_size; float v_bottom = v_top + v_icon_size; float u_right = u_left + u_icon_size; -#else - float uv_icon_size = (float)m_icons_texture.metadata.icon_size * inv_texture_size; - float v_top = sprite_id * uv_icon_size; - float u_left = state * uv_icon_size; - float v_bottom = v_top + uv_icon_size; - float u_right = u_left + uv_icon_size; -#endif // ENABLE_SVG_ICONS GLTexture::render_sub_texture(icons_texture_id, top_x, top_x + scaled_icons_size, top_y - scaled_icons_size, top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } }); if (it->second->get_state() == GLGizmoBase::On) { - float toolbar_top = (float)cnv_h - canvas.get_view_toolbar_height(); -#if ENABLE_SVG_ICONS - it->second->render_input_window(width, 0.5f * cnv_h - top_y * zoom, toolbar_top, selection); -#else - it->second->render_input_window(2.0f * m_overlay_border + icon_size * zoom, 0.5f * cnv_h - top_y * zoom, toolbar_top, selection); -#endif // ENABLE_SVG_ICONS + float toolbar_top = (float)cnv_h - m_parent.get_view_toolbar_height(); + it->second->render_input_window(width, 0.5f * cnv_h - top_y * zoom, toolbar_top); } -#if ENABLE_SVG_ICONS top_y -= scaled_stride_y; -#else - top_y -= (scaled_icons_size + scaled_gap_y); -#endif // ENABLE_SVG_ICONS } } float GLGizmosManager::get_total_overlay_height() const { -#if ENABLE_SVG_ICONS float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; float scaled_border = m_overlay_border * m_overlay_scale; float scaled_gap_y = m_overlay_gap_y * m_overlay_scale; float scaled_stride_y = scaled_icons_size + scaled_gap_y; float height = 2.0f * scaled_border; -#else - float height = 2.0f * m_overlay_border; - - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale; -#endif // ENABLE_SVG_ICONS for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) continue; -#if ENABLE_SVG_ICONS height += scaled_stride_y; -#else - height += (scaled_icons_size + m_overlay_gap_y); -#endif // ENABLE_SVG_ICONS } -#if ENABLE_SVG_ICONS return height - scaled_gap_y; -#else - return height - m_overlay_gap_y; -#endif // ENABLE_SVG_ICONS } float GLGizmosManager::get_total_overlay_width() const { -#if ENABLE_SVG_ICONS return (2.0f * m_overlay_border + m_overlay_icons_size) * m_overlay_scale; -#else - return (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale + 2.0f * m_overlay_border; -#endif // ENABLE_SVG_ICONS } GLGizmoBase* GLGizmosManager::get_current() const @@ -1140,7 +1043,6 @@ GLGizmoBase* GLGizmosManager::get_current() const return (it != m_gizmos.end()) ? it->second : nullptr; } -#if ENABLE_SVG_ICONS bool GLGizmosManager::generate_icons_texture() const { std::string path = resources_dir() + "/icons/"; @@ -1160,155 +1062,144 @@ bool GLGizmosManager::generate_icons_texture() const states.push_back(std::make_pair(0, false)); states.push_back(std::make_pair(0, true)); - bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, (unsigned int)(m_overlay_icons_size * m_overlay_scale)); + bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, (unsigned int)(m_overlay_icons_size * m_overlay_scale), true); if (res) m_icons_texture_dirty = false; return res; } -#endif // ENABLE_SVG_ICONS -void GLGizmosManager::update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const Selection& selection) +void GLGizmosManager::update_on_off_state(const Vec2d& mouse_pos) { if (!m_enabled) return; - float cnv_h = (float)canvas.get_canvas_size().get_height(); + float cnv_h = (float)m_parent.get_canvas_size().get_height(); float height = get_total_overlay_height(); -#if ENABLE_SVG_ICONS float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; float scaled_border = m_overlay_border * m_overlay_scale; float scaled_gap_y = m_overlay_gap_y * m_overlay_scale; float scaled_stride_y = scaled_icons_size + scaled_gap_y; float top_y = 0.5f * (cnv_h - height) + scaled_border; -#else - float top_y = 0.5f * (cnv_h - height) + m_overlay_border; - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale; -#endif // ENABLE_SVG_ICONS - for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) + auto inside = [scaled_border, scaled_icons_size, &mouse_pos](float top_y) { + return (scaled_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= scaled_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size); + }; + + bool could_activate = true; + for (std::pair &type_and_gizmo : m_gizmos) { - if ((it->second == nullptr) || !it->second->is_selectable()) + GLGizmoBase *gizmo = type_and_gizmo.second; + if ((gizmo == nullptr) || !gizmo->is_selectable()) continue; -#if ENABLE_SVG_ICONS - bool inside = (scaled_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= scaled_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size); -#else - bool inside = (m_overlay_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= m_overlay_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size); -#endif // ENABLE_SVG_ICONS - if (it->second->is_activable(selection) && inside) - { - if ((it->second->get_state() == GLGizmoBase::On)) - { - it->second->set_state(GLGizmoBase::Hover); - m_current = Undefined; - } - else if ((it->second->get_state() == GLGizmoBase::Hover)) - { - it->second->set_state(GLGizmoBase::On); - m_current = it->first; + if (! (gizmo->is_activable() && inside(top_y))) { + gizmo->set_state(GLGizmoBase::Off); + if (gizmo->get_state() != GLGizmoBase::Off) { + // Gizmo refused to leave it's active state. Don't try to select + could_activate = false; } } - else - it->second->set_state(GLGizmoBase::Off); -#if ENABLE_SVG_ICONS top_y += scaled_stride_y; -#else - top_y += (scaled_icons_size + m_overlay_gap_y); -#endif // ENABLE_SVG_ICONS } - GizmosMap::iterator it = m_gizmos.find(m_current); - if ((it != m_gizmos.end()) && (it->second != nullptr) && (it->second->get_state() != GLGizmoBase::On)) - it->second->set_state(GLGizmoBase::On); + // We may change m_current soon. If we did it during following loop, gizmos that take undo/redo snapshots + // in their on_set_state function could snapshot a state with the new gizmo already active. + // Therefore, just remember what needs to be done and actually change m_current afterwards. + EType new_current = m_current; + top_y = 0.5f * (cnv_h - height) + scaled_border; + for (std::pair &type_and_gizmo : m_gizmos) + { + GLGizmoBase *gizmo = type_and_gizmo.second; + if ((gizmo == nullptr) || !gizmo->is_selectable()) + continue; + + if (gizmo->is_activable() && inside(top_y)) + { + if ((gizmo->get_state() == GLGizmoBase::On)) + { + gizmo->set_state(GLGizmoBase::Off); + if (gizmo->get_state() == GLGizmoBase::Off) { + gizmo->set_state(GLGizmoBase::Hover); + new_current = Undefined; + } + } + else if ((gizmo->get_state() == GLGizmoBase::Hover) && could_activate) + { + gizmo->set_state(GLGizmoBase::On); + new_current = type_and_gizmo.first; + } + } + + top_y += scaled_stride_y; + } + m_current = new_current; + + if (could_activate) { + GizmosMap::iterator it = m_gizmos.find(m_current); + if ((it != m_gizmos.end()) && (it->second != nullptr) && (it->second->get_state() != GLGizmoBase::On)) + it->second->set_state(GLGizmoBase::On); + } } -std::string GLGizmosManager::update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos) +std::string GLGizmosManager::update_hover_state(const Vec2d& mouse_pos) { std::string name = ""; if (!m_enabled) return name; - const Selection& selection = canvas.get_selection(); - - float cnv_h = (float)canvas.get_canvas_size().get_height(); + float cnv_h = (float)m_parent.get_canvas_size().get_height(); float height = get_total_overlay_height(); -#if ENABLE_SVG_ICONS float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; float scaled_border = m_overlay_border * m_overlay_scale; float scaled_gap_y = m_overlay_gap_y * m_overlay_scale; float scaled_stride_y = scaled_icons_size + scaled_gap_y; float top_y = 0.5f * (cnv_h - height) + scaled_border; -#else - float top_y = 0.5f * (cnv_h - height) + m_overlay_border; - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale; -#endif // ENABLE_SVG_ICONS for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) continue; -#if ENABLE_SVG_ICONS bool inside = (scaled_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= scaled_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size); -#else - bool inside = (m_overlay_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= m_overlay_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size); -#endif // ENABLE_SVG_ICONS if (inside) name = it->second->get_name(); - if (it->second->is_activable(selection) && (it->second->get_state() != GLGizmoBase::On)) + if (it->second->is_activable() && (it->second->get_state() != GLGizmoBase::On)) it->second->set_state(inside ? GLGizmoBase::Hover : GLGizmoBase::Off); -#if ENABLE_SVG_ICONS top_y += scaled_stride_y; -#else - top_y += (scaled_icons_size + m_overlay_gap_y); -#endif // ENABLE_SVG_ICONS } return name; } -bool GLGizmosManager::overlay_contains_mouse(const GLCanvas3D& canvas, const Vec2d& mouse_pos) const +bool GLGizmosManager::overlay_contains_mouse(const Vec2d& mouse_pos) const { if (!m_enabled) return false; - float cnv_h = (float)canvas.get_canvas_size().get_height(); + float cnv_h = (float)m_parent.get_canvas_size().get_height(); float height = get_total_overlay_height(); -#if ENABLE_SVG_ICONS float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; float scaled_border = m_overlay_border * m_overlay_scale; float scaled_gap_y = m_overlay_gap_y * m_overlay_scale; float scaled_stride_y = scaled_icons_size + scaled_gap_y; float top_y = 0.5f * (cnv_h - height) + scaled_border; -#else - float top_y = 0.5f * (cnv_h - height) + m_overlay_border; - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale; -#endif // ENABLE_SVG_ICONS for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) continue; -#if ENABLE_SVG_ICONS if ((scaled_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= scaled_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size)) -#else - if ((m_overlay_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= m_overlay_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size)) -#endif // ENABLE_SVG_ICONS return true; -#if ENABLE_SVG_ICONS top_y += scaled_stride_y; -#else - top_y += (scaled_icons_size + m_overlay_gap_y); -#endif // ENABLE_SVG_ICONS } return false; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index 1e42a29e68..c0adeb957d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -4,14 +4,18 @@ #include "slic3r/GUI/GLTexture.hpp" #include "slic3r/GUI/GLToolbar.hpp" #include "slic3r/GUI/Gizmos/GLGizmos.hpp" +#include "libslic3r/ObjectID.hpp" #include namespace Slic3r { + +namespace UndoRedo { +struct Snapshot; +} + namespace GUI { -class Selection; -class GLGizmoBase; class GLCanvas3D; class ClippingPlane; @@ -43,12 +47,10 @@ public: float get_height() const { return m_top - m_bottom; } }; -class GLGizmosManager +class GLGizmosManager : public Slic3r::ObjectBase { public: -#if ENABLE_SVG_ICONS static const float Default_Icons_Size; -#endif // ENABLE_SVG_ICONS enum EType : unsigned char { @@ -63,24 +65,17 @@ public: }; private: + GLCanvas3D& m_parent; bool m_enabled; typedef std::map GizmosMap; GizmosMap m_gizmos; -#if ENABLE_SVG_ICONS mutable GLTexture m_icons_texture; mutable bool m_icons_texture_dirty; -#else - ItemsIconsTexture m_icons_texture; -#endif // ENABLE_SVG_ICONS BackgroundTexture m_background_texture; EType m_current; -#if ENABLE_SVG_ICONS float m_overlay_icons_size; float m_overlay_scale; -#else - float m_overlay_icons_scale; -#endif // ENABLE_SVG_ICONS float m_overlay_border; float m_overlay_gap_y; @@ -99,38 +94,71 @@ private: MouseCapture m_mouse_capture; std::string m_tooltip; + bool m_serializing; public: - GLGizmosManager(); + explicit GLGizmosManager(GLCanvas3D& parent); ~GLGizmosManager(); - bool init(GLCanvas3D& parent); + bool init(); + + template + void load(Archive& ar) + { + if (!m_enabled) + return; + + m_serializing = true; + + ar(m_current); + + GLGizmoBase* curr = get_current(); + for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { + GLGizmoBase* gizmo = it->second; + if (gizmo != nullptr) { + gizmo->set_hover_id(-1); + gizmo->set_state((it->second == curr) ? GLGizmoBase::On : GLGizmoBase::Off); + if (gizmo == curr) + gizmo->load(ar); + } + } + } + + template + void save(Archive& ar) const + { + if (!m_enabled) + return; + + ar(m_current); + + GLGizmoBase* curr = get_current(); + if (curr != nullptr) + curr->save(ar); + } bool is_enabled() const { return m_enabled; } void set_enabled(bool enable) { m_enabled = enable; } -#if ENABLE_SVG_ICONS void set_overlay_icon_size(float size); -#endif // ENABLE_SVG_ICONS void set_overlay_scale(float scale); - void refresh_on_off_state(const Selection& selection); + void refresh_on_off_state(); void reset_all_states(); void set_hover_id(int id); void enable_grabber(EType type, unsigned int id, bool enable); - void update(const Linef3& mouse_ray, const Selection& selection, const Point* mouse_pos = nullptr); - void update_data(GLCanvas3D& canvas); + void update(const Linef3& mouse_ray, const Point& mouse_pos); + void update_data(); - Rect get_reset_rect_viewport(const GLCanvas3D& canvas) const; EType get_current_type() const { return m_current; } bool is_running() const; - bool handle_shortcut(int key, const Selection& selection); + bool handle_shortcut(int key); bool is_dragging() const; - void start_dragging(const Selection& selection); + void start_dragging(); void stop_dragging(); Vec3d get_displacement() const; @@ -147,43 +175,50 @@ public: void set_flattening_data(const ModelObject* model_object); - void set_sla_support_data(ModelObject* model_object, const Selection& selection); + void set_sla_support_data(ModelObject* model_object); bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position = Vec2d::Zero(), bool shift_down = false, bool alt_down = false, bool control_down = false); ClippingPlane get_sla_clipping_plane() const; + bool wants_reslice_supports_on_undo() const; - void render_current_gizmo(const Selection& selection) const; - void render_current_gizmo_for_picking_pass(const Selection& selection) const; + void render_current_gizmo() const; + void render_current_gizmo_for_picking_pass() const; - void render_overlay(const GLCanvas3D& canvas, const Selection& selection) const; + void render_overlay() const; const std::string& get_tooltip() const { return m_tooltip; } - bool on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas); - bool on_mouse_wheel(wxMouseEvent& evt, GLCanvas3D& canvas); - bool on_char(wxKeyEvent& evt, GLCanvas3D& canvas); - bool on_key(wxKeyEvent& evt, GLCanvas3D& canvas); + bool on_mouse(wxMouseEvent& evt); + bool on_mouse_wheel(wxMouseEvent& evt); + bool on_char(wxKeyEvent& evt); + bool on_key(wxKeyEvent& evt); + + void update_after_undo_redo(const UndoRedo::Snapshot& snapshot); private: void reset(); - void do_render_overlay(const GLCanvas3D& canvas, const Selection& selection) const; + void render_background(float left, float top, float right, float bottom, float border) const; + void do_render_overlay() const; float get_total_overlay_height() const; float get_total_overlay_width() const; GLGizmoBase* get_current() const; -#if ENABLE_SVG_ICONS bool generate_icons_texture() const; -#endif // ENABLE_SVG_ICONS - void update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const Selection& selection); - std::string update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos); - bool overlay_contains_mouse(const GLCanvas3D& canvas, const Vec2d& mouse_pos) const; + void update_on_off_state(const Vec2d& mouse_pos); + std::string update_hover_state(const Vec2d& mouse_pos); + bool overlay_contains_mouse(const Vec2d& mouse_pos) const; bool grabber_contains_mouse() const; }; } // namespace GUI } // namespace Slic3r +namespace cereal +{ + template struct specialize {}; +} + #endif // slic3r_GUI_GLGizmosManager_hpp_ diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 67016077c7..8e4d9eebfa 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -206,7 +206,7 @@ void ImGuiWrapper::new_frame() } if (m_font_texture == 0) { - init_font(); + init_font(true); } ImGui::NewFrame(); @@ -233,9 +233,9 @@ ImVec2 ImGuiWrapper::calc_text_size(const wxString &text) return size; } -void ImGuiWrapper::set_next_window_pos(float x, float y, int flag) +void ImGuiWrapper::set_next_window_pos(float x, float y, int flag, float pivot_x, float pivot_y) { - ImGui::SetNextWindowPos(ImVec2(x, y), (ImGuiCond)flag); + ImGui::SetNextWindowPos(ImVec2(x, y), (ImGuiCond)flag, ImVec2(pivot_x, pivot_y)); ImGui::SetNextWindowSize(ImVec2(0.0, 0.0)); } @@ -326,9 +326,9 @@ bool ImGuiWrapper::combo(const wxString& label, const std::vector& int selection_out = -1; bool res = false; - const char *selection_str = selection < options.size() ? options[selection].c_str() : ""; + const char *selection_str = selection < (int)options.size() ? options[selection].c_str() : ""; if (ImGui::BeginCombo("", selection_str)) { - for (int i = 0; i < options.size(); i++) { + for (int i = 0; i < (int)options.size(); i++) { if (ImGui::Selectable(options[i].c_str(), i == selection)) { selection_out = i; } @@ -342,6 +342,32 @@ bool ImGuiWrapper::combo(const wxString& label, const std::vector& return res; } +bool ImGuiWrapper::undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool , int , const char**), int& hovered, int& selected) +{ + bool is_hovered = false; + ImGui::ListBoxHeader("", size); + + int i=0; + const char* item_text; + while (items_getter(is_undo, i, &item_text)) + { + ImGui::Selectable(item_text, i < hovered); + + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", item_text); + hovered = i; + is_hovered = true; + } + + if (ImGui::IsItemClicked()) + selected = i; + i++; + } + + ImGui::ListBoxFooter(); + return is_hovered; +} + void ImGuiWrapper::disabled_begin(bool disabled) { wxCHECK_RET(!m_disabled, "ImGUI: Unbalanced disabled_begin() call"); @@ -383,7 +409,7 @@ bool ImGuiWrapper::want_any_input() const return io.WantCaptureMouse || io.WantCaptureKeyboard || io.WantTextInput; } -void ImGuiWrapper::init_font() +void ImGuiWrapper::init_font(bool compress) { destroy_font(); @@ -412,7 +438,10 @@ void ImGuiWrapper::init_font() glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); glsafe(::glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels)); + if (compress && GLEW_EXT_texture_compression_s3tc) + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels)); + else + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels)); // Store our identifier io.Fonts->TexID = (ImTextureID)(intptr_t)m_font_texture; diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 37ef90ff35..c6550351e7 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -51,7 +51,7 @@ public: ImVec2 scaled(float x, float y) const { return ImVec2(x * m_font_size, y * m_font_size); } ImVec2 calc_text_size(const wxString &text); - void set_next_window_pos(float x, float y, int flag); + void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f); void set_next_window_bg_alpha(float alpha); bool begin(const std::string &name, int flags = 0); @@ -67,6 +67,7 @@ public: void text(const std::string &label); void text(const wxString &label); bool combo(const wxString& label, const std::vector& options, int& selection); // Use -1 to not mark any option as selected + bool undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool, int, const char**), int& hovered, int& selected); void disabled_begin(bool disabled); void disabled_end(); @@ -77,7 +78,7 @@ public: bool want_any_input() const; private: - void init_font(); + void init_font(bool compress); void init_input(); void init_style(); void render_draw_data(ImDrawData *draw_data); diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 347dac13ea..2d5454ce99 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -3,26 +3,27 @@ #include "libslic3r/Utils.hpp" #include "GUI.hpp" #include +#include #include "GUI_App.hpp" #include "wxExtensions.hpp" -namespace Slic3r { +namespace Slic3r { namespace GUI { KBShortcutsDialog::KBShortcutsDialog() - : DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(L("Keyboard Shortcuts")), + : DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(L("Keyboard Shortcuts")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { - SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - auto main_sizer = new wxBoxSizer(wxVERTICAL); + auto main_sizer = new wxBoxSizer(wxVERTICAL); // logo m_logo_bmp = ScalableBitmap(this, "PrusaSlicer_32px.png", 32); // fonts const wxFont& font = wxGetApp().normal_font(); - const wxFont& bold_font = wxGetApp().bold_font(); + const wxFont& bold_font = wxGetApp().bold_font(); SetFont(font); wxFont head_font = bold_font; @@ -34,7 +35,9 @@ KBShortcutsDialog::KBShortcutsDialog() fill_shortcuts(); - auto panel = new wxPanel(this); + panel = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, get_size()); + panel->SetScrollbars(1, 20, 1, 2); + auto main_grid_sizer = new wxFlexGridSizer(2, 10, 10); panel->SetSizer(main_grid_sizer); main_sizer->Add(panel, 1, wxEXPAND | wxALL, 0); @@ -78,17 +81,17 @@ KBShortcutsDialog::KBShortcutsDialog() grid_sizer->Add(description, -1, wxALIGN_CENTRE_VERTICAL); } } - + wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK); this->SetEscapeId(wxID_OK); this->Bind(wxEVT_BUTTON, &KBShortcutsDialog::onCloseDialog, this, wxID_OK); main_sizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 15); - + this->Bind(wxEVT_LEFT_DOWN, &KBShortcutsDialog::onCloseDialog, this); - SetSizer(main_sizer); - main_sizer->SetSizeHints(this); + SetSizer(main_sizer); + main_sizer->SetSizeHints(this); } void KBShortcutsDialog::fill_shortcuts() @@ -132,6 +135,7 @@ void KBShortcutsDialog::fill_shortcuts() plater_shortcuts.reserve(20); plater_shortcuts.push_back(Shortcut("A", L("Arrange"))); + plater_shortcuts.push_back(Shortcut("Shift+A", L("Arrange selection"))); plater_shortcuts.push_back(Shortcut(ctrl+"A", L("Select All objects"))); plater_shortcuts.push_back(Shortcut("Del", L("Delete selected"))); plater_shortcuts.push_back(Shortcut(ctrl+"Del", L("Delete All"))); @@ -147,22 +151,26 @@ void KBShortcutsDialog::fill_shortcuts() plater_shortcuts.push_back(Shortcut("F", L("Press to scale selection to fit print volume\nin Gizmo scale"))); plater_shortcuts.push_back(Shortcut(alt, L("Press to activate deselection rectangle\nor to scale or rotate selected objects\naround their own center"))); plater_shortcuts.push_back(Shortcut(ctrl, L("Press to activate one direction scaling in Gizmo scale"))); + plater_shortcuts.push_back(Shortcut("K", L("Change camera type"))); plater_shortcuts.push_back(Shortcut("B", L("Zoom to Bed"))); plater_shortcuts.push_back(Shortcut("Z", L("Zoom to all objects in scene, if none selected"))); plater_shortcuts.push_back(Shortcut("Z", L("Zoom to selected object"))); plater_shortcuts.push_back(Shortcut("I", L("Zoom in"))); plater_shortcuts.push_back(Shortcut("O", L("Zoom out"))); plater_shortcuts.push_back(Shortcut("ESC", L("Unselect gizmo / Clear selection"))); +#if ENABLE_RENDER_PICKING_PASS + plater_shortcuts.push_back(Shortcut("T", L("Toggle picking pass texture rendering on/off"))); +#endif // ENABLE_RENDER_PICKING_PASS m_full_shortcuts.push_back(std::make_pair(_(L("Plater Shortcuts")), std::make_pair(plater_shortcuts, szRight))); // Shortcuts gizmo_shortcuts; // gizmo_shortcuts.reserve(2); -// +// // gizmo_shortcuts.push_back(Shortcut("Shift+", L("Press to snap by 5% in Gizmo Scale\n or by 1mm in Gizmo Move"))); // gizmo_shortcuts.push_back(Shortcut(alt, L("Press to scale or rotate selected objects around their own center"))); -// +// // m_full_shortcuts.push_back(std::make_pair(_(L("Gizmo Shortcuts")), std::make_pair(gizmo_shortcuts, 1))); @@ -173,6 +181,7 @@ void KBShortcutsDialog::fill_shortcuts() preview_shortcuts.push_back(Shortcut(L("Arrow Down"), L("Lower Layer"))); preview_shortcuts.push_back(Shortcut("U", L("Upper Layer"))); preview_shortcuts.push_back(Shortcut("D", L("Lower Layer"))); + preview_shortcuts.push_back(Shortcut("L", L("Show/Hide (L)egend"))); m_full_shortcuts.push_back(std::make_pair(_(L("Preview Shortcuts")), std::make_pair(preview_shortcuts, szLeft))); @@ -201,7 +210,9 @@ void KBShortcutsDialog::on_dpi_changed(const wxRect &suggested_rect) msw_buttons_rescale(this, em, { wxID_OK }); - const wxSize& size = wxSize(85 * em, 75 * em); + wxSize size = get_size(); + + panel->SetMinSize(size); SetMinSize(size); Fit(); @@ -214,5 +225,30 @@ void KBShortcutsDialog::onCloseDialog(wxEvent &) this->EndModal(wxID_CLOSE); } +wxSize KBShortcutsDialog::get_size() +{ + wxTopLevelWindow* window = Slic3r::GUI::find_toplevel_parent(this); + const int display_idx = wxDisplay::GetFromWindow(window); + wxRect display; + if (display_idx == wxNOT_FOUND) { + display = wxDisplay(0u).GetClientArea(); + window->Move(display.GetTopLeft()); + } + else { + display = wxDisplay(display_idx).GetClientArea(); + } + + const int em = em_unit(); + wxSize dialog_size = wxSize(90 * em, 85 * em); + + const int margin = 10 * em; + if (dialog_size.x > display.GetWidth()) + dialog_size.x = display.GetWidth() - margin; + if (dialog_size.y > display.GetHeight()) + dialog_size.y = display.GetHeight() - margin; + + return dialog_size; +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/KBShortcutsDialog.hpp b/src/slic3r/GUI/KBShortcutsDialog.hpp index 66fe7c399b..7ac28778b5 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.hpp +++ b/src/slic3r/GUI/KBShortcutsDialog.hpp @@ -23,10 +23,10 @@ class KBShortcutsDialog : public DPIDialog typedef std::vector< Shortcut > Shortcuts; typedef std::vector< std::pair> > ShortcutsVec; - wxString text_info {wxEmptyString}; + wxScrolledWindow* panel; - ShortcutsVec m_full_shortcuts; - ScalableBitmap m_logo_bmp; + ShortcutsVec m_full_shortcuts; + ScalableBitmap m_logo_bmp; std::vector m_head_bitmaps; public: @@ -39,6 +39,7 @@ protected: private: void onCloseDialog(wxEvent &); + wxSize get_size(); }; } // namespace GUI diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 6091f10a14..6e3d1406bc 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -35,13 +35,16 @@ namespace GUI { MainFrame::MainFrame() : DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE, "mainframe"), m_printhost_queue_dlg(new PrintHostQueueDialog(this)) + , m_recent_projects(9) { // Fonts were created by the DPIFrame constructor for the monitor, on which the window opened. wxGetApp().update_fonts(this); +/* #ifndef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList this->SetFont(this->normal_font()); #endif - + // Font is already set in DPIFrame constructor +*/ // Load the icon either from the exe, or from the ico file. #if _WIN32 { @@ -54,7 +57,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S #endif // _WIN32 // initialize status bar - m_statusbar = new ProgressStatusBar(this); + m_statusbar.reset(new ProgressStatusBar(this)); m_statusbar->embed(this); m_statusbar->set_status_text(_(L("Version")) + " " + SLIC3R_VERSION + @@ -103,6 +106,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S event.Veto(); return; } + + if(m_plater) m_plater->stop_jobs(); // Weird things happen as the Paint messages are floating around the windows being destructed. // Avoid the Paint messages by hiding the main window. @@ -138,6 +143,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S update_ui_from_settings(); // FIXME (?) } +MainFrame::~MainFrame() = default; + void MainFrame::update_title() { wxString title = wxEmptyString; @@ -240,6 +247,11 @@ bool MainFrame::can_export_model() const return (m_plater != nullptr) && !m_plater->model().objects.empty(); } +bool MainFrame::can_export_toolpaths() const +{ + return (m_plater != nullptr) && (m_plater->printer_technology() == ptFFF) && m_plater->is_preview_shown() && m_plater->is_preview_loaded() && m_plater->has_toolpaths_to_export(); +} + bool MainFrame::can_export_supports() const { if ((m_plater == nullptr) || (m_plater->printer_technology() != ptSLA) || m_plater->model().objects.empty()) @@ -274,6 +286,18 @@ bool MainFrame::can_export_gcode() const return true; } +bool MainFrame::can_send_gcode() const +{ + if (m_plater == nullptr) + return false; + + if (m_plater->model().objects.empty()) + return false; + + const auto print_host_opt = wxGetApp().preset_bundle->printers.get_edited_preset().config.option("print_host"); + return print_host_opt != nullptr && !print_host_opt->value.empty(); +} + bool MainFrame::can_slice() const { bool bg_proc = wxGetApp().app_config->get("background_processing") == "1"; @@ -379,6 +403,41 @@ void MainFrame::init_menubar() append_menu_item(fileMenu, wxID_ANY, _(L("&Open Project")) + dots + "\tCtrl+O", _(L("Open a project file")), [this](wxCommandEvent&) { if (m_plater) m_plater->load_project(); }, menu_icon("open"), nullptr, [this](){return m_plater != nullptr; }, this); + + wxMenu* recent_projects_menu = new wxMenu(); + wxMenuItem* recent_projects_submenu = append_submenu(fileMenu, recent_projects_menu, wxID_ANY, _(L("Recent projects")), ""); + m_recent_projects.UseMenu(recent_projects_menu); + Bind(wxEVT_MENU, [this](wxCommandEvent& evt) { + size_t file_id = evt.GetId() - wxID_FILE1; + wxString filename = m_recent_projects.GetHistoryFile(file_id); + if (wxFileExists(filename)) + m_plater->load_project(filename); + else + { + wxMessageDialog msg(this, _(L("The selected project is no more available")), _(L("Error"))); + msg.ShowModal(); + + m_recent_projects.RemoveFileFromHistory(file_id); + std::vector recent_projects; + size_t count = m_recent_projects.GetCount(); + for (size_t i = 0; i < count; ++i) + { + recent_projects.push_back(into_u8(m_recent_projects.GetHistoryFile(i))); + } + wxGetApp().app_config->set_recent_projects(recent_projects); + wxGetApp().app_config->save(); + } + }, wxID_FILE1, wxID_FILE9); + + std::vector recent_projects = wxGetApp().app_config->get_recent_projects(); + std::reverse(recent_projects.begin(), recent_projects.end()); + for (const std::string& project : recent_projects) + { + m_recent_projects.AddFileToHistory(from_u8(project)); + } + + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_recent_projects.GetCount() > 0); }, recent_projects_submenu->GetId()); + append_menu_item(fileMenu, wxID_ANY, _(L("&Save Project")) + "\tCtrl+S", _(L("Save current project file")), [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(into_path(m_plater->get_project_filename(".3mf"))); }, menu_icon("save"), nullptr, [this](){return m_plater != nullptr && can_save(); }, this); @@ -411,17 +470,25 @@ void MainFrame::init_menubar() [this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(); }, menu_icon("export_gcode"), nullptr, [this](){return can_export_gcode(); }, this); m_changeable_menu_items.push_back(item_export_gcode); + wxMenuItem* item_send_gcode = append_menu_item(export_menu, wxID_ANY, _(L("S&end G-code")) + dots +"\tCtrl+Shift+G", _(L("Send to print current plate as G-code")), + [this](wxCommandEvent&) { if (m_plater) m_plater->send_gcode(); }, menu_icon("export_gcode"), nullptr, + [this](){return can_send_gcode(); }, this); + m_changeable_menu_items.push_back(item_send_gcode); export_menu->AppendSeparator(); append_menu_item(export_menu, wxID_ANY, _(L("Export plate as &STL")) + dots, _(L("Export current plate as STL")), [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(); }, menu_icon("export_plater"), nullptr, [this](){return can_export_model(); }, this); - append_menu_item(export_menu, wxID_ANY, _(L("Export plate as STL including supports")) + dots, _(L("Export current plate as STL including supports")), + append_menu_item(export_menu, wxID_ANY, _(L("Export plate as STL &including supports")) + dots, _(L("Export current plate as STL including supports")), [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(true); }, menu_icon("export_plater"), nullptr, [this](){return can_export_supports(); }, this); append_menu_item(export_menu, wxID_ANY, _(L("Export plate as &AMF")) + dots, _(L("Export current plate as AMF")), [this](wxCommandEvent&) { if (m_plater) m_plater->export_amf(); }, menu_icon("export_plater"), nullptr, [this](){return can_export_model(); }, this); export_menu->AppendSeparator(); + append_menu_item(export_menu, wxID_ANY, _(L("Export &toolpaths as OBJ")) + dots, _(L("Export toolpaths as OBJ")), + [this](wxCommandEvent&) { if (m_plater) m_plater->export_toolpaths_to_obj(); }, menu_icon("export_plater"), nullptr, + [this]() {return can_export_toolpaths(); }, this); + export_menu->AppendSeparator(); append_menu_item(export_menu, wxID_ANY, _(L("Export &Config")) +dots +"\tCtrl+E", _(L("Export current configuration to file")), [this](wxCommandEvent&) { export_config(); }, menu_icon("export_config")); append_menu_item(export_menu, wxID_ANY, _(L("Export Config &Bundle")) + dots, _(L("Export all presets to file")), @@ -498,13 +565,21 @@ void MainFrame::init_menubar() _(L("Deletes all objects")), [this](wxCommandEvent&) { m_plater->reset_with_confirm(); }, menu_icon("delete_all_menu"), nullptr, [this](){return can_delete_all(); }, this); + editMenu->AppendSeparator(); + append_menu_item(editMenu, wxID_ANY, _(L("&Undo")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "Z", + _(L("Undo")), [this](wxCommandEvent&) { m_plater->undo(); }, + "undo", nullptr, [this](){return m_plater->can_undo(); }, this); + append_menu_item(editMenu, wxID_ANY, _(L("&Redo")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "Y", + _(L("Redo")), [this](wxCommandEvent&) { m_plater->redo(); }, + "redo", nullptr, [this](){return m_plater->can_redo(); }, this); + editMenu->AppendSeparator(); append_menu_item(editMenu, wxID_ANY, _(L("&Copy")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "C", _(L("Copy selection to clipboard")), [this](wxCommandEvent&) { m_plater->copy_selection_to_clipboard(); }, - menu_icon("copy_menu"), nullptr, [this](){return m_plater->can_copy(); }, this); + menu_icon("copy_menu"), nullptr, [this](){return m_plater->can_copy_to_clipboard(); }, this); append_menu_item(editMenu, wxID_ANY, _(L("&Paste")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "V", _(L("Paste clipboard")), [this](wxCommandEvent&) { m_plater->paste_from_clipboard(); }, - menu_icon("paste_menu"), nullptr, [this](){return m_plater->can_paste(); }, this); + menu_icon("paste_menu"), nullptr, [this](){return m_plater->can_paste_from_clipboard(); }, this); } // Window menu @@ -641,7 +716,8 @@ void MainFrame::update_menubar() { const bool is_fff = plater()->printer_technology() == ptFFF; - m_changeable_menu_items[miExport] ->SetItemLabel((is_fff ? _(L("Export &G-code")) : _(L("Export")) ) + dots + "\tCtrl+G"); + m_changeable_menu_items[miExport] ->SetItemLabel((is_fff ? _(L("Export &G-code")) : _(L("E&xport")) ) + dots + "\tCtrl+G"); + m_changeable_menu_items[miSend] ->SetItemLabel((is_fff ? _(L("S&end G-code")) : _(L("S&end to print"))) + dots + "\tCtrl+Shift+G"); m_changeable_menu_items[miMaterialTab] ->SetItemLabel((is_fff ? _(L("&Filament Settings Tab")) : _(L("Mate&rial Settings Tab"))) + "\tCtrl+3"); m_changeable_menu_items[miMaterialTab] ->SetBitmap(create_scaled_bitmap(this, menu_icon(is_fff ? "spool": "resin"))); @@ -664,29 +740,26 @@ void MainFrame::quick_slice(const int qs) // select input file if (!(qs & qsReslice)) { - auto dlg = new wxFileDialog(this, _(L("Choose a file to slice (STL/OBJ/AMF/3MF/PRUSA):")), + wxFileDialog dlg(this, _(L("Choose a file to slice (STL/OBJ/AMF/3MF/PRUSA):")), wxGetApp().app_config->get_last_dir(), "", file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); - if (dlg->ShowModal() != wxID_OK) { - dlg->Destroy(); + if (dlg.ShowModal() != wxID_OK) return; - } - input_file = dlg->GetPath(); - dlg->Destroy(); + input_file = dlg.GetPath(); if (!(qs & qsExportSVG)) m_qs_last_input_file = input_file; } else { if (m_qs_last_input_file.IsEmpty()) { - auto dlg = new wxMessageDialog(this, _(L("No previously sliced file.")), + wxMessageDialog dlg(this, _(L("No previously sliced file.")), _(L("Error")), wxICON_ERROR | wxOK); - dlg->ShowModal(); + dlg.ShowModal(); return; } if (std::ifstream(m_qs_last_input_file.ToUTF8().data())) { - auto dlg = new wxMessageDialog(this, _(L("Previously sliced file ("))+m_qs_last_input_file+_(L(") not found.")), + wxMessageDialog dlg(this, _(L("Previously sliced file ("))+m_qs_last_input_file+_(L(") not found.")), _(L("File Not Found")), wxICON_ERROR | wxOK); - dlg->ShowModal(); + dlg.ShowModal(); return; } input_file = m_qs_last_input_file; @@ -720,30 +793,24 @@ void MainFrame::quick_slice(const int qs) } else if (qs & qsSaveAs) { // The following line may die if the output_filename_format template substitution fails. - auto dlg = new wxFileDialog(this, wxString::Format(_(L("Save %s file as:")) , qs & qsExportSVG ? _(L("SVG")) : _(L("G-code")) ), + wxFileDialog dlg(this, wxString::Format(_(L("Save %s file as:")) , qs & qsExportSVG ? _(L("SVG")) : _(L("G-code")) ), wxGetApp().app_config->get_last_output_dir(get_dir_name(output_file)), get_base_name(input_file), qs & qsExportSVG ? file_wildcards(FT_SVG) : file_wildcards(FT_GCODE), wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - if (dlg->ShowModal() != wxID_OK) { - dlg->Destroy(); + if (dlg.ShowModal() != wxID_OK) return; - } - output_file = dlg->GetPath(); - dlg->Destroy(); + output_file = dlg.GetPath(); if (!(qs & qsExportSVG)) m_qs_last_output_file = output_file; wxGetApp().app_config->update_last_output_dir(get_dir_name(output_file)); } else if (qs & qsExportPNG) { - auto dlg = new wxFileDialog(this, _(L("Save zip file as:")), + wxFileDialog dlg(this, _(L("Save zip file as:")), wxGetApp().app_config->get_last_output_dir(get_dir_name(output_file)), get_base_name(output_file), "*.sl1", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - if (dlg->ShowModal() != wxID_OK) { - dlg->Destroy(); + if (dlg.ShowModal() != wxID_OK) return; - } - output_file = dlg->GetPath(); - dlg->Destroy(); + output_file = dlg.GetPath(); } // show processbar dialog @@ -789,28 +856,22 @@ void MainFrame::repair_stl() { wxString input_file; { - auto dlg = new wxFileDialog(this, _(L("Select the STL file to repair:")), + wxFileDialog dlg(this, _(L("Select the STL file to repair:")), wxGetApp().app_config->get_last_dir(), "", file_wildcards(FT_STL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); - if (dlg->ShowModal() != wxID_OK) { - dlg->Destroy(); + if (dlg.ShowModal() != wxID_OK) return; - } - input_file = dlg->GetPath(); - dlg->Destroy(); + input_file = dlg.GetPath(); } wxString output_file = input_file; { - auto dlg = new wxFileDialog( this, L("Save OBJ file (less prone to coordinate errors than STL) as:"), + wxFileDialog dlg( this, L("Save OBJ file (less prone to coordinate errors than STL) as:"), get_dir_name(output_file), get_base_name(output_file, ".obj"), file_wildcards(FT_OBJ), wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - if (dlg->ShowModal() != wxID_OK) { - dlg->Destroy(); + if (dlg.ShowModal() != wxID_OK) return; - } - output_file = dlg->GetPath(); - dlg->Destroy(); + output_file = dlg.GetPath(); } auto tmesh = new Slic3r::TriangleMesh(); @@ -831,14 +892,13 @@ void MainFrame::export_config() return; } // Ask user for the file name for the config file. - auto dlg = new wxFileDialog(this, _(L("Save configuration as:")), + wxFileDialog dlg(this, _(L("Save configuration as:")), !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(), !m_last_config.IsEmpty() ? get_base_name(m_last_config) : "config.ini", file_wildcards(FT_INI), wxFD_SAVE | wxFD_OVERWRITE_PROMPT); wxString file; - if (dlg->ShowModal() == wxID_OK) - file = dlg->GetPath(); - dlg->Destroy(); + if (dlg.ShowModal() == wxID_OK) + file = dlg.GetPath(); if (!file.IsEmpty()) { wxGetApp().app_config->update_config_dir(get_dir_name(file)); m_last_config = file; @@ -851,13 +911,12 @@ void MainFrame::load_config_file() { if (!wxGetApp().check_unsaved_changes()) return; - auto dlg = new wxFileDialog(this, _(L("Select configuration to load:")), + wxFileDialog dlg(this, _(L("Select configuration to load:")), !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(), "config.ini", "INI files (*.ini, *.gcode)|*.ini;*.INI;*.gcode;*.g", wxFD_OPEN | wxFD_FILE_MUST_EXIST); wxString file; - if (dlg->ShowModal() == wxID_OK) - file = dlg->GetPath(); - dlg->Destroy(); + if (dlg.ShowModal() == wxID_OK) + file = dlg.GetPath(); if (! file.IsEmpty() && this->load_config_file(file.ToUTF8().data())) { wxGetApp().app_config->update_config_dir(get_dir_name(file)); m_last_config = file; @@ -888,14 +947,13 @@ void MainFrame::export_configbundle() return; } // Ask user for a file name. - auto dlg = new wxFileDialog(this, _(L("Save presets bundle as:")), + wxFileDialog dlg(this, _(L("Save presets bundle as:")), !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(), SLIC3R_APP_KEY "_config_bundle.ini", file_wildcards(FT_INI), wxFD_SAVE | wxFD_OVERWRITE_PROMPT); wxString file; - if (dlg->ShowModal() == wxID_OK) - file = dlg->GetPath(); - dlg->Destroy(); + if (dlg.ShowModal() == wxID_OK) + file = dlg.GetPath(); if (!file.IsEmpty()) { // Export the config bundle. wxGetApp().app_config->update_config_dir(get_dir_name(file)); @@ -915,15 +973,12 @@ void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool re if (!wxGetApp().check_unsaved_changes()) return; if (file.IsEmpty()) { - auto dlg = new wxFileDialog(this, _(L("Select configuration to load:")), + wxFileDialog dlg(this, _(L("Select configuration to load:")), !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(), "config.ini", file_wildcards(FT_INI), wxFD_OPEN | wxFD_FILE_MUST_EXIST); - if (dlg->ShowModal() != wxID_OK) { - dlg->Destroy(); - return; - } - file = dlg->GetPath(); - dlg->Destroy(); + if (dlg.ShowModal() != wxID_OK) + return; + file = dlg.GetPath(); } wxGetApp().app_config->update_config_dir(get_dir_name(file)); @@ -976,7 +1031,8 @@ void MainFrame::load_config(const DynamicPrintConfig& config) if (! boost::algorithm::ends_with(opt_key, "_settings_id")) tab->get_config()->option(opt_key)->set(config.option(opt_key)); } - wxGetApp().load_current_presets(); + + wxGetApp().load_current_presets(); #endif } @@ -1041,6 +1097,23 @@ void MainFrame::on_config_changed(DynamicPrintConfig* config) const m_plater->on_config_change(*config); // propagate config change events to the plater } +void MainFrame::add_to_recent_projects(const wxString& filename) +{ + if (wxFileExists(filename)) + { + m_recent_projects.AddFileToHistory(filename); + std::vector recent_projects; + size_t count = m_recent_projects.GetCount(); + for (size_t i = 0; i < count; ++i) + { + recent_projects.push_back(into_u8(m_recent_projects.GetHistoryFile(i))); + } + wxGetApp().app_config->set_recent_projects(recent_projects); + wxGetApp().app_config->save(); + } +} + +// // Called after the Preferences dialog is closed and the program settings are saved. // Update the UI based on the current preferences. void MainFrame::update_ui_from_settings() diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index a5de947388..aa1e3d5002 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -1,4 +1,4 @@ -#ifndef slic3r_MainFrame_hpp_ +#ifndef slic3r_MainFrame_hpp_ #define slic3r_MainFrame_hpp_ #include "libslic3r/PrintConfig.hpp" @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -64,8 +65,10 @@ class MainFrame : public DPIFrame bool can_start_new_project() const; bool can_save() const; bool can_export_model() const; + bool can_export_toolpaths() const; bool can_export_supports() const; bool can_export_gcode() const; + bool can_send_gcode() const; bool can_slice() const; bool can_change_view() const; bool can_select() const; @@ -78,18 +81,21 @@ class MainFrame : public DPIFrame enum MenuItems { // FFF SLA miExport = 0, // Export G-code Export + miSend, // Send G-code Send to print miMaterialTab, // Filament Settings Material Settings }; // vector of a MenuBar items changeable in respect to printer technology std::vector m_changeable_menu_items; + wxFileHistory m_recent_projects; + protected: virtual void on_dpi_changed(const wxRect &suggested_rect); public: MainFrame(); - ~MainFrame() {} + ~MainFrame(); Plater* plater() { return m_plater; } @@ -121,12 +127,14 @@ public: // Propagate changed configuration from the Tab to the Platter and save changes to the AppConfig void on_config_changed(DynamicPrintConfig* cfg) const ; + void add_to_recent_projects(const wxString& filename); + PrintHostQueueDialog* printhost_queue_dlg() { return m_printhost_queue_dlg; } Plater* m_plater { nullptr }; wxNotebook* m_tabpanel { nullptr }; wxProgressDialog* m_progress_dialog { nullptr }; - ProgressStatusBar* m_statusbar { nullptr }; + std::unique_ptr m_statusbar; }; } // GUI diff --git a/src/slic3r/GUI/MsgDialog.hpp b/src/slic3r/GUI/MsgDialog.hpp index ad4bbcc971..5a49298495 100644 --- a/src/slic3r/GUI/MsgDialog.hpp +++ b/src/slic3r/GUI/MsgDialog.hpp @@ -8,8 +8,6 @@ #include #include -#include "slic3r/Utils/Semver.hpp" - class wxBoxSizer; class wxCheckBox; class wxStaticBitmap; diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 2ac6b00af6..656df86ff2 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -1,4 +1,4 @@ -#include "OptionsGroup.hpp" +#include "OptionsGroup.hpp" #include "ConfigExceptions.hpp" #include @@ -202,7 +202,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n // so we need a horizontal sizer to arrange these things auto sizer = new wxBoxSizer(wxHORIZONTAL); grid_sizer->Add(sizer, 0, wxEXPAND | (staticbox ? wxALL : wxBOTTOM | wxTOP | wxLEFT), staticbox ? 0 : 1); - sizer->Add(m_near_label_widget_ptrs.back(), 0, wxRIGHT, 7); + sizer->Add(m_near_label_widget_ptrs.back(), 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 7); sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) | wxALIGN_CENTER_VERTICAL, 5); } } @@ -266,7 +266,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n is_sizer_field(field) ? v_sizer->Add(field->getSizer(), 0, wxEXPAND) : v_sizer->Add(field->getWindow(), 0, wxEXPAND); - return; + break;//return; } is_sizer_field(field) ? @@ -276,7 +276,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n // add sidetext if any if (option.sidetext != "") { auto sidetext = new wxStaticText( this->ctrl_parent(), wxID_ANY, _(option.sidetext), wxDefaultPosition, - /*wxSize(sidetext_width*wxGetApp().em_unit(), -1)*/wxDefaultSize, wxALIGN_LEFT); + wxSize(sidetext_width != -1 ? sidetext_width*wxGetApp().em_unit() : -1, -1) /*wxDefaultSize*/, wxALIGN_LEFT); sidetext->SetBackgroundStyle(wxBG_STYLE_PAINT); sidetext->SetFont(wxGetApp().normal_font()); sizer_tmp->Add(sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4); @@ -300,7 +300,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n { // extra widget for non-staticbox option group (like for the frequently used parameters on the sidebar) should be wxALIGN_RIGHT const auto v_sizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(v_sizer, 1, wxEXPAND); + sizer->Add(v_sizer, option_set.size() == 1 ? 0 : 1, wxEXPAND); v_sizer->Add(extra_widget(this->ctrl_parent()), 0, wxALIGN_RIGHT); return; } @@ -320,6 +320,17 @@ Line OptionsGroup::create_single_option_line(const Option& option) const { return retval; } +void OptionsGroup::clear_fields_except_of(const std::vector left_fields) +{ + auto it = m_fields.begin(); + while (it != m_fields.end()) { + if (std::find(left_fields.begin(), left_fields.end(), it->first) == left_fields.end()) + it = m_fields.erase(it); + else + it++; + } +} + void OptionsGroup::on_set_focus(const std::string& opt_key) { if (m_set_focus != nullptr) @@ -361,30 +372,10 @@ void ConfigOptionsGroup::on_change_OG(const t_config_option_key& opt_id, const b auto option = m_options.at(opt_id).opt; - // get value -//! auto field_value = get_value(opt_id); - if (option.gui_flags.compare("serialized")==0) { - if (opt_index != -1) { - // die "Can't set serialized option indexed value" ; - } - change_opt_value(*m_config, opt_key, value); - } - else { - if (opt_index == -1) { - // change_opt_value(*m_config, opt_key, field_value); - //!? why field_value?? in this case changed value will be lose! No? - change_opt_value(*m_config, opt_key, value); - } - else { - change_opt_value(*m_config, opt_key, value, opt_index); -// auto value = m_config->get($opt_key); -// $value->[$opt_index] = $field_value; -// $self->config->set($opt_key, $value); - } - } + change_opt_value(*m_config, opt_key, value, opt_index == -1 ? 0 : opt_index); } - OptionsGroup::on_change_OG(opt_id, value); //!? Why doing this + OptionsGroup::on_change_OG(opt_id, value); } void ConfigOptionsGroup::back_to_initial_value(const std::string& opt_key) @@ -567,6 +558,34 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config boost::any ret; wxString text_value = wxString(""); const ConfigOptionDef* opt = config.def()->get(opt_key); + + if (opt->nullable) + { + switch (opt->type) + { + case coPercents: + case coFloats: { + if (config.option(opt_key)->is_nil()) + ret = _(L("N/A")); + else { + double val = opt->type == coFloats ? + config.option(opt_key)->get_at(idx) : + config.option(opt_key)->get_at(idx); + ret = double_to_string(val); } + } + break; + case coBools: + ret = config.option(opt_key)->values[idx]; + break; + case coInts: + ret = config.option(opt_key)->get_at(idx); + break; + default: + break; + } + return ret; + } + switch (opt->type) { case coFloatOrPercent:{ const auto &value = *config.option(opt_key); diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index 73b2c5110f..cc3d89b1fa 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -160,6 +160,14 @@ public: m_show_modified_btns = show; } + void clear_fields_except_of(const std::vector left_fields); + + void hide_labels() { + label_width = 0; + m_grid_sizer->SetCols(m_grid_sizer->GetEffectiveColsCount()-1); + static_cast(m_grid_sizer)->AddGrowableCol(!extra_column ? 0 : 1); + } + OptionsGroup( wxWindow* _parent, const wxString& title, bool is_tab_opt = false, column_t extra_clmn = nullptr) : m_parent(_parent), title(title), @@ -244,6 +252,9 @@ public: Option option = get_option(title, idx); return OptionsGroup::create_single_option_line(option); } + Line create_single_option_line(const Option& option) const { + return OptionsGroup::create_single_option_line(option); + } void append_single_option_line(const Option& option) { OptionsGroup::append_single_option_line(option); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 4c42994ee7..95450b755f 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2,12 +2,15 @@ #include #include +#include #include #include #include +#include #include #include #include +#include #include #include @@ -29,7 +32,6 @@ #include "libslic3r/Format/3mf.hpp" #include "libslic3r/GCode/PreviewData.hpp" #include "libslic3r/Model.hpp" -#include "libslic3r/ModelArrange.hpp" #include "libslic3r/Polygon.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/PrintConfig.hpp" @@ -37,12 +39,18 @@ #include "libslic3r/SLA/SLARotfinder.hpp" #include "libslic3r/Utils.hpp" -#include "libnest2d/optimizers/nlopt/genetic.hpp" +//#include "libslic3r/ClipperUtils.hpp" + +// #include "libnest2d/optimizers/nlopt/genetic.hpp" +// #include "libnest2d/backends/clipper/geometries.hpp" +// #include "libnest2d/utils/rotcalipers.hpp" +#include "libslic3r/MinAreaBoundingBox.hpp" #include "GUI.hpp" #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" #include "GUI_ObjectManipulation.hpp" +#include "GUI_ObjectLayers.hpp" #include "GUI_Utils.hpp" #include "wxExtensions.hpp" #include "MainFrame.hpp" @@ -62,6 +70,7 @@ #include "../Utils/ASCIIFolding.hpp" #include "../Utils/PrintHost.hpp" #include "../Utils/FixModelByWin10.hpp" +#include "../Utils/UndoRedo.hpp" #include // Needs to be last because reasons :-/ #include "WipeTowerDialog.hpp" @@ -237,6 +246,7 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 * last_selected(wxNOT_FOUND), m_em_unit(wxGetApp().em_unit()) { + SetFont(wxGetApp().normal_font()); Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &evt) { auto selected_item = this->GetSelection(); @@ -246,7 +256,7 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 * evt.StopPropagation(); if (marker == LABEL_ITEM_CONFIG_WIZARD) wxTheApp->CallAfter([]() { Slic3r::GUI::config_wizard(Slic3r::GUI::ConfigWizard::RR_USER); }); - } else if ( this->last_selected != selected_item || + } else if ( this->last_selected != selected_item || wxGetApp().get_tab(this->preset_type)->get_presets()->current_is_dirty() ) { this->last_selected = selected_item; evt.SetInt(this->preset_type); @@ -255,7 +265,7 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 * evt.StopPropagation(); } }); - + if (preset_type == Slic3r::Preset::TYPE_FILAMENT) { Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &event) { @@ -272,7 +282,7 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 * event.Skip(); return; } - + // Swallow the mouse click and open the color picker. // get current color @@ -280,26 +290,25 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 * auto colors = static_cast(cfg->option("extruder_colour")->clone()); wxColour clr(colors->values[extruder_idx]); if (!clr.IsOk()) - clr = wxTransparentColour; + clr = wxColour(0,0,0); // Don't set alfa to transparence auto data = new wxColourData(); data->SetChooseFull(1); data->SetColour(clr); - auto dialog = new wxColourDialog(this, data); - dialog->CenterOnParent(); - if (dialog->ShowModal() == wxID_OK) + wxColourDialog dialog(this, data); + dialog.CenterOnParent(); + if (dialog.ShowModal() == wxID_OK) { - colors->values[extruder_idx] = dialog->GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX); + colors->values[extruder_idx] = dialog.GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX); - DynamicPrintConfig cfg_new = *cfg; + DynamicPrintConfig cfg_new = *cfg; cfg_new.set_key_value("extruder_colour", colors); wxGetApp().get_tab(Preset::TYPE_PRINTER)->load_config(cfg_new); wxGetApp().preset_bundle->update_platter_filament_ui(extruder_idx, this); wxGetApp().plater()->on_config_change(cfg_new); } - dialog->Destroy(); }); } @@ -318,14 +327,14 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 * wxGetApp().tab_panel()->ChangeSelection(page_id); - /* In a case of a multi-material printing, for editing another Filament Preset - * it's needed to select this preset for the "Filament settings" Tab + /* In a case of a multi-material printing, for editing another Filament Preset + * it's needed to select this preset for the "Filament settings" Tab */ - if (preset_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_cnt() > 1) + if (preset_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1) { const std::string& selected_preset = GetString(GetSelection()).ToUTF8().data(); - // Call select_preset() only if there is new preset and not just modified + // Call select_preset() only if there is new preset and not just modified if ( !boost::algorithm::ends_with(selected_preset, Preset::suffix_modified()) ) tab->select_preset(selected_preset); } @@ -364,24 +373,36 @@ class FreqChangedParams : public OG_Settings wxSizer* m_sizer {nullptr}; std::shared_ptr m_og_sla; + std::vector m_empty_buttons; public: - FreqChangedParams(wxWindow* parent, const int label_width); + FreqChangedParams(wxWindow* parent); ~FreqChangedParams() {} wxButton* get_wiping_dialog_button() { return m_wiping_dialog_button; } wxSizer* get_sizer() override; ConfigOptionsGroup* get_og(const bool is_fff); void Show(const bool is_fff); + + void msw_rescale(); }; -FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : +void FreqChangedParams::msw_rescale() +{ + m_og->msw_rescale(); + m_og_sla->msw_rescale(); + + for (auto btn: m_empty_buttons) + btn->msw_rescale(); +} + +FreqChangedParams::FreqChangedParams(wxWindow* parent) : OG_Settings(parent, false) { DynamicPrintConfig* config = &wxGetApp().preset_bundle->prints.get_edited_preset().config; // Frequently changed parameters for FFF_technology m_og->set_config(config); - m_og->label_width = label_width == 0 ? 1 : label_width; + m_og->hide_labels(); m_og->m_on_change = [config, this](t_config_option_key opt_key, boost::any value) { Tab* tab_print = wxGetApp().get_tab(Preset::TYPE_PRINT); @@ -437,7 +458,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : tab_print->update_dirty(); }; - + Line line = Line { "", "" }; ConfigOptionDef support_def; @@ -453,15 +474,29 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : Option option = Option(support_def, "support"); option.opt.full_width = true; line.append_option(option); + + /* Not a best solution, but + * Temporary workaround for right border alignment + */ + auto empty_widget = [this] (wxWindow* parent) { + auto sizer = new wxBoxSizer(wxHORIZONTAL); + auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_transparent.png", wxEmptyString, + wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); + sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, int(0.3 * wxGetApp().em_unit())); + m_empty_buttons.push_back(btn); + return sizer; + }; + line.append_widget(empty_widget); + m_og->append_line(line); - + line = Line { "", "" }; option = m_og->get_option("fill_density"); option.opt.label = L("Infill"); option.opt.width = 7/*6*/; - option.opt.sidetext = " "; + option.opt.sidetext = " "; line.append_option(option); m_brim_width = config->opt_float("brim_width"); @@ -472,13 +507,14 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : def.gui_type = ""; def.set_default_value(new ConfigOptionBool{ m_brim_width > 0.0 ? true : false }); option = Option(def, "brim"); - option.opt.sidetext = " "; + option.opt.sidetext = ""; line.append_option(option); auto wiping_dialog_btn = [this](wxWindow* parent) { m_wiping_dialog_button = new wxButton(parent, wxID_ANY, _(L("Purging volumes")) + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); + m_wiping_dialog_button->SetFont(wxGetApp().normal_font()); auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(m_wiping_dialog_button); + sizer->Add(m_wiping_dialog_button, 0, wxALIGN_CENTER_VERTICAL); m_wiping_dialog_button->Bind(wxEVT_BUTTON, ([parent](wxCommandEvent& e) { auto &project_config = wxGetApp().preset_bundle->project_config; @@ -498,6 +534,13 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : wxPostEvent(parent, SimpleEvent(EVT_SCHEDULE_BACKGROUND_PROCESS, parent)); } })); + + auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_transparent.png", wxEmptyString, + wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); + sizer->Add(btn , 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, + int(0.3 * wxGetApp().em_unit())); + m_empty_buttons.push_back(btn); + return sizer; }; line.append_widget(wiping_dialog_btn); @@ -507,22 +550,29 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : // Frequently changed parameters for SLA_technology m_og_sla = std::make_shared(parent, ""); + m_og_sla->hide_labels(); DynamicPrintConfig* config_sla = &wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; m_og_sla->set_config(config_sla); - m_og_sla->label_width = label_width == 0 ? 1 : label_width; m_og_sla->m_on_change = [config_sla, this](t_config_option_key opt_key, boost::any value) { Tab* tab = wxGetApp().get_tab(Preset::TYPE_SLA_PRINT); if (!tab) return; - if (opt_key == "pad_enable") { - tab->set_value(opt_key, value); - tab->update(); + DynamicPrintConfig new_conf = *config_sla; + if (opt_key == "pad") { + const wxString& selection = boost::any_cast(value); + + const bool pad_enable = selection == _("None") ? false : true; + new_conf.set_key_value("pad_enable", new ConfigOptionBool(pad_enable)); + + if (selection == _("Below object")) + new_conf.set_key_value("pad_zero_elevation", new ConfigOptionBool(false)); + else if (selection == _("Around object")) + new_conf.set_key_value("pad_zero_elevation", new ConfigOptionBool(true)); } else { assert(opt_key == "support"); - DynamicPrintConfig new_conf = *config_sla; const wxString& selection = boost::any_cast(value); const bool supports_enable = selection == _("None") ? false : true; @@ -532,10 +582,9 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : new_conf.set_key_value("support_buildplate_only", new ConfigOptionBool(false)); else if (selection == _("Support on build plate only")) new_conf.set_key_value("support_buildplate_only", new ConfigOptionBool(true)); - - tab->load_config(new_conf); } + tab->load_config(new_conf); tab->update_dirty(); }; @@ -548,13 +597,24 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : option = Option(support_def_sla, "support"); option.opt.full_width = true; line.append_option(option); + line.append_widget(empty_widget); m_og_sla->append_line(line); line = Line{ "", "" }; - option = m_og_sla->get_option("pad_enable"); - option.opt.sidetext = " "; + ConfigOptionDef pad_def; + pad_def.label = L("Pad"); + pad_def.type = coStrings; + pad_def.gui_type = "select_open"; + pad_def.tooltip = L("Select what kind of pad do you need"); + pad_def.enum_labels.push_back(L("None")); + pad_def.enum_labels.push_back(L("Below object")); + pad_def.enum_labels.push_back(L("Around object")); + pad_def.set_default_value(new ConfigOptionStrings{ "Below object" }); + option = Option(pad_def, "pad"); + option.opt.full_width = true; line.append_option(option); + line.append_widget(empty_widget); m_og_sla->append_line(line); @@ -575,7 +635,7 @@ void FreqChangedParams::Show(const bool is_fff) m_og->Show(is_fff); m_og_sla->Show(!is_fff); - // correct showing of the FreqChangedParams sizer when m_wiping_dialog_button is hidden + // correct showing of the FreqChangedParams sizer when m_wiping_dialog_button is hidden if (is_fff && !is_wdb_shown) m_wiping_dialog_button->Hide(); } @@ -610,10 +670,11 @@ struct Sidebar::priv PresetComboBox *combo_printer; wxBoxSizer *sizer_params; - FreqChangedParams *frequently_changed_parameters; - ObjectList *object_list; - ObjectManipulation *object_manipulation; - ObjectSettings *object_settings; + FreqChangedParams *frequently_changed_parameters{ nullptr }; + ObjectList *object_list{ nullptr }; + ObjectManipulation *object_manipulation{ nullptr }; + ObjectSettings *object_settings{ nullptr }; + ObjectLayers *object_layers{ nullptr }; ObjectInfo *object_info; SlicedInfo *sliced_info; @@ -622,10 +683,26 @@ struct Sidebar::priv wxButton *btn_send_gcode; priv(Plater *plater) : plater(plater) {} + ~priv(); void show_preset_comboboxes(); }; +Sidebar::priv::~priv() +{ + if (object_manipulation != nullptr) + delete object_manipulation; + + if (object_settings != nullptr) + delete object_settings; + + if (frequently_changed_parameters != nullptr) + delete frequently_changed_parameters; + + if (object_layers != nullptr) + delete object_layers; +} + void Sidebar::priv::show_preset_comboboxes() { const bool showSLA = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA; @@ -659,7 +736,7 @@ Sidebar::Sidebar(Plater *parent) p->scrolled->SetSizer(scrolled_sizer); // Sizer with buttons for mode changing - p->mode_sizer = new ModeSizer(p->scrolled, 2 * wxGetApp().em_unit()); + p->mode_sizer = new ModeSizer(p->scrolled); // The preset chooser p->sizer_presets = new wxFlexGridSizer(10, 1, 1, 2); @@ -688,7 +765,7 @@ Sidebar::Sidebar(Plater *parent) auto combo_and_btn_sizer = new wxBoxSizer(wxHORIZONTAL); combo_and_btn_sizer->Add(*combo, 1, wxEXPAND); if ((*combo)->edit_btn) - combo_and_btn_sizer->Add((*combo)->edit_btn, 0, wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT, + combo_and_btn_sizer->Add((*combo)->edit_btn, 0, wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT, int(0.3*wxGetApp().em_unit())); auto *sizer_presets = this->p->sizer_presets; @@ -711,18 +788,17 @@ Sidebar::Sidebar(Plater *parent) init_combo(&p->combo_printer, _(L("Printer")), Preset::TYPE_PRINTER, false); const int margin_5 = int(0.5*wxGetApp().em_unit());// 5; - const int margin_10 = 10;//int(1.5*wxGetApp().em_unit());// 15; p->sizer_params = new wxBoxSizer(wxVERTICAL); // Frequently changed parameters - p->frequently_changed_parameters = new FreqChangedParams(p->scrolled, 0/*label_width*/); - p->sizer_params->Add(p->frequently_changed_parameters->get_sizer(), 0, wxEXPAND | wxTOP | wxBOTTOM, margin_10); - + p->frequently_changed_parameters = new FreqChangedParams(p->scrolled); + p->sizer_params->Add(p->frequently_changed_parameters->get_sizer(), 0, wxEXPAND | wxTOP | wxBOTTOM, wxOSX ? 1 : margin_5); + // Object List p->object_list = new ObjectList(p->scrolled); p->sizer_params->Add(p->object_list->get_sizer(), 1, wxEXPAND); - + // Object Manipulations p->object_manipulation = new ObjectManipulation(p->scrolled); p->object_manipulation->Hide(); @@ -733,6 +809,11 @@ Sidebar::Sidebar(Plater *parent) p->object_settings->Hide(); p->sizer_params->Add(p->object_settings->get_sizer(), 0, wxEXPAND | wxTOP, margin_5); + // Object Layers + p->object_layers = new ObjectLayers(p->scrolled); + p->object_layers->Hide(); + p->sizer_params->Add(p->object_layers->get_sizer(), 0, wxEXPAND | wxTOP, margin_5); + // Info boxes p->object_info = new ObjectInfo(p->scrolled); p->sliced_info = new SlicedInfo(p->scrolled); @@ -740,7 +821,7 @@ Sidebar::Sidebar(Plater *parent) // Sizer in the scrolled area scrolled_sizer->Add(p->mode_sizer, 0, wxALIGN_CENTER_HORIZONTAL/*RIGHT | wxBOTTOM | wxRIGHT, 5*/); is_msw ? - scrolled_sizer->Add(p->presets_panel, 0, wxEXPAND | wxLEFT, margin_5) : + scrolled_sizer->Add(p->presets_panel, 0, wxEXPAND | wxLEFT, margin_5) : scrolled_sizer->Add(p->sizer_presets, 0, wxEXPAND | wxLEFT, margin_5); scrolled_sizer->Add(p->sizer_params, 1, wxEXPAND | wxLEFT, margin_5); scrolled_sizer->Add(p->object_info, 0, wxEXPAND | wxTOP | wxLEFT, margin_5); @@ -749,7 +830,7 @@ Sidebar::Sidebar(Plater *parent) // Buttons underneath the scrolled area auto init_btn = [this](wxButton **btn, wxString label) { - *btn = new wxButton(this, wxID_ANY, label, wxDefaultPosition, + *btn = new wxButton(this, wxID_ANY, label, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); (*btn)->SetFont(wxGetApp().bold_font()); }; @@ -779,7 +860,7 @@ Sidebar::Sidebar(Plater *parent) p->plater->export_gcode(); else p->plater->reslice(); - p->plater->select_view_3D("Preview"); + p->plater->select_view_3D("Preview"); }); p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->send_gcode(); }); } @@ -839,11 +920,11 @@ void Sidebar::update_all_preset_comboboxes() void Sidebar::update_presets(Preset::Type preset_type) { - PresetBundle &preset_bundle = *wxGetApp().preset_bundle; + PresetBundle &preset_bundle = *wxGetApp().preset_bundle; const auto print_tech = preset_bundle.printers.get_edited_preset().printer_technology(); switch (preset_type) { - case Preset::TYPE_FILAMENT: + case Preset::TYPE_FILAMENT: { const int extruder_cnt = print_tech != ptFFF ? 1 : dynamic_cast(preset_bundle.printers.get_edited_preset().config.option("nozzle_diameter"))->values.size(); @@ -863,23 +944,23 @@ void Sidebar::update_presets(Preset::Type preset_type) } case Preset::TYPE_PRINT: - preset_bundle.prints.update_platter_ui(p->combo_print); + preset_bundle.prints.update_platter_ui(p->combo_print); break; case Preset::TYPE_SLA_PRINT: - preset_bundle.sla_prints.update_platter_ui(p->combo_sla_print); + preset_bundle.sla_prints.update_platter_ui(p->combo_sla_print); break; case Preset::TYPE_SLA_MATERIAL: - preset_bundle.sla_materials.update_platter_ui(p->combo_sla_material); + preset_bundle.sla_materials.update_platter_ui(p->combo_sla_material); break; - case Preset::TYPE_PRINTER: - { + case Preset::TYPE_PRINTER: + { update_all_preset_comboboxes(); - p->show_preset_comboboxes(); - break; - } + p->show_preset_comboboxes(); + break; + } default: break; } @@ -897,11 +978,11 @@ void Sidebar::update_reslice_btn_tooltip() const { wxString tooltip = wxString("Slice") + " [" + GUI::shortkey_ctrl_prefix() + "R]"; if (m_mode != comSimple) - tooltip += wxString("\n") + _(L("Hold Shift to Slice & Export G-code")); + tooltip += wxString("\n") + _(L("Hold Shift to Slice & Export G-code")); p->btn_reslice->SetToolTip(tooltip); } -void Sidebar::msw_rescale() +void Sidebar::msw_rescale() { SetMinSize(wxSize(40 * wxGetApp().em_unit(), -1)); @@ -919,12 +1000,11 @@ void Sidebar::msw_rescale() // ... then refill them and set min size to correct layout of the sidebar update_all_preset_comboboxes(); - p->frequently_changed_parameters->get_og(true)->msw_rescale(); - p->frequently_changed_parameters->get_og(false)->msw_rescale(); - + p->frequently_changed_parameters->msw_rescale(); p->object_list->msw_rescale(); p->object_manipulation->msw_rescale(); p->object_settings->msw_rescale(); + p->object_layers->msw_rescale(); p->object_info->msw_rescale(); @@ -946,6 +1026,11 @@ ObjectSettings* Sidebar::obj_settings() return p->object_settings; } +ObjectLayers* Sidebar::obj_layers() +{ + return p->object_layers; +} + wxScrolledWindow* Sidebar::scrolled_panel() { return p->scrolled; @@ -1005,7 +1090,7 @@ void Sidebar::show_info_sizer() if (errors > 0) { wxString tooltip = wxString::Format(_(L("Auto-repaired (%d errors)")), errors); p->object_info->info_manifold->SetLabel(tooltip); - + tooltip += ":\n" + wxString::Format(_(L("%d degenerate facets, %d edges fixed, %d facets removed, " "%d facets added, %d facets reversed, %d backwards edges")), stats.degenerate_facets, stats.edges_fixed, stats.facets_removed, @@ -1014,7 +1099,7 @@ void Sidebar::show_info_sizer() p->object_info->showing_manifold_warning_icon = true; p->object_info->info_manifold->SetToolTip(tooltip); p->object_info->manifold_warning_icon->SetToolTip(tooltip); - } + } else { p->object_info->info_manifold->SetLabel(_(L("Yes"))); p->object_info->showing_manifold_warning_icon = false; @@ -1030,7 +1115,7 @@ void Sidebar::show_info_sizer() } } -void Sidebar::show_sliced_info_sizer(const bool show) +void Sidebar::show_sliced_info_sizer(const bool show) { wxWindowUpdateLocker freeze_guard(this); @@ -1061,7 +1146,7 @@ void Sidebar::show_sliced_info_sizer(const bool show) p->sliced_info->SetTextAndShow(siWTNumbetOfToolchanges, "N/A"); } else - { + { const PrintStatistics& ps = p->plater->fff_print().print_statistics(); const bool is_wipe_tower = ps.total_wipe_tower_filament > 0; @@ -1071,7 +1156,7 @@ void Sidebar::show_sliced_info_sizer(const bool show) wxString info_text = is_wipe_tower ? wxString::Format("%.2f \n%.2f \n%.2f", ps.total_used_filament / 1000, - (ps.total_used_filament - ps.total_wipe_tower_filament) / 1000, + (ps.total_used_filament - ps.total_wipe_tower_filament) / 1000, ps.total_wipe_tower_filament / 1000) : wxString::Format("%.2f", ps.total_used_filament / 1000); p->sliced_info->SetTextAndShow(siFilament_m, info_text, new_label); @@ -1083,10 +1168,10 @@ void Sidebar::show_sliced_info_sizer(const bool show) new_label = _(L("Cost")); if (is_wipe_tower) new_label += wxString::Format(" :\n - %s\n - %s", _(L("objects")), _(L("wipe tower"))); - + info_text = is_wipe_tower ? wxString::Format("%.2f \n%.2f \n%.2f", ps.total_cost, - (ps.total_cost - ps.total_wipe_tower_cost), + (ps.total_cost - ps.total_wipe_tower_cost), ps.total_wipe_tower_cost) : wxString::Format("%.2f", ps.total_cost); p->sliced_info->SetTextAndShow(siCost, info_text, new_label); @@ -1099,10 +1184,20 @@ void Sidebar::show_sliced_info_sizer(const bool show) if (ps.estimated_normal_print_time != "N/A") { new_label += wxString::Format("\n - %s", _(L("normal mode"))); info_text += wxString::Format("\n%s", ps.estimated_normal_print_time); + for (int i = (int)ps.estimated_normal_color_print_times.size() - 1; i >= 0; --i) + { + new_label += wxString::Format("\n - %s%d", _(L("Color ")), i + 1); + info_text += wxString::Format("\n%s", ps.estimated_normal_color_print_times[i]); + } } if (ps.estimated_silent_print_time != "N/A") { new_label += wxString::Format("\n - %s", _(L("stealth mode"))); info_text += wxString::Format("\n%s", ps.estimated_silent_print_time); + for (int i = (int)ps.estimated_silent_color_print_times.size() - 1; i >= 0; --i) + { + new_label += wxString::Format("\n - %s%d", _(L("Color ")), i + 1); + info_text += wxString::Format("\n%s", ps.estimated_silent_color_print_times[i]); + } } p->sliced_info->SetTextAndShow(siEstimatedTime, info_text, new_label); } @@ -1112,7 +1207,7 @@ void Sidebar::show_sliced_info_sizer(const bool show) // Hide non-FFF sliced info parameters p->sliced_info->SetTextAndShow(siMateril_unit, "N/A"); - } + } } Layout(); @@ -1150,7 +1245,7 @@ void Sidebar::update_mode() p->object_list->unselect_objects(); p->object_list->update_selections(); p->object_list->update_object_menu(); - + Layout(); } @@ -1164,7 +1259,7 @@ std::vector& Sidebar::combos_filament() class PlaterDropTarget : public wxFileDropTarget { public: - PlaterDropTarget(Plater *plater) : plater(plater) { this->SetDefaultAction(wxDragCopy); } + PlaterDropTarget(Plater *plater) : plater(plater) { this->SetDefaultAction(wxDragCopy); } virtual bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames); @@ -1179,10 +1274,8 @@ const std::regex PlaterDropTarget::pattern_drop(".*[.](stl|obj|amf|3mf|prusa)", bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames) { std::vector paths; - for (const auto &filename : filenames) { fs::path path(into_path(filename)); - if (std::regex_match(path.string(), pattern_drop)) { paths.push_back(std::move(path)); } else { @@ -1190,6 +1283,23 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi } } + wxString snapshot_label; + assert(! paths.empty()); + if (paths.size() == 1) { + snapshot_label = _(L("Load File")); + snapshot_label += ": "; + snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); + } else { + snapshot_label = _(L("Load Files")); + snapshot_label += ": "; + snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); + for (size_t i = 1; i < paths.size(); ++ i) { + snapshot_label += ", "; + snapshot_label += wxString::FromUTF8(paths[i].filename().string().c_str()); + } + } + Plater::TakeSnapshot snapshot(plater, snapshot_label); + // FIXME: when drag and drop is done on a .3mf or a .amf file we should clear the plater for consistence with the open project command // (the following call to plater->load_files() will load the config data, if present) @@ -1239,7 +1349,7 @@ struct Plater::priv // Data Slic3r::DynamicPrintConfig *config; // FIXME: leak? Slic3r::Print fff_print; - Slic3r::SLAPrint sla_print; + Slic3r::SLAPrint sla_print; Slic3r::Model model; PrinterTechnology printer_technology = ptFFF; Slic3r::GCodePreviewData gcode_preview_data; @@ -1256,8 +1366,385 @@ struct Plater::priv Preview *preview; BackgroundSlicingProcess background_process; - bool arranging; - bool rotoptimizing; + bool suppressed_backround_processing_update { false }; + + // Cache the wti info + class WipeTower: public GLCanvas3D::WipeTowerInfo { + using ArrangePolygon = arrangement::ArrangePolygon; + friend priv; + public: + + void apply_arrange_result(const Vec2crd& tr, double rotation) + { + m_pos = unscaled(tr); m_rotation = rotation; + apply_wipe_tower(); + } + + ArrangePolygon get_arrange_polygon() const + { + Polygon p({ + {coord_t(0), coord_t(0)}, + {scaled(m_bb_size(X)), coord_t(0)}, + {scaled(m_bb_size)}, + {coord_t(0), scaled(m_bb_size(Y))}, + {coord_t(0), coord_t(0)}, + }); + + ArrangePolygon ret; + ret.poly.contour = std::move(p); + ret.translation = scaled(m_pos); + ret.rotation = m_rotation; + return ret; + } + } wipetower; + + WipeTower& updated_wipe_tower() { + auto wti = view3D->get_canvas3d()->get_wipe_tower_info(); + wipetower.m_pos = wti.pos(); + wipetower.m_rotation = wti.rotation(); + wipetower.m_bb_size = wti.bb_size(); + return wipetower; + } + + // A class to handle UI jobs like arranging and optimizing rotation. + // These are not instant jobs, the user has to be informed about their + // state in the status progress indicator. On the other hand they are + // separated from the background slicing process. Ideally, these jobs should + // run when the background process is not running. + // + // TODO: A mechanism would be useful for blocking the plater interactions: + // objects would be frozen for the user. In case of arrange, an animation + // could be shown, or with the optimize orientations, partial results + // could be displayed. + class Job : public wxEvtHandler + { + int m_range = 100; + std::future m_ftr; + priv * m_plater = nullptr; + std::atomic m_running{false}, m_canceled{false}; + bool m_finalized = false; + + void run() + { + m_running.store(true); + process(); + m_running.store(false); + + // ensure to call the last status to finalize the job + update_status(status_range(), ""); + } + + protected: + // status range for a particular job + virtual int status_range() const { return 100; } + + // status update, to be used from the work thread (process() method) + void update_status(int st, const wxString &msg = "") + { + auto evt = new wxThreadEvent(); + evt->SetInt(st); + evt->SetString(msg); + wxQueueEvent(this, evt); + } + + priv & plater() { return *m_plater; } + const priv &plater() const { return *m_plater; } + bool was_canceled() const { return m_canceled.load(); } + + // Launched just before start(), a job can use it to prepare internals + virtual void prepare() {} + + // Launched when the job is finished. It refreshes the 3Dscene by def. + virtual void finalize() + { + // Do a full refresh of scene tree, including regenerating + // all the GLVolumes. FIXME The update function shall just + // reload the modified matrices. + if (!was_canceled()) plater().update(true); + } + + public: + Job(priv *_plater) : m_plater(_plater) + { + Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) { + auto msg = evt.GetString(); + if (!msg.empty()) + plater().statusbar()->set_status_text(msg); + + if (m_finalized) return; + + plater().statusbar()->set_progress(evt.GetInt()); + if (evt.GetInt() == status_range()) { + // set back the original range and cancel callback + plater().statusbar()->set_range(m_range); + plater().statusbar()->set_cancel_callback(); + wxEndBusyCursor(); + + finalize(); + + // dont do finalization again for the same process + m_finalized = true; + } + }); + } + + Job(const Job &) = delete; + Job(Job &&) = default; + Job &operator=(const Job &) = delete; + Job &operator=(Job &&) = default; + + virtual void process() = 0; + + void start() + { // Start the job. No effect if the job is already running + if (!m_running.load()) { + prepare(); + + // Save the current status indicatior range and push the new one + m_range = plater().statusbar()->get_range(); + plater().statusbar()->set_range(status_range()); + + // init cancellation flag and set the cancel callback + m_canceled.store(false); + plater().statusbar()->set_cancel_callback( + [this]() { m_canceled.store(true); }); + + m_finalized = false; + + // Changing cursor to busy + wxBeginBusyCursor(); + + try { // Execute the job + m_ftr = std::async(std::launch::async, &Job::run, this); + } catch (std::exception &) { + update_status(status_range(), + _(L("ERROR: not enough resources to " + "execute a new job."))); + } + + // The state changes will be undone when the process hits the + // last status value, in the status update handler (see ctor) + } + } + + // To wait for the running job and join the threads. False is + // returned if the timeout has been reached and the job is still + // running. Call cancel() before this fn if you want to explicitly + // end the job. + bool join(int timeout_ms = 0) const + { + if (!m_ftr.valid()) return true; + + if (timeout_ms <= 0) + m_ftr.wait(); + else if (m_ftr.wait_for(std::chrono::milliseconds( + timeout_ms)) == std::future_status::timeout) + return false; + + return true; + } + + bool is_running() const { return m_running.load(); } + void cancel() { m_canceled.store(true); } + }; + + enum class Jobs : size_t { + Arrange, + Rotoptimize + }; + + class ArrangeJob : public Job + { + using ArrangePolygon = arrangement::ArrangePolygon; + using ArrangePolygons = arrangement::ArrangePolygons; + + // The gap between logical beds in the x axis expressed in ratio of + // the current bed width. + static const constexpr double LOGICAL_BED_GAP = 1. / 5.; + + ArrangePolygons m_selected, m_unselected; + + // clear m_selected and m_unselected, reserve space for next usage + void clear_input() { + const Model &model = plater().model; + + size_t count = 0; // To know how much space to reserve + for (auto obj : model.objects) count += obj->instances.size(); + m_selected.clear(), m_unselected.clear(); + m_selected.reserve(count + 1 /* for optional wti */); + m_unselected.reserve(count + 1 /* for optional wti */); + } + + // Stride between logical beds + coord_t bed_stride() const { + double bedwidth = plater().bed_shape_bb().size().x(); + return scaled((1. + LOGICAL_BED_GAP) * bedwidth); + } + + // Set up arrange polygon for a ModelInstance and Wipe tower + template ArrangePolygon get_arrange_poly(T *obj) const { + ArrangePolygon ap = obj->get_arrange_polygon(); + ap.priority = 0; + ap.bed_idx = ap.translation.x() / bed_stride(); + ap.setter = [obj, this](const ArrangePolygon &p) { + if (p.is_arranged()) { + auto t = p.translation; t.x() += p.bed_idx * bed_stride(); + obj->apply_arrange_result(t, p.rotation); + } + }; + return ap; + } + + // Prepare all objects on the bed regardless of the selection + void prepare_all() { + clear_input(); + + for (ModelObject *obj: plater().model.objects) + for (ModelInstance *mi : obj->instances) + m_selected.emplace_back(get_arrange_poly(mi)); + + auto& wti = plater().updated_wipe_tower(); + if (wti) m_selected.emplace_back(get_arrange_poly(&wti)); + } + + // Prepare the selected and unselected items separately. If nothing is + // selected, behaves as if everything would be selected. + void prepare_selected() { + clear_input(); + + Model &model = plater().model; + coord_t stride = bed_stride(); + + std::vector + obj_sel(model.objects.size(), nullptr); + + for (auto &s : plater().get_selection().get_content()) + if (s.first < int(obj_sel.size())) obj_sel[s.first] = &s.second; + + // Go through the objects and check if inside the selection + for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) { + const Selection::InstanceIdxsList * instlist = obj_sel[oidx]; + ModelObject *mo = model.objects[oidx]; + + std::vector inst_sel(mo->instances.size(), false); + + if (instlist) + for (auto inst_id : *instlist) inst_sel[inst_id] = true; + + for (size_t i = 0; i < inst_sel.size(); ++i) { + ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]); + + inst_sel[i] ? + m_selected.emplace_back(std::move(ap)) : + m_unselected.emplace_back(std::move(ap)); + } + } + + auto& wti = plater().updated_wipe_tower(); + if (wti) { + ArrangePolygon &&ap = get_arrange_poly(&wti); + + plater().get_selection().is_wipe_tower() ? + m_selected.emplace_back(std::move(ap)) : + m_unselected.emplace_back(std::move(ap)); + } + + // If the selection was empty arrange everything + if (m_selected.empty()) m_selected.swap(m_unselected); + + // The strides have to be removed from the fixed items. For the + // arrangeable (selected) items bed_idx is ignored and the + // translation is irrelevant. + for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride; + } + + protected: + + void prepare() override + { + wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all(); + } + + public: + using Job::Job; + + int status_range() const override { return int(m_selected.size()); } + + void process() override; + + void finalize() override { + // Ignore the arrange result if aborted. + if (was_canceled()) return; + + // Apply the arrange result to all selected objects + for (ArrangePolygon &ap : m_selected) ap.apply(); + + plater().update(false /*dont force_full_scene_refresh*/); + } + }; + + class RotoptimizeJob : public Job + { + public: + using Job::Job; + void process() override; + }; + + // Jobs defined inside the group class will be managed so that only one can + // run at a time. Also, the background process will be stopped if a job is + // started. + class ExclusiveJobGroup { + + static const int ABORT_WAIT_MAX_MS = 10000; + + priv * m_plater; + + ArrangeJob arrange_job{m_plater}; + RotoptimizeJob rotoptimize_job{m_plater}; + + // To create a new job, just define a new subclass of Job, implement + // the process and the optional prepare() and finalize() methods + // Register the instance of the class in the m_jobs container + // if it cannot run concurrently with other jobs in this group + + std::vector> m_jobs{arrange_job, + rotoptimize_job}; + + public: + ExclusiveJobGroup(priv *_plater) : m_plater(_plater) {} + + void start(Jobs jid) { + m_plater->background_process.stop(); + stop_all(); + m_jobs[size_t(jid)].get().start(); + } + + void cancel_all() { for (Job& j : m_jobs) j.cancel(); } + + void join_all(int wait_ms = 0) + { + std::vector aborted(m_jobs.size(), false); + + for (size_t jid = 0; jid < m_jobs.size(); ++jid) + aborted[jid] = m_jobs[jid].get().join(wait_ms); + + if (!all_of(aborted)) + BOOST_LOG_TRIVIAL(error) << "Could not abort a job!"; + } + + void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); } + + const Job& get(Jobs jobid) const { return m_jobs[size_t(jobid)]; } + + bool is_any_running() const + { + return std::any_of(m_jobs.begin(), + m_jobs.end(), + [](const Job &j) { return j.is_running(); }); + } + + } m_ui_jobs{this}; + bool delayed_scene_refresh; std::string delayed_error_message; @@ -1273,17 +1760,26 @@ struct Plater::priv static const std::regex pattern_prusa; priv(Plater *q, MainFrame *main_frame); + ~priv(); - void update(bool force_full_scene_refresh = false); + void update(bool force_full_scene_refresh = false, bool force_background_processing_update = false); void select_view(const std::string& direction); void select_view_3D(const std::string& name); void select_next_view_3D(); + + bool is_preview_shown() const { return current_panel == preview; } + bool is_preview_loaded() const { return preview->is_loaded(); } + bool is_view3D_shown() const { return current_panel == view3D; } + void reset_all_gizmos(); void update_ui_from_settings(); ProgressStatusBar* statusbar(); std::string get_config(const std::string &key) const; BoundingBoxf bed_shape_bb() const; BoundingBox scaled_bed_shape_bb() const; + arrangement::BedShapeHint get_bed_shape_hint() const; + + void find_new_position(const ModelInstancePtrs &instances, coord_t min_d); std::vector load_files(const std::vector& input_files, bool load_model, bool load_config); std::vector load_model_objects(const ModelObjectPtrs &model_objects); wxString get_export_file(GUI::FileType file_type); @@ -1306,6 +1802,24 @@ struct Plater::priv void split_object(); void split_volume(); void scale_selection_to_fit_print_volume(); + + // Return the active Undo/Redo stack. It may be either the main stack or the Gimzo stack. + Slic3r::UndoRedo::Stack& undo_redo_stack() { assert(m_undo_redo_stack_active != nullptr); return *m_undo_redo_stack_active; } + Slic3r::UndoRedo::Stack& undo_redo_stack_main() { return m_undo_redo_stack_main; } + void enter_gizmos_stack(); + void leave_gizmos_stack(); + + void take_snapshot(const std::string& snapshot_name); + void take_snapshot(const wxString& snapshot_name) { this->take_snapshot(std::string(snapshot_name.ToUTF8().data())); } + int get_active_snapshot_index(); + + void undo(); + void redo(); + void undo_redo_to(size_t time_to_load); + + void suppress_snapshots() { this->m_prevent_snapshots++; } + void allow_snapshots() { this->m_prevent_snapshots--; } + bool background_processing_enabled() const { return this->get_config("background_processing") == "1"; } void update_print_volume_state(); void schedule_background_process(); @@ -1316,7 +1830,7 @@ struct Plater::priv UPDATE_BACKGROUND_PROCESS_RESTART = 1, // update_background_process() reports, that the Print / SLAPrint was updated in a way, // that a scene needs to be refreshed (you should call _3DScene::reload_scene(canvas3Dwidget, false)) - UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE = 2, + UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE = 2, // update_background_process() reports, that the Print / SLAPrint is invalid, and the error message // was sent to the status line. UPDATE_BACKGROUND_PROCESS_INVALID = 4, @@ -1329,8 +1843,8 @@ struct Plater::priv unsigned int update_background_process(bool force_validation = false); // Restart background processing thread based on a bitmask of UpdateBackgroundProcessReturnState. bool restart_background_process(unsigned int state); - // returns bit mask of UpdateBackgroundProcessReturnState - unsigned int update_restart_background_process(bool force_scene_update, bool force_preview_update); + // returns bit mask of UpdateBackgroundProcessReturnState + unsigned int update_restart_background_process(bool force_scene_update, bool force_preview_update); void export_gcode(fs::path output_path, PrintHostJob upload_job); void reload_from_disk(); void fix_through_netfabb(const int obj_idx, const int vol_idx = -1); @@ -1362,7 +1876,7 @@ struct Plater::priv // triangulate the bed and store the triangles into m_bed.m_triangles, // fills the m_bed.m_grid_lines and sets m_bed.m_origin. // Sets m_bed.m_polygon to limit the object placement. - void set_bed_shape(const Pointfs& shape); + void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model); bool can_delete() const; bool can_delete_all() const; @@ -1396,9 +1910,21 @@ private: void update_fff_scene(); void update_sla_scene(); + void undo_redo_to(std::vector::const_iterator it_snapshot); + void update_after_undo_redo(const UndoRedo::Snapshot& snapshot, bool temp_snapshot_was_taken = false); // path to project file stored with no extension - wxString m_project_filename; + wxString m_project_filename; + Slic3r::UndoRedo::Stack m_undo_redo_stack_main; + Slic3r::UndoRedo::Stack m_undo_redo_stack_gizmos; + Slic3r::UndoRedo::Stack *m_undo_redo_stack_active = &m_undo_redo_stack_main; + int m_prevent_snapshots = 0; /* Used for avoid of excess "snapshoting". + * Like for "delete selected" or "set numbers of copies" + * we should call tack_snapshot just ones + * instead of calls for each action separately + * */ + std::string m_last_fff_printer_profile_name; + std::string m_last_sla_printer_profile_name; }; const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase); @@ -1411,7 +1937,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) : q(q) , main_frame(main_frame) , config(Slic3r::DynamicPrintConfig::new_from_defaults_keys({ - "bed_shape", "complete_objects", "duplicate_distance", "extruder_clearance_radius", "skirts", "skirt_distance", + "bed_shape", "bed_custom_texture", "bed_custom_model", "complete_objects", "duplicate_distance", "extruder_clearance_radius", "skirts", "skirt_distance", "brim_width", "variable_layer_height", "serial_port", "serial_speed", "host_type", "print_host", "printhost_apikey", "printhost_cafile", "nozzle_diameter", "single_extruder_multi_material", "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", @@ -1423,23 +1949,17 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) })) , sidebar(new Sidebar(q)) , delayed_scene_refresh(false) -#if ENABLE_SVG_ICONS , view_toolbar(GLToolbar::Radio, "View") -#else - , view_toolbar(GLToolbar::Radio) -#endif // ENABLE_SVG_ICONS , m_project_filename(wxEmptyString) { - this->q->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + this->q->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - arranging = false; - rotoptimizing = false; background_process.set_fff_print(&fff_print); - background_process.set_sla_print(&sla_print); + background_process.set_sla_print(&sla_print); background_process.set_gcode_preview_data(&gcode_preview_data); background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED); background_process.set_finished_event(EVT_PROCESS_COMPLETED); - // Default printer technology for default config. + // Default printer technology for default config. background_process.select_technology(this->printer_technology); // Register progress callback from the Print class to the Platter. @@ -1457,7 +1977,11 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) panels.push_back(preview); this->background_process_timer.SetOwner(this->q, 0); - this->q->Bind(wxEVT_TIMER, [this](wxTimerEvent &evt) { this->update_restart_background_process(false, false); }); + this->q->Bind(wxEVT_TIMER, [this](wxTimerEvent &evt) + { + if (!this->suppressed_backround_processing_update) + this->update_restart_background_process(false, false); + }); update(); @@ -1488,7 +2012,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { arrange(); }); view3D_canvas->Bind(EVT_GLCANVAS_SELECT_ALL, [this](SimpleEvent&) { this->q->select_all(); }); view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); }); - view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event &evt) + view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event &evt) { if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); }); view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); }); view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this); @@ -1500,6 +2024,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, &priv::on_3dcanvas_mouse_dragging_finished, this); view3D_canvas->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); }); view3D_canvas->Bind(EVT_GLCANVAS_RESETGIZMOS, [this](SimpleEvent&) { reset_all_gizmos(); }); + view3D_canvas->Bind(EVT_GLCANVAS_UNDO, [this](SimpleEvent&) { this->undo(); }); + view3D_canvas->Bind(EVT_GLCANVAS_REDO, [this](SimpleEvent&) { this->redo(); }); // 3DScene/Toolbar: view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this); @@ -1514,12 +2040,24 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_VOLUMES, &priv::on_action_split_volumes, this); view3D_canvas->Bind(EVT_GLTOOLBAR_LAYERSEDITING, &priv::on_action_layersediting, this); view3D_canvas->Bind(EVT_GLCANVAS_INIT, [this](SimpleEvent&) { init_view_toolbar(); }); - view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) { set_bed_shape(config->option("bed_shape")->values); }); + view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) + { + set_bed_shape(config->option("bed_shape")->values, + config->option("bed_custom_texture")->value, + config->option("bed_custom_model")->value); + }); // Preview events: preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); }); - preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) { set_bed_shape(config->option("bed_shape")->values); }); + preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) + { + set_bed_shape(config->option("bed_shape")->values, + config->option("bed_custom_texture")->value, + config->option("bed_custom_model")->value); + }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); }); + preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, [this](wxKeyEvent& evt) { preview->move_double_slider(evt); }); + preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_EDIT_COLOR_CHANGE, [this](wxKeyEvent& evt) { preview->edit_double_slider(evt); }); q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this); q->Bind(EVT_PROCESS_COMPLETED, &priv::on_process_completed, this); @@ -1533,11 +2071,24 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) q->Layout(); set_current_panel(view3D); + + // updates camera type from .ini file + camera.set_type(get_config("use_perspective_camera")); + + // Initialize the Undo / Redo stack with a first snapshot. + this->take_snapshot(_(L("New Project"))); } -void Plater::priv::update(bool force_full_scene_refresh) +Plater::priv::~priv() { - wxWindowUpdateLocker freeze_guard(q); + if (config != nullptr) + delete config; +} + +void Plater::priv::update(bool force_full_scene_refresh, bool force_background_processing_update) +{ + // the following line, when enabled, causes flickering on NVIDIA graphics cards +// wxWindowUpdateLocker freeze_guard(q); if (get_config("autocenter") == "1") { // auto *bed_shape_opt = config->opt("bed_shape"); // const auto bed_shape = Slic3r::Polygon::new_scale(bed_shape_opt->values); @@ -1547,12 +2098,12 @@ void Plater::priv::update(bool force_full_scene_refresh) } unsigned int update_status = 0; - if (this->printer_technology == ptSLA) + if (this->printer_technology == ptSLA || force_background_processing_update) // Update the SLAPrint from the current Model, so that the reload_scene() // pulls the correct data. update_status = this->update_background_process(false); this->view3D->reload_scene(false, force_full_scene_refresh); - this->preview->reload_print(); + this->preview->reload_print(); if (this->printer_technology == ptSLA) this->restart_background_process(update_status); else @@ -1599,15 +2150,13 @@ void Plater::priv::update_ui_from_settings() // $self->{buttons_sizer}->Layout; // } -#if ENABLE_RETINA_GL view3D->get_canvas3d()->update_ui_from_settings(); preview->get_canvas3d()->update_ui_from_settings(); -#endif } ProgressStatusBar* Plater::priv::statusbar() { - return main_frame->m_statusbar; + return main_frame->m_statusbar.get(); } std::string Plater::priv::get_config(const std::string &key) const @@ -1669,12 +2218,28 @@ std::vector Plater::priv::load_files(const std::vector& input_ DynamicPrintConfig config; { DynamicPrintConfig config_loaded; - model = Slic3r::Model::read_from_archive(path.string(), &config_loaded, false); + model = Slic3r::Model::read_from_archive(path.string(), &config_loaded, false, load_config); if (load_config && !config_loaded.empty()) { // Based on the printer technology field found in the loaded config, select the base for the config, - PrinterTechnology printer_technology = Preset::printer_technology(config_loaded); - config.apply(printer_technology == ptFFF ? - static_cast(FullPrintConfig::defaults()) : + PrinterTechnology printer_technology = Preset::printer_technology(config_loaded); + + // We can't to load SLA project if there is at least one multi-part object on the bed + if (printer_technology == ptSLA) + { + const ModelObjectPtrs& objects = q->model().objects; + for (auto object : objects) + if (object->volumes.size() > 1) + { + Slic3r::GUI::show_info(nullptr, + _(L("You can't to load SLA project if there is at least one multi-part object on the bed")) + "\n\n" + + _(L("Please check your object list before preset changing.")), + _(L("Attention!"))); + return obj_idxs; + } + } + + config.apply(printer_technology == ptFFF ? + static_cast(FullPrintConfig::defaults()) : static_cast(SLAFullPrintConfig::defaults())); // and place the loaded config over the base. config += std::move(config_loaded); @@ -1693,7 +2258,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ } } else { - model = Slic3r::Model::read_from_file(path.string(), nullptr, false); + model = Slic3r::Model::read_from_file(path.string(), nullptr, false, load_config); for (auto obj : model.objects) if (obj->name.empty()) obj->name = fs::path(obj->input_file).filename().string(); @@ -1719,74 +2284,30 @@ std::vector Plater::priv::load_files(const std::vector& input_ } } } - else if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf)) - { - bool advanced = false; - for (const ModelObject* model_object : model.objects) + else if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf) && model_has_advanced_features(model)) { + wxMessageDialog dlg(q, _(L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?\n")), + _(L("Detected advanced data")), wxICON_WARNING | wxYES | wxNO); + if (dlg.ShowModal() == wxID_YES) { - // is there more than one instance ? - if (model_object->instances.size() > 1) - { - advanced = true; - break; - } - - // is there any advanced config data ? - auto opt_keys = model_object->config.keys(); - if (!opt_keys.empty() && !((opt_keys.size() == 1) && (opt_keys[0] == "extruder"))) - { - advanced = true; - break; - } - - // is there any modifier ? - for (const ModelVolume* model_volume : model_object->volumes) - { - if (!model_volume->is_model_part()) - { - advanced = true; - break; - } - - // is there any advanced config data ? - opt_keys = model_volume->config.keys(); - if (!opt_keys.empty() && !((opt_keys.size() == 1) && (opt_keys[0] == "extruder"))) - { - advanced = true; - break; - } - } - - if (advanced) - break; - } - - if (advanced) - { - wxMessageDialog dlg(q, _(L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?\n")), - _(L("Detected advanced data")), wxICON_WARNING | wxYES | wxNO); - if (dlg.ShowModal() == wxID_YES) - { - Slic3r::GUI::wxGetApp().save_mode(comAdvanced); - view3D->set_as_dirty(); - } - else - return obj_idxs; + Slic3r::GUI::wxGetApp().save_mode(comAdvanced); + view3D->set_as_dirty(); } + else + return obj_idxs; } - for (ModelObject* model_object : model.objects) { - model_object->center_around_origin(false); - model_object->ensure_on_bed(); - } + for (ModelObject* model_object : model.objects) { + model_object->center_around_origin(false); + model_object->ensure_on_bed(); + } // check multi-part object adding for the SLA-printing if (printer_technology == ptSLA) { for (auto obj : model.objects) if ( obj->volumes.size()>1 ) { - Slic3r::GUI::show_error(nullptr, - wxString::Format(_(L("You can't to add the object(s) from %s because of one or some of them is(are) multi-part")), + Slic3r::GUI::show_error(nullptr, + wxString::Format(_(L("You can't to add the object(s) from %s because of one or some of them is(are) multi-part")), from_path(filename))); return obj_idxs; } @@ -1804,7 +2325,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ } } - if (new_model != nullptr) { + if (new_model != nullptr && new_model->objects.size() > 1) { wxMessageDialog dlg(q, _(L( "Multiple objects were loaded for a multi-material printer.\n" "Instead of considering them as multiple objects, should I consider\n" @@ -1828,6 +2349,9 @@ std::vector Plater::priv::load_files(const std::vector& input_ // automatic selection of added objects if (!obj_idxs.empty() && (view3D != nullptr)) { + // update printable state for new volumes on canvas3D + wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_objects(obj_idxs); + Selection& selection = view3D->get_canvas3d()->get_selection(); selection.clear(); for (size_t idx : obj_idxs) @@ -1868,9 +2392,9 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode #else /* AUTOPLACEMENT_ON_LOAD */ // if object has no defined position(s) we need to rearrange everything after loading need_arrange = true; - // add a default instance and center object around origin - object->center_around_origin(); // also aligns object to Z = 0 - ModelInstance* instance = object->add_instance(); + // add a default instance and center object around origin + object->center_around_origin(); // also aligns object to Z = 0 + ModelInstance* instance = object->add_instance(); instance->set_offset(Slic3r::to_3d(bed_shape.center().cast(), -object->origin_translation(2))); #endif /* AUTOPLACEMENT_ON_LOAD */ } @@ -1881,8 +2405,8 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode if (max_ratio > 10000) { // the size of the object is too big -> this could lead to overflow when moving to clipper coordinates, // so scale down the mesh - double inv = 1. / max_ratio; - object->scale_mesh(Vec3d(inv, inv, inv)); + double inv = 1. / max_ratio; + object->scale_mesh_after_creation(Vec3d(inv, inv, inv)); object->origin_translation = Vec3d::Zero(); object->center_around_origin(); scaled_down = true; @@ -1895,9 +2419,6 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode } object->ensure_on_bed(); - - // print.auto_assign_extruders(object); - // print.add_model_object(object); } #ifdef AUTOPLACEMENT_ON_LOAD @@ -1909,7 +2430,7 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode Polyline bed; bed.points.reserve(bedpoints.size()); for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); - arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info(); + std::pair wti = view3D->get_canvas3d()->get_wipe_tower_info(); arr::find_new_position(model, new_instances, min_obj_distance, bed, wti); @@ -1944,6 +2465,7 @@ wxString Plater::priv::get_export_file(GUI::FileType file_type) case FT_AMF: case FT_3MF: case FT_GCODE: + case FT_OBJ: wildcard = file_wildcards(file_type); break; default: @@ -1994,18 +2516,23 @@ wxString Plater::priv::get_export_file(GUI::FileType file_type) dlg_title = _(L("Save file as:")); break; } + case FT_OBJ: + { + output_file.replace_extension("obj"); + dlg_title = _(L("Export OBJ file:")); + break; + } default: break; } - wxFileDialog* dlg = new wxFileDialog(q, dlg_title, + wxFileDialog dlg(q, dlg_title, from_path(output_file.parent_path()), from_path(output_file.filename()), wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - if (dlg->ShowModal() != wxID_OK) { + if (dlg.ShowModal() != wxID_OK) return wxEmptyString; - } - wxString out_path = dlg->GetPath(); + wxString out_path = dlg.GetPath(); fs::path path(into_path(out_path)); wxGetApp().app_config->update_last_output_dir(path.parent_path().string()); @@ -2032,7 +2559,7 @@ int Plater::priv::get_selected_volume_idx() const { auto& selection = get_selection(); int idx = selection.get_object_idx(); - if ((0 > idx) || (idx > 1000)) + if ((0 > idx) || (idx > 1000)) return-1; const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); if (model.objects[idx]->volumes.size() > 1) @@ -2064,12 +2591,15 @@ void Plater::priv::object_list_changed() void Plater::priv::select_all() { +// this->take_snapshot(_(L("Select All"))); + view3D->select_all(); this->sidebar->obj_list()->update_selections(); } void Plater::priv::deselect_all() { +// this->take_snapshot(_(L("Deselect All"))); view3D->deselect_all(); } @@ -2091,6 +2621,10 @@ void Plater::priv::remove(size_t obj_idx) void Plater::priv::delete_object_from_model(size_t obj_idx) { + wxString snapshot_label = _(L("Delete Object")); + if (! model.objects[obj_idx]->name.empty()) + snapshot_label += ": " + wxString::FromUTF8(model.objects[obj_idx]->name.c_str()); + Plater::TakeSnapshot snapshot(q, snapshot_label); model.delete_object(obj_idx); update(); object_list_changed(); @@ -2098,6 +2632,8 @@ void Plater::priv::delete_object_from_model(size_t obj_idx) void Plater::priv::reset() { + Plater::TakeSnapshot snapshot(q, _(L("Reset Project"))); + set_project_filename(wxEmptyString); // Prevent toolpaths preview from rendering while we modify the Print object @@ -2128,197 +2664,140 @@ void Plater::priv::mirror(Axis axis) void Plater::priv::arrange() { - if (arranging) { return; } - arranging = true; - Slic3r::ScopeGuard arranging_guard([this]() { arranging = false; }); - - wxBusyCursor wait; - - this->background_process.stop(); - - unsigned count = 0; - for(auto obj : model.objects) count += obj->instances.size(); - - auto prev_range = statusbar()->get_range(); - statusbar()->set_range(count); - - auto statusfn = [this, count] (unsigned st, const std::string& msg) { - /* // In case we would run the arrange asynchronously - wxCommandEvent event(EVT_PROGRESS_BAR); - event.SetInt(st); - event.SetString(msg); - wxQueueEvent(this->q, event.Clone()); */ - statusbar()->set_progress(count - st); - statusbar()->set_status_text(_(msg)); - - // ok, this is dangerous, but we are protected by the flag - // 'arranging' and the arrange button is also disabled. - // This call is needed for the cancel button to work. - wxYieldIfNeeded(); - }; - - statusbar()->set_cancel_callback([this, statusfn](){ - arranging = false; - statusfn(0, L("Arranging canceled")); - }); - - static const std::string arrangestr = L("Arranging"); - - // FIXME: I don't know how to obtain the minimum distance, it depends - // on printer technology. I guess the following should work but it crashes. - double dist = 6; //PrintConfig::min_object_distance(config); - if(printer_technology == ptFFF) { - dist = PrintConfig::min_object_distance(config); - } - - auto min_obj_distance = coord_t(dist/SCALING_FACTOR); - - const auto *bed_shape_opt = config->opt("bed_shape"); - - assert(bed_shape_opt); - auto& bedpoints = bed_shape_opt->values; - Polyline bed; bed.points.reserve(bedpoints.size()); - for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); - - statusfn(0, arrangestr); - - arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info(); - - try { - arr::BedShapeHint hint; - - // TODO: from Sasha from GUI or - hint.type = arr::BedShapeType::WHO_KNOWS; - - arr::arrange(model, - wti, - min_obj_distance, - bed, - hint, - false, // create many piles not just one pile - [statusfn](unsigned st) { statusfn(st, arrangestr); }, - [this] () { return !arranging; }); - } catch(std::exception& /*e*/) { - GUI::show_error(this->q, L("Could not arrange model objects! " - "Some geometries may be invalid.")); - } - - // it remains to move the wipe tower: - view3D->get_canvas3d()->arrange_wipe_tower(wti); - - statusfn(0, L("Arranging done.")); - statusbar()->set_range(prev_range); - statusbar()->set_cancel_callback(); // remove cancel button - - // Do a full refresh of scene tree, including regenerating all the GLVolumes. - //FIXME The update function shall just reload the modified matrices. - update(true); + this->take_snapshot(_(L("Arrange"))); + m_ui_jobs.start(Jobs::Arrange); } // This method will find an optimal orientation for the currently selected item // Very similar in nature to the arrange method above... void Plater::priv::sla_optimize_rotation() { + this->take_snapshot(_(L("Optimize Rotation"))); + m_ui_jobs.start(Jobs::Rotoptimize); +} - // TODO: we should decide whether to allow arrange when the search is - // running we should probably disable explicit slicing and background - // processing - - if (rotoptimizing) { return; } - rotoptimizing = true; - Slic3r::ScopeGuard rotoptimizing_guard([this]() { rotoptimizing = false; }); - - int obj_idx = get_selected_object_idx(); - if (obj_idx < 0) { return; } - - ModelObject * o = model.objects[size_t(obj_idx)]; - - background_process.stop(); - - auto prev_range = statusbar()->get_range(); - statusbar()->set_range(100); - - auto stfn = [this] (unsigned st, const std::string& msg) { - statusbar()->set_progress(int(st)); - statusbar()->set_status_text(msg); - - // could be problematic, but we need the cancel button. - wxYieldIfNeeded(); - }; - - statusbar()->set_cancel_callback([this, stfn](){ - rotoptimizing = false; - stfn(0, L("Orientation search canceled")); - }); - - auto r = sla::find_best_rotation( - *o, .005f, - [stfn](unsigned s) { stfn(s, L("Searching for optimal orientation")); }, - [this](){ return !rotoptimizing; } - ); +arrangement::BedShapeHint Plater::priv::get_bed_shape_hint() const { const auto *bed_shape_opt = config->opt("bed_shape"); assert(bed_shape_opt); - auto& bedpoints = bed_shape_opt->values; - Polyline bed; bed.points.reserve(bedpoints.size()); - for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); + if (!bed_shape_opt) return {}; - double mindist = 6.0; // FIXME - double offs = mindist / 2.0 - EPSILON; + auto &bedpoints = bed_shape_opt->values; + Polyline bedpoly; bedpoly.points.reserve(bedpoints.size()); + for (auto &v : bedpoints) bedpoly.append(scaled(v)); - if(rotoptimizing) // wasn't canceled - for(ModelInstance * oi : o->instances) { - oi->set_rotation({r[X], r[Y], r[Z]}); + return arrangement::BedShapeHint(bedpoly); +} - auto trchull = o->convex_hull_2d(oi->get_transformation().get_matrix()); +void Plater::priv::find_new_position(const ModelInstancePtrs &instances, + coord_t min_d) +{ + arrangement::ArrangePolygons movable, fixed; - namespace opt = libnest2d::opt; - opt::StopCriteria stopcr; - stopcr.relative_score_difference = 0.01; - stopcr.max_iterations = 10000; - stopcr.stop_score = 0.0; - opt::GeneticOptimizer solver(stopcr); - Polygon pbed(bed); + for (const ModelObject *mo : model.objects) + for (const ModelInstance *inst : mo->instances) { + auto it = std::find(instances.begin(), instances.end(), inst); + auto arrpoly = inst->get_arrange_polygon(); - auto bin = pbed.bounding_box(); - double binw = bin.size()(X) * SCALING_FACTOR - offs; - double binh = bin.size()(Y) * SCALING_FACTOR - offs; + if (it == instances.end()) + fixed.emplace_back(std::move(arrpoly)); + else + movable.emplace_back(std::move(arrpoly)); + } - auto result = solver.optimize_min([&trchull, binw, binh](double rot){ - auto chull = trchull; - chull.rotate(rot); + if (updated_wipe_tower()) + fixed.emplace_back(wipetower.get_arrange_polygon()); - auto bb = chull.bounding_box(); - double bbw = bb.size()(X) * SCALING_FACTOR; - double bbh = bb.size()(Y) * SCALING_FACTOR; + arrangement::arrange(movable, fixed, min_d, get_bed_shape_hint()); - auto wdiff = bbw - binw; - auto hdiff = bbh - binh; - double diff = 0; - if(wdiff < 0 && hdiff < 0) diff = wdiff + hdiff; - if(wdiff > 0) diff += wdiff; - if(hdiff > 0) diff += hdiff; + for (size_t i = 0; i < instances.size(); ++i) + if (movable[i].bed_idx == 0) + instances[i]->apply_arrange_result(movable[i].translation, + movable[i].rotation); +} - return diff; - }, opt::initvals(0.0), opt::bound(-PI/2, PI/2)); +void Plater::priv::ArrangeJob::process() { + static const auto arrangestr = _(L("Arranging")); - double r = std::get<0>(result.optimum); - - Vec3d rt = oi->get_rotation(); rt(Z) += r; - oi->set_rotation(rt); + // FIXME: I don't know how to obtain the minimum distance, it depends + // on printer technology. I guess the following should work but it crashes. + double dist = 6; // PrintConfig::min_object_distance(config); + if (plater().printer_technology == ptFFF) { + dist = PrintConfig::min_object_distance(plater().config); } - arr::WipeTowerInfo wti; // useless in SLA context - arr::find_new_position(model, o->instances, coord_t(mindist/SCALING_FACTOR), bed, wti); + coord_t min_d = scaled(dist); + auto count = unsigned(m_selected.size()); + arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint(); - // Correct the z offset of the object which was corrupted be the rotation - o->ensure_on_bed(); + try { + arrangement::arrange(m_selected, m_unselected, min_d, bedshape, + [this, count](unsigned st) { + if (st > + 0) // will not finalize after last one + update_status(count - st, arrangestr); + }, + [this]() { return was_canceled(); }); + } catch (std::exception & /*e*/) { + GUI::show_error(plater().q, + _(L("Could not arrange model objects! " + "Some geometries may be invalid."))); + } - stfn(0, L("Orientation found.")); - statusbar()->set_range(prev_range); - statusbar()->set_cancel_callback(); + // finalize just here. + update_status(int(count), + was_canceled() ? _(L("Arranging canceled.")) + : _(L("Arranging done."))); +} - update(true); +void Plater::priv::RotoptimizeJob::process() +{ + int obj_idx = plater().get_selected_object_idx(); + if (obj_idx < 0) { return; } + + ModelObject *o = plater().model.objects[size_t(obj_idx)]; + + auto r = sla::find_best_rotation( + *o, + .005f, + [this](unsigned s) { + if (s < 100) + update_status(int(s), + _(L("Searching for optimal orientation"))); + }, + [this]() { return was_canceled(); }); + + + double mindist = 6.0; // FIXME + + if (!was_canceled()) { + for(ModelInstance * oi : o->instances) { + oi->set_rotation({r[X], r[Y], r[Z]}); + + auto trmatrix = oi->get_transformation().get_matrix(); + Polygon trchull = o->convex_hull_2d(trmatrix); + + MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex); + double r = rotbb.angle_to_X(); + + // The box should be landscape + if(rotbb.width() < rotbb.height()) r += PI / 2; + + Vec3d rt = oi->get_rotation(); rt(Z) += r; + + oi->set_rotation(rt); + } + + plater().find_new_position(o->instances, scaled(mindist)); + + // Correct the z offset of the object which was corrupted be + // the rotation + o->ensure_on_bed(); + } + + update_status(100, + was_canceled() ? _(L("Orientation search canceled.")) + : _(L("Orientation found."))); } void Plater::priv::split_object() @@ -2345,6 +2824,8 @@ void Plater::priv::split_object() Slic3r::GUI::warning_catcher(q, _(L("The selected object couldn't be split because it contains only one part."))); else { + Plater::TakeSnapshot snapshot(q, _(L("Split to Objects"))); + unsigned int counter = 1; for (ModelObject* m : new_objects) m->name = current_model_object->name + "_" + std::to_string(counter++); @@ -2398,7 +2879,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation) // bitmap of enum UpdateBackgroundProcessReturnState unsigned int return_state = 0; - // If the update_background_process() was not called by the timer, kill the timer, + // If the update_background_process() was not called by the timer, kill the timer, // so the update_restart_background_process() will not be called again in vain. this->background_process_timer.Stop(); // Update the "out of print bed" state of ModelInstances. @@ -2419,21 +2900,21 @@ unsigned int Plater::priv::update_background_process(bool force_validation) this->sidebar->show_sliced_info_sizer(false); // Reset preview canvases. If the print has been invalidated, the preview canvases will be cleared. // Otherwise they will be just refreshed. - if (this->preview != nullptr) - // If the preview is not visible, the following line just invalidates the preview, - // but the G-code paths or SLA preview are calculated first once the preview is made visible. - this->preview->reload_print(); - // In FDM mode, we need to reload the 3D scene because of the wipe tower preview box. - // In SLA mode, we need to reload the 3D scene every time to show the support structures. - if (this->printer_technology == ptSLA || (this->printer_technology == ptFFF && this->config->opt_bool("wipe_tower"))) - return_state |= UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE; + if (this->preview != nullptr) + // If the preview is not visible, the following line just invalidates the preview, + // but the G-code paths or SLA preview are calculated first once the preview is made visible. + this->preview->reload_print(); + // In FDM mode, we need to reload the 3D scene because of the wipe tower preview box. + // In SLA mode, we need to reload the 3D scene every time to show the support structures. + if (this->printer_technology == ptSLA || (this->printer_technology == ptFFF && this->config->opt_bool("wipe_tower"))) + return_state |= UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE; } if ((invalidated != Print::APPLY_STATUS_UNCHANGED || force_validation) && ! this->background_process.empty()) { // The state of the Print changed, and it is non-zero. Let's validate it and give the user feedback on errors. std::string err = this->background_process.validate(); if (err.empty()) { - if (invalidated != Print::APPLY_STATUS_UNCHANGED && this->background_processing_enabled()) + if (invalidated != Print::APPLY_STATUS_UNCHANGED && this->background_processing_enabled()) return_state |= UPDATE_BACKGROUND_PROCESS_RESTART; } else { // The print is not valid. @@ -2455,12 +2936,12 @@ unsigned int Plater::priv::update_background_process(bool force_validation) if (invalidated != Print::APPLY_STATUS_UNCHANGED && was_running && ! this->background_process.running() && (return_state & UPDATE_BACKGROUND_PROCESS_RESTART) == 0) { - // The background processing was killed and it will not be restarted. - wxCommandEvent evt(EVT_PROCESS_COMPLETED); - evt.SetInt(-1); - // Post the "canceled" callback message, so that it will be processed after any possible pending status bar update messages. - wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone()); - } + // The background processing was killed and it will not be restarted. + wxCommandEvent evt(EVT_PROCESS_COMPLETED); + evt.SetInt(-1); + // Post the "canceled" callback message, so that it will be processed after any possible pending status bar update messages. + wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone()); + } if ((return_state & UPDATE_BACKGROUND_PROCESS_INVALID) != 0) { @@ -2478,17 +2959,17 @@ unsigned int Plater::priv::update_background_process(bool force_validation) sidebar->set_btn_label(ActionButtonType::abExport, _(label_btn_export)); sidebar->set_btn_label(ActionButtonType::abSendGCode, _(label_btn_send)); - - const wxString slice_string = background_process.running() && wxGetApp().get_mode() == comSimple ? + + const wxString slice_string = background_process.running() && wxGetApp().get_mode() == comSimple ? _(L("Slicing")) + dots : _(L("Slice now")); sidebar->set_btn_label(ActionButtonType::abReslice, slice_string); if (background_process.finished()) show_action_buttons(false); - else if (!background_process.empty() && + else if (!background_process.empty() && !background_process.running()) /* Do not update buttons if background process is running - * This condition is important for SLA mode especially, - * when this function is called several times during calculations + * This condition is important for SLA mode especially, + * when this function is called several times during calculations * */ show_action_buttons(true); } @@ -2499,14 +2980,14 @@ unsigned int Plater::priv::update_background_process(bool force_validation) // Restart background processing thread based on a bitmask of UpdateBackgroundProcessReturnState. bool Plater::priv::restart_background_process(unsigned int state) { - if (arranging || rotoptimizing) { + if (m_ui_jobs.is_any_running()) { // Avoid a race condition return false; } - if ( ! this->background_process.empty() && - (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) == 0 && - ( ((state & UPDATE_BACKGROUND_PROCESS_FORCE_RESTART) != 0 && ! this->background_process.finished()) || + if ( ! this->background_process.empty() && + (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) == 0 && + ( ((state & UPDATE_BACKGROUND_PROCESS_FORCE_RESTART) != 0 && ! this->background_process.finished()) || (state & UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT) != 0 || (state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 ) ) { // The print is valid and it can be started. @@ -2562,7 +3043,7 @@ unsigned int Plater::priv::update_restart_background_process(bool force_update_s if (force_update_preview) this->preview->reload_print(); this->restart_background_process(state); - return state; + return state; } void Plater::priv::update_fff_scene() @@ -2570,7 +3051,7 @@ void Plater::priv::update_fff_scene() if (this->preview != nullptr) this->preview->reload_print(); // In case this was MM print, wipe tower bounding box on 3D tab might need redrawing with exact depth: - view3D->reload_scene(true); + view3D->reload_scene(true); } void Plater::priv::update_sla_scene() @@ -2583,6 +3064,8 @@ void Plater::priv::update_sla_scene() void Plater::priv::reload_from_disk() { + Plater::TakeSnapshot snapshot(q, _(L("Reload from Disk"))); + const auto &selection = get_selection(); const auto obj_orig_idx = selection.get_object_idx(); if (selection.is_wipe_tower() || obj_orig_idx == -1) { return; } @@ -2616,6 +3099,9 @@ void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* = { if (obj_idx < 0) return; + + Plater::TakeSnapshot snapshot(q, _(L("Fix Throught NetFabb"))); + fix_model_by_win10_sdk_gui(*model.objects[obj_idx], vol_idx); this->update(); this->object_list_changed(); @@ -2681,7 +3167,7 @@ void Plater::priv::set_current_panel(wxPanel* panel) } else if (current_panel == preview) { - this->q->reslice(); + this->q->reslice(); // keeps current gcode preview, if any preview->reload_print(true); preview->set_canvas_as_dirty(); @@ -2698,12 +3184,12 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) auto idx = combo->get_extruder_idx(); - //! Because of The MSW and GTK version of wxBitmapComboBox derived from wxComboBox, + //! Because of The MSW and GTK version of wxBitmapComboBox derived from wxComboBox, //! but the OSX version derived from wxOwnerDrawnCombo. - //! So, to get selected string we do - //! combo->GetString(combo->GetSelection()) - //! instead of - //! combo->GetStringSelection().ToUTF8().data()); + //! So, to get selected string we do + //! combo->GetString(combo->GetSelection()) + //! instead of + //! combo->GetStringSelection().ToUTF8().data()); const std::string& selected_string = combo->GetString(combo->GetSelection()).ToUTF8().data(); @@ -2715,7 +3201,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) if (preset_type == Preset::TYPE_FILAMENT && sidebar->is_multifilament()) { // Only update the platter UI for the 2nd and other filaments. wxGetApp().preset_bundle->update_platter_filament_ui(idx, combo); - } + } else { wxWindowUpdateLocker noUpdates(sidebar->presets_panel()); wxGetApp().get_tab(preset_type)->select_preset(selected_string); @@ -2723,14 +3209,20 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) // update plater with new config wxGetApp().plater()->on_config_change(wxGetApp().preset_bundle->full_config()); + /* Settings list can be changed after printer preset changing, so + * update all settings items for all item had it. + * Furthermore, Layers editing is implemented only for FFF printers + * and for SLA presets they should be deleted + */ if (preset_type == Preset::TYPE_PRINTER) - wxGetApp().obj_list()->update_settings_items(); +// wxGetApp().obj_list()->update_settings_items(); + wxGetApp().obj_list()->update_object_list_by_printer_technology(); } void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) { if (evt.status.percent >= -1) { - if (arranging || rotoptimizing) { + if (m_ui_jobs.is_any_running()) { // Avoid a race condition return; } @@ -2780,9 +3272,9 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) this->background_process.stop(); this->statusbar()->reset_cancel_callback(); this->statusbar()->stop_busy(); - + const bool canceled = evt.GetInt() < 0; - const bool error = evt.GetInt() == 0; + const bool error = evt.GetInt() == 0; const bool success = evt.GetInt() > 0; // Reset the "export G-code path" name, so that the automatic background processing will be enabled again. this->background_process.reset_export(); @@ -2794,8 +3286,8 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) show_error(q, message); this->statusbar()->set_status_text(message); } - if (canceled) - this->statusbar()->set_status_text(_(L("Cancelled"))); + if (canceled) + this->statusbar()->set_status_text(_(L("Cancelled"))); this->sidebar->show_sliced_info_sizer(success); @@ -2803,7 +3295,7 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) // Namely, it refreshes the "Out of print bed" property of all the ModelObjects, and it enables // the "Slice now" and "Export G-code" buttons based on their "out of bed" status. this->object_list_changed(); - + // refresh preview switch (this->printer_technology) { case ptFFF: @@ -2855,6 +3347,8 @@ void Plater::priv::on_action_layersediting(SimpleEvent&) void Plater::priv::on_object_select(SimpleEvent& evt) { +// this->take_snapshot(_(L("Object Selection"))); + wxGetApp().obj_list()->update_selections(); selection_changed(); } @@ -2936,7 +3430,7 @@ void Plater::priv::on_update_geometry(Vec3dsEvent<2>&) // TODO } -// Update the scene from the background processing, +// Update the scene from the background processing, // if the update message was received during mouse manipulation. void Plater::priv::on_3dcanvas_mouse_dragging_finished(SimpleEvent&) { @@ -2993,6 +3487,9 @@ void Plater::priv::set_project_filename(const wxString& filename) m_project_filename = from_path(full_path); wxGetApp().mainframe->update_title(); + + if (!filename.empty()) + wxGetApp().mainframe->add_to_recent_projects(filename); } bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/) @@ -3024,6 +3521,9 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ sidebar->obj_list()->append_menu_item_instance_to_object(menu, q); menu->AppendSeparator(); + wxMenuItem* menu_item_printable = sidebar->obj_list()->append_menu_item_printable(menu, q); + menu->AppendSeparator(); + append_menu_item(menu, wxID_ANY, _(L("Reload from Disk")), _(L("Reload the selected file from Disk")), [this](wxCommandEvent&) { reload_from_disk(); }); @@ -3031,6 +3531,17 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ [this](wxCommandEvent&) { q->export_stl(false, true); }); menu->AppendSeparator(); + + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { + const Selection& selection = get_selection(); + int instance_idx = selection.get_instance_idx(); + evt.Enable(instance_idx != -1); + if (instance_idx != -1) + { + evt.Check(model.objects[selection.get_object_idx()]->instances[instance_idx]->printable); + view3D->set_as_dirty(); + } + }, menu_item_printable->GetId()); } sidebar->obj_list()->append_menu_item_fix_through_netfabb(menu); @@ -3065,10 +3576,14 @@ bool Plater::priv::complit_init_object_menu() append_menu_item(split_menu, wxID_ANY, _(L("To parts")), _(L("Split the selected object into individual sub-parts")), [this](wxCommandEvent&) { split_volume(); }, "split_parts_SMALL", &object_menu, [this]() { return can_split(); }, q); - append_submenu(&object_menu, split_menu, wxID_ANY, _(L("Split")), _(L("Split the selected object")), "", + append_submenu(&object_menu, split_menu, wxID_ANY, _(L("Split")), _(L("Split the selected object")), "", [this]() { return can_split() && wxGetApp().get_mode() > comSimple; }, q); object_menu.AppendSeparator(); + // Layers Editing for object + sidebar->obj_list()->append_menu_item_layers_editing(&object_menu); + object_menu.AppendSeparator(); + // "Add (volumes)" popupmenu will be added later in append_menu_items_add_volume() return true; @@ -3103,12 +3618,6 @@ bool Plater::priv::complit_init_part_menu() void Plater::priv::init_view_toolbar() { -#if !ENABLE_SVG_ICONS - ItemsIconsTexture::Metadata icons_data; - icons_data.filename = "view_toolbar.png"; - icons_data.icon_size = 64; -#endif // !ENABLE_SVG_ICONS - BackgroundTexture::Metadata background_data; background_data.filename = "toolbar_background.png"; background_data.left = 16; @@ -3116,38 +3625,29 @@ void Plater::priv::init_view_toolbar() background_data.right = 16; background_data.bottom = 16; -#if ENABLE_SVG_ICONS if (!view_toolbar.init(background_data)) -#else - if (!view_toolbar.init(icons_data, background_data)) -#endif // ENABLE_SVG_ICONS return; - view_toolbar.set_layout_orientation(GLToolbar::Layout::Bottom); + view_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Left); + view_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Bottom); view_toolbar.set_border(5.0f); view_toolbar.set_gap_size(1.0f); GLToolbarItem::Data item; item.name = "3D"; -#if ENABLE_SVG_ICONS item.icon_filename = "editor.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("3D editor view")) + " [" + GUI::shortkey_ctrl_prefix() + "5]"; item.sprite_id = 0; - item.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_3D)); }; - item.is_toggable = false; + item.left.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_3D)); }; if (!view_toolbar.add_item(item)) return; item.name = "Preview"; -#if ENABLE_SVG_ICONS item.icon_filename = "preview.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Preview")) + " [" + GUI::shortkey_ctrl_prefix() + "6]"; item.sprite_id = 1; - item.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_PREVIEW)); }; - item.is_toggable = false; + item.left.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_PREVIEW)); }; if (!view_toolbar.add_item(item)) return; @@ -3180,9 +3680,9 @@ bool Plater::priv::can_mirror() const return get_selection().is_from_single_instance(); } -void Plater::priv::set_bed_shape(const Pointfs& shape) +void Plater::priv::set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model) { - bool new_shape = bed.set_shape(shape); + bool new_shape = bed.set_shape(shape, custom_texture, custom_model); if (new_shape) { if (view3D) view3D->bed_shape_changed(); @@ -3192,7 +3692,7 @@ void Plater::priv::set_bed_shape(const Pointfs& shape) bool Plater::priv::can_delete() const { - return !get_selection().is_empty() && !get_selection().is_wipe_tower(); + return !get_selection().is_empty() && !get_selection().is_wipe_tower() && !m_ui_jobs.is_any_running(); } bool Plater::priv::can_delete_all() const @@ -3211,7 +3711,7 @@ bool Plater::priv::can_fix_through_netfabb() const bool Plater::priv::can_increase_instances() const { - if (arranging || rotoptimizing) { + if (m_ui_jobs.is_any_running()) { return false; } @@ -3221,7 +3721,7 @@ bool Plater::priv::can_increase_instances() const bool Plater::priv::can_decrease_instances() const { - if (arranging || rotoptimizing) { + if (m_ui_jobs.is_any_running()) { return false; } @@ -3241,7 +3741,7 @@ bool Plater::priv::can_split_to_volumes() const bool Plater::priv::can_arrange() const { - return !model.objects.empty() && !arranging; + return !model.objects.empty() && !m_ui_jobs.is_any_running(); } bool Plater::priv::can_layers_editing() const @@ -3254,27 +3754,248 @@ void Plater::priv::update_object_menu() sidebar->obj_list()->append_menu_items_add_volume(&object_menu); } -void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const +void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const { wxWindowUpdateLocker noUpdater(sidebar); const auto prin_host_opt = config->option("print_host"); const bool send_gcode_shown = prin_host_opt != nullptr && !prin_host_opt->value.empty(); - // when a background processing is ON, export_btn and/or send_btn are showing + // when a background processing is ON, export_btn and/or send_btn are showing if (wxGetApp().app_config->get("background_processing") == "1") { if (sidebar->show_reslice(false) | - sidebar->show_export(true) | - sidebar->show_send(send_gcode_shown)) - sidebar->Layout(); - } + sidebar->show_export(true) | + sidebar->show_send(send_gcode_shown)) + sidebar->Layout(); + } else { - if (sidebar->show_reslice(is_ready_to_slice) | - sidebar->show_export(!is_ready_to_slice) | - sidebar->show_send(send_gcode_shown && !is_ready_to_slice)) - sidebar->Layout(); - } + if (sidebar->show_reslice(is_ready_to_slice) | + sidebar->show_export(!is_ready_to_slice) | + sidebar->show_send(send_gcode_shown && !is_ready_to_slice)) + sidebar->Layout(); + } +} + +void Plater::priv::enter_gizmos_stack() +{ + assert(m_undo_redo_stack_active == &m_undo_redo_stack_main); + if (m_undo_redo_stack_active == &m_undo_redo_stack_main) { + m_undo_redo_stack_active = &m_undo_redo_stack_gizmos; + assert(m_undo_redo_stack_active->empty()); + // Take the initial snapshot of the gizmos. + // Not localized on purpose, the text will never be shown to the user. + this->take_snapshot(std::string("Gizmos-Initial")); + } +} + +void Plater::priv::leave_gizmos_stack() +{ + assert(m_undo_redo_stack_active == &m_undo_redo_stack_gizmos); + if (m_undo_redo_stack_active == &m_undo_redo_stack_gizmos) { + assert(! m_undo_redo_stack_active->empty()); + m_undo_redo_stack_active->clear(); + m_undo_redo_stack_active = &m_undo_redo_stack_main; + } +} + +int Plater::priv::get_active_snapshot_index() +{ + const size_t active_snapshot_time = this->undo_redo_stack().active_snapshot_time(); + const std::vector& ss_stack = this->undo_redo_stack().snapshots(); + const auto it = std::lower_bound(ss_stack.begin(), ss_stack.end(), UndoRedo::Snapshot(active_snapshot_time)); + return it - ss_stack.begin(); +} + +void Plater::priv::take_snapshot(const std::string& snapshot_name) +{ + if (this->m_prevent_snapshots > 0) + return; + assert(this->m_prevent_snapshots >= 0); + UndoRedo::SnapshotData snapshot_data; + snapshot_data.printer_technology = this->printer_technology; + if (this->view3D->is_layers_editing_enabled()) + snapshot_data.flags |= UndoRedo::SnapshotData::VARIABLE_LAYER_EDITING_ACTIVE; + if (this->sidebar->obj_list()->is_selected(itSettings)) { + snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_SETTINGS_ON_SIDEBAR; + snapshot_data.layer_range_idx = this->sidebar->obj_list()->get_selected_layers_range_idx(); + } + else if (this->sidebar->obj_list()->is_selected(itLayer)) { + snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_LAYER_ON_SIDEBAR; + snapshot_data.layer_range_idx = this->sidebar->obj_list()->get_selected_layers_range_idx(); + } + else if (this->sidebar->obj_list()->is_selected(itLayerRoot)) + snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_LAYERROOT_ON_SIDEBAR; + + // If SLA gizmo is active, ask it if it wants to trigger support generation + // on loading this snapshot. + if (view3D->get_canvas3d()->get_gizmos_manager().wants_reslice_supports_on_undo()) + snapshot_data.flags |= UndoRedo::SnapshotData::RECALCULATE_SLA_SUPPORTS; + + //FIXME updating the Wipe tower config values at the ModelWipeTower from the Print config. + // This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config. + if (this->printer_technology == ptFFF) { + const DynamicPrintConfig &config = wxGetApp().preset_bundle->prints.get_edited_preset().config; + model.wipe_tower.position = Vec2d(config.opt_float("wipe_tower_x"), config.opt_float("wipe_tower_y")); + model.wipe_tower.rotation = config.opt_float("wipe_tower_rotation_angle"); + } + this->undo_redo_stack().take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager(), snapshot_data); + this->undo_redo_stack().release_least_recently_used(); + // Save the last active preset name of a particular printer technology. + ((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name(); + BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot taken: " << snapshot_name << ", Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack().memsize()) << log_memory_info(); +} + +void Plater::priv::undo() +{ + const std::vector &snapshots = this->undo_redo_stack().snapshots(); + auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack().active_snapshot_time())); + if (-- it_current != snapshots.begin()) + this->undo_redo_to(it_current); +} + +void Plater::priv::redo() +{ + const std::vector &snapshots = this->undo_redo_stack().snapshots(); + auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack().active_snapshot_time())); + if (++ it_current != snapshots.end()) + this->undo_redo_to(it_current); +} + +void Plater::priv::undo_redo_to(size_t time_to_load) +{ + const std::vector &snapshots = this->undo_redo_stack().snapshots(); + auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(time_to_load)); + assert(it_current != snapshots.end()); + this->undo_redo_to(it_current); +} + +void Plater::priv::undo_redo_to(std::vector::const_iterator it_snapshot) +{ + // Make sure that no updating function calls take_snapshot until we are done. + SuppressSnapshots snapshot_supressor(q); + + bool temp_snapshot_was_taken = this->undo_redo_stack().temp_snapshot_active(); + PrinterTechnology new_printer_technology = it_snapshot->snapshot_data.printer_technology; + bool printer_technology_changed = this->printer_technology != new_printer_technology; + if (printer_technology_changed) { + // Switching the printer technology when jumping forwards / backwards in time. Switch to the last active printer profile of the other type. + std::string s_pt = (it_snapshot->snapshot_data.printer_technology == ptFFF) ? "FFF" : "SLA"; + if (! wxGetApp().check_unsaved_changes(from_u8((boost::format(_utf8( + L("%1% printer was active at the time the target Undo / Redo snapshot was taken. Switching to %1% printer requires reloading of %1% presets."))) % s_pt).str()))) + // Don't switch the profiles. + return; + } + // Save the last active preset name of a particular printer technology. + ((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name(); + //FIXME updating the Wipe tower config values at the ModelWipeTower from the Print config. + // This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config. + if (this->printer_technology == ptFFF) { + const DynamicPrintConfig &config = wxGetApp().preset_bundle->prints.get_edited_preset().config; + model.wipe_tower.position = Vec2d(config.opt_float("wipe_tower_x"), config.opt_float("wipe_tower_y")); + model.wipe_tower.rotation = config.opt_float("wipe_tower_rotation_angle"); + } + const int layer_range_idx = it_snapshot->snapshot_data.layer_range_idx; + // Flags made of Snapshot::Flags enum values. + unsigned int new_flags = it_snapshot->snapshot_data.flags; + UndoRedo::SnapshotData top_snapshot_data; + top_snapshot_data.printer_technology = this->printer_technology; + if (this->view3D->is_layers_editing_enabled()) + top_snapshot_data.flags |= UndoRedo::SnapshotData::VARIABLE_LAYER_EDITING_ACTIVE; + if (this->sidebar->obj_list()->is_selected(itSettings)) { + top_snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_SETTINGS_ON_SIDEBAR; + top_snapshot_data.layer_range_idx = this->sidebar->obj_list()->get_selected_layers_range_idx(); + } + else if (this->sidebar->obj_list()->is_selected(itLayer)) { + top_snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_LAYER_ON_SIDEBAR; + top_snapshot_data.layer_range_idx = this->sidebar->obj_list()->get_selected_layers_range_idx(); + } + else if (this->sidebar->obj_list()->is_selected(itLayerRoot)) + top_snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_LAYERROOT_ON_SIDEBAR; + bool new_variable_layer_editing_active = (new_flags & UndoRedo::SnapshotData::VARIABLE_LAYER_EDITING_ACTIVE) != 0; + bool new_selected_settings_on_sidebar = (new_flags & UndoRedo::SnapshotData::SELECTED_SETTINGS_ON_SIDEBAR) != 0; + bool new_selected_layer_on_sidebar = (new_flags & UndoRedo::SnapshotData::SELECTED_LAYER_ON_SIDEBAR) != 0; + bool new_selected_layerroot_on_sidebar = (new_flags & UndoRedo::SnapshotData::SELECTED_LAYERROOT_ON_SIDEBAR) != 0; + + if (this->view3D->get_canvas3d()->get_gizmos_manager().wants_reslice_supports_on_undo()) + top_snapshot_data.flags |= UndoRedo::SnapshotData::RECALCULATE_SLA_SUPPORTS; + + // Disable layer editing before the Undo / Redo jump. + if (!new_variable_layer_editing_active && view3D->is_layers_editing_enabled()) + view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting")); + // Make a copy of the snapshot, undo/redo could invalidate the iterator + const UndoRedo::Snapshot snapshot_copy = *it_snapshot; + // Do the jump in time. + if (it_snapshot->timestamp < this->undo_redo_stack().active_snapshot_time() ? + this->undo_redo_stack().undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager(), top_snapshot_data, it_snapshot->timestamp) : + this->undo_redo_stack().redo(model, this->view3D->get_canvas3d()->get_gizmos_manager(), it_snapshot->timestamp)) { + if (printer_technology_changed) { + // Switch to the other printer technology. Switch to the last printer active for that particular technology. + AppConfig *app_config = wxGetApp().app_config; + app_config->set("presets", "printer", (new_printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name); + wxGetApp().preset_bundle->load_presets(*app_config); + // Load the currently selected preset into the GUI, update the preset selection box. + // This also switches the printer technology based on the printer technology of the active printer profile. + wxGetApp().load_current_presets(); + } + //FIXME updating the Print config from the Wipe tower config values at the ModelWipeTower. + // This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config. + if (this->printer_technology == ptFFF) { + const DynamicPrintConfig ¤t_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; + Vec2d current_position(current_config.opt_float("wipe_tower_x"), current_config.opt_float("wipe_tower_y")); + double current_rotation = current_config.opt_float("wipe_tower_rotation_angle"); + if (current_position != model.wipe_tower.position || current_rotation != model.wipe_tower.rotation) { + DynamicPrintConfig new_config; + new_config.set_key_value("wipe_tower_x", new ConfigOptionFloat(model.wipe_tower.position.x())); + new_config.set_key_value("wipe_tower_y", new ConfigOptionFloat(model.wipe_tower.position.y())); + new_config.set_key_value("wipe_tower_rotation_angle", new ConfigOptionFloat(model.wipe_tower.rotation)); + Tab *tab_print = wxGetApp().get_tab(Preset::TYPE_PRINT); + tab_print->load_config(new_config); + tab_print->update_dirty(); + } + } + // set selection mode for ObjectList on sidebar + this->sidebar->obj_list()->set_selection_mode(new_selected_settings_on_sidebar ? ObjectList::SELECTION_MODE::smSettings : + new_selected_layer_on_sidebar ? ObjectList::SELECTION_MODE::smLayer : + new_selected_layerroot_on_sidebar ? ObjectList::SELECTION_MODE::smLayerRoot : + ObjectList::SELECTION_MODE::smUndef); + if (new_selected_settings_on_sidebar || new_selected_layer_on_sidebar) + this->sidebar->obj_list()->set_selected_layers_range_idx(layer_range_idx); + + this->update_after_undo_redo(snapshot_copy, temp_snapshot_was_taken); + // Enable layer editing after the Undo / Redo jump. + if (! view3D->is_layers_editing_enabled() && this->layers_height_allowed() && new_variable_layer_editing_active) + view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting")); + } +} + +void Plater::priv::update_after_undo_redo(const UndoRedo::Snapshot& snapshot, bool /* temp_snapshot_was_taken */) +{ + this->view3D->get_canvas3d()->get_selection().clear(); + // Update volumes from the deserializd model, always stop / update the background processing (for both the SLA and FFF technologies). + this->update(false, true); + // Release old snapshots if the memory allocated is excessive. This may remove the top most snapshot if jumping to the very first snapshot. + //if (temp_snapshot_was_taken) + // Release the old snapshots always, as it may have happened, that some of the triangle meshes got deserialized from the snapshot, while some + // triangle meshes may have gotten released from the scene or the background processing, therefore now being calculated into the Undo / Redo stack size. + this->undo_redo_stack().release_least_recently_used(); + //YS_FIXME update obj_list from the deserialized model (maybe store ObjectIDs into the tree?) (no selections at this point of time) + this->view3D->get_canvas3d()->get_selection().set_deserialized(GUI::Selection::EMode(this->undo_redo_stack().selection_deserialized().mode), this->undo_redo_stack().selection_deserialized().volumes_and_instances); + this->view3D->get_canvas3d()->get_gizmos_manager().update_after_undo_redo(snapshot); + + wxGetApp().obj_list()->update_after_undo_redo(); + + if (wxGetApp().get_mode() == comSimple && model_has_advanced_features(this->model)) { + // If the user jumped to a snapshot that require user interface with advanced features, switch to the advanced mode without asking. + // There is a little risk of surprising the user, as he already must have had the advanced or expert mode active for such a snapshot to be taken. + Slic3r::GUI::wxGetApp().save_mode(comAdvanced); + view3D->set_as_dirty(); + } + + //FIXME what about the state of the manipulators? + //FIXME what about the focus? Cursor in the side panel? + + BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot reloaded. Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack().memsize()) << log_memory_info(); } void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& label) const @@ -3308,22 +4029,32 @@ SLAPrint& Plater::sla_print() { return p->sla_print; } void Plater::new_project() { + p->select_view_3D("3D"); wxPostEvent(p->view3D->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); } void Plater::load_project() { + // Ask user for a project file name. wxString input_file; wxGetApp().load_project(this, input_file); + // And finally load the new project. + load_project(input_file); +} - if (input_file.empty()) +void Plater::load_project(const wxString& filename) +{ + if (filename.empty()) return; + // Take the Undo / Redo snapshot. + Plater::TakeSnapshot snapshot(this, _(L("Load Project")) + ": " + wxString::FromUTF8(into_path(filename).stem().string().c_str())); + p->reset(); - p->set_project_filename(input_file); + p->set_project_filename(filename); std::vector input_paths; - input_paths.push_back(into_path(input_file)); + input_paths.push_back(into_path(filename)); load_files(input_paths); } @@ -3334,11 +4065,28 @@ void Plater::add_model() if (input_files.empty()) return; - std::vector input_paths; - for (const auto &file : input_files) { - input_paths.push_back(into_path(file)); + std::vector paths; + for (const auto &file : input_files) + paths.push_back(into_path(file)); + + wxString snapshot_label; + assert(! paths.empty()); + if (paths.size() == 1) { + snapshot_label = _(L("Import Object")); + snapshot_label += ": "; + snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); + } else { + snapshot_label = _(L("Import Objects")); + snapshot_label += ": "; + snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); + for (size_t i = 1; i < paths.size(); ++ i) { + snapshot_label += ", "; + snapshot_label += wxString::FromUTF8(paths[i].filename().string().c_str()); + } } - load_files(input_paths, true, false); + + Plater::TakeSnapshot snapshot(this, snapshot_label); + load_files(paths, true, false); } void Plater::extract_config_from_project() @@ -3358,7 +4106,7 @@ void Plater::load_files(const std::vector& input_files, bool load_mode // To be called when providing a list of files to the GUI slic3r on command line. void Plater::load_files(const std::vector& input_files, bool load_model, bool load_config) -{ +{ std::vector paths; paths.reserve(input_files.size()); for (const std::string &path : input_files) @@ -3368,12 +4116,18 @@ void Plater::load_files(const std::vector& input_files, bool load_m void Plater::update() { p->update(); } +void Plater::stop_jobs() { p->m_ui_jobs.stop_all(); } + void Plater::update_ui_from_settings() { p->update_ui_from_settings(); } void Plater::select_view(const std::string& direction) { p->select_view(direction); } void Plater::select_view_3D(const std::string& name) { p->select_view_3D(name); } +bool Plater::is_preview_shown() const { return p->is_preview_shown(); } +bool Plater::is_preview_loaded() const { return p->is_preview_loaded(); } +bool Plater::is_view3D_shown() const { return p->is_view3D_shown(); } + void Plater::select_all() { p->select_all(); } void Plater::deselect_all() { p->deselect_all(); } @@ -3381,7 +4135,7 @@ void Plater::remove(size_t obj_idx) { p->remove(obj_idx); } void Plater::reset() { p->reset(); } void Plater::reset_with_confirm() { - if (wxMessageDialog((wxWindow*)this, _(L("All objects will be removed, continue ?")), wxString(SLIC3R_APP_NAME) + " - " + _(L("Delete all")), wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) + if (wxMessageDialog((wxWindow*)this, _(L("All objects will be removed, continue ?")), wxString(SLIC3R_APP_NAME) + " - " + _(L("Delete all")), wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) reset(); } @@ -3389,6 +4143,7 @@ void Plater::delete_object_from_model(size_t obj_idx) { p->delete_object_from_mo void Plater::remove_selected() { + Plater::TakeSnapshot snapshot(this, _(L("Delete Selected Objects"))); this->p->view3D->delete_selected(); } @@ -3396,13 +4151,15 @@ void Plater::increase_instances(size_t num) { if (! can_increase_instances()) { return; } + Plater::TakeSnapshot snapshot(this, _(L("Increase Instances"))); + int obj_idx = p->get_selected_object_idx(); ModelObject* model_object = p->model.objects[obj_idx]; ModelInstance* model_instance = model_object->instances.back(); bool was_one_instance = model_object->instances.size()==1; - + double offset_base = canvas3D()->get_size_proportional_to_max_bed_size(0.05); double offset = offset_base; for (size_t i = 0; i < num; i++, offset += offset_base) { @@ -3430,6 +4187,8 @@ void Plater::decrease_instances(size_t num) { if (! can_decrease_instances()) { return; } + Plater::TakeSnapshot snapshot(this, _(L("Decrease Instances"))); + int obj_idx = p->get_selected_object_idx(); ModelObject* model_object = p->model.objects[obj_idx]; @@ -3459,11 +4218,13 @@ void Plater::set_number_of_copies(/*size_t num*/) ModelObject* model_object = p->model.objects[obj_idx]; - const auto num = wxGetNumberFromUser( " ", _("Enter the number of copies:"), + const auto num = wxGetNumberFromUser( " ", _("Enter the number of copies:"), _("Copies of the selected object"), model_object->instances.size(), 0, 1000, this ); if (num < 0) return; + Plater::TakeSnapshot snapshot(this, wxString::Format(_(L("Set numbers of copies to %d")), num)); + int diff = (int)num - (int)model_object->instances.size(); if (diff > 0) increase_instances(diff); @@ -3492,6 +4253,8 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_uppe return; } + Plater::TakeSnapshot snapshot(this, _(L("Cut by Plane"))); + wxBusyCursor wait; const auto new_objects = object->cut(instance_idx, z, keep_upper, keep_lower, rotate_lower); @@ -3508,11 +4271,11 @@ void Plater::export_gcode() // This function is useful for generating file names to be processed by legacy firmwares. fs::path default_output_file; try { - // Update the background processing, so that the placeholder parser will get the correct values for the ouput file template. - // Also if there is something wrong with the current configuration, a pop-up dialog will be shown and the export will not be performed. - unsigned int state = this->p->update_restart_background_process(false, false); - if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) - return; + // Update the background processing, so that the placeholder parser will get the correct values for the ouput file template. + // Also if there is something wrong with the current configuration, a pop-up dialog will be shown and the export will not be performed. + unsigned int state = this->p->update_restart_background_process(false, false); + if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) + return; default_output_file = this->p->background_process.output_filepath_for_project(into_path(get_project_filename(".3mf"))); } catch (const std::exception &ex) { @@ -3568,7 +4331,7 @@ void Plater::export_stl(bool extended, bool selection_only) else { const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - mesh = model_object->volumes[volume->volume_idx()]->mesh; + mesh = model_object->volumes[volume->volume_idx()]->mesh(); mesh.transform(volume->get_volume_transformation().get_matrix()); mesh.translate(-model_object->origin_translation.cast()); } @@ -3674,7 +4437,7 @@ void Plater::export_3mf(const boost::filesystem::path& output_path) if (!path.Lower().EndsWith(".3mf")) return; - DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); + DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); const std::string path_u8 = into_u8(path); wxBusyCursor wait; if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr)) { @@ -3688,8 +4451,35 @@ void Plater::export_3mf(const boost::filesystem::path& output_path) } } +bool Plater::has_toolpaths_to_export() const +{ + return p->preview->get_canvas3d()->has_toolpaths_to_export(); +} + +void Plater::export_toolpaths_to_obj() const +{ + if ((printer_technology() != ptFFF) || !is_preview_loaded()) + return; + + wxString path = p->get_export_file(FT_OBJ); + if (path.empty()) + return; + + wxBusyCursor wait; + p->preview->get_canvas3d()->export_toolpaths_to_obj(into_u8(path).c_str()); +} + void Plater::reslice() { + // Stop arrange and (or) optimize rotation tasks. + this->stop_jobs(); + + if (printer_technology() == ptSLA) { + for (auto& object : model().objects) + if (object->sla_points_status == sla::PointsStatus::NoPoints) + object->sla_points_status = sla::PointsStatus::Generating; + } + //FIXME Don't reslice if export of G-code or sending to OctoPrint is running. // bitmask of UpdateBackgroundProcessReturnState unsigned int state = this->p->update_background_process(true); @@ -3715,6 +4505,9 @@ void Plater::reslice() } else if (!p->background_process.empty() && !p->background_process.idle()) p->show_action_buttons(true); + + // update type of preview + p->preview->update_view_type(); } void Plater::reslice_SLA_supports(const ModelObject &object) @@ -3725,7 +4518,7 @@ void Plater::reslice_SLA_supports(const ModelObject &object) if (state & priv::UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) this->p->view3D->reload_scene(false); - if (this->p->background_process.empty() || (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID)) + if (this->p->background_process.empty() || (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID)) // Nothing to do on empty input or invalid configuration. return; @@ -3734,10 +4527,10 @@ void Plater::reslice_SLA_supports(const ModelObject &object) task.single_model_object = object.id(); // If the background processing is not enabled, calculate supports just for the single instance. // Otherwise calculate everything, but start with the provided object. - if (!this->p->background_processing_enabled()) { - task.single_model_instance_only = true; - task.to_object_step = slaposBasePool; - } + if (!this->p->background_processing_enabled()) { + task.single_model_instance_only = true; + task.to_object_step = slaposBasePool; + } this->p->background_process.set_task(task); // and let the background processing start. this->p->restart_background_process(state | priv::UPDATE_BACKGROUND_PROCESS_FORCE_RESTART); @@ -3753,11 +4546,11 @@ void Plater::send_gcode() // Obtain default output path fs::path default_output_file; try { - // Update the background processing, so that the placeholder parser will get the correct values for the ouput file template. - // Also if there is something wrong with the current configuration, a pop-up dialog will be shown and the export will not be performed. - unsigned int state = this->p->update_restart_background_process(false, false); - if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) - return; + // Update the background processing, so that the placeholder parser will get the correct values for the ouput file template. + // Also if there is something wrong with the current configuration, a pop-up dialog will be shown and the export will not be performed. + unsigned int state = this->p->update_restart_background_process(false, false); + if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) + return; default_output_file = this->p->background_process.output_filepath_for_project(into_path(get_project_filename(".3mf"))); } catch (const std::exception &ex) { @@ -3775,6 +4568,58 @@ void Plater::send_gcode() } } +void Plater::take_snapshot(const std::string &snapshot_name) { p->take_snapshot(snapshot_name); } +void Plater::take_snapshot(const wxString &snapshot_name) { p->take_snapshot(snapshot_name); } +void Plater::suppress_snapshots() { p->suppress_snapshots(); } +void Plater::allow_snapshots() { p->allow_snapshots(); } +void Plater::undo() { p->undo(); } +void Plater::redo() { p->redo(); } +void Plater::undo_to(int selection) +{ + if (selection == 0) { + p->undo(); + return; + } + + const int idx = p->get_active_snapshot_index() - selection - 1; + p->undo_redo_to(p->undo_redo_stack().snapshots()[idx].timestamp); +} +void Plater::redo_to(int selection) +{ + if (selection == 0) { + p->redo(); + return; + } + + const int idx = p->get_active_snapshot_index() + selection + 1; + p->undo_redo_to(p->undo_redo_stack().snapshots()[idx].timestamp); +} +bool Plater::undo_redo_string_getter(const bool is_undo, int idx, const char** out_text) +{ + const std::vector& ss_stack = p->undo_redo_stack().snapshots(); + const int idx_in_ss_stack = p->get_active_snapshot_index() + (is_undo ? -(++idx) : idx); + + if (0 < idx_in_ss_stack && idx_in_ss_stack < ss_stack.size() - 1) { + *out_text = ss_stack[idx_in_ss_stack].name.c_str(); + return true; + } + + return false; +} + +void Plater::undo_redo_topmost_string_getter(const bool is_undo, std::string& out_text) +{ + const std::vector& ss_stack = p->undo_redo_stack().snapshots(); + const int idx_in_ss_stack = p->get_active_snapshot_index() + (is_undo ? -1 : 0); + + if (0 < idx_in_ss_stack && idx_in_ss_stack < ss_stack.size() - 1) { + out_text = ss_stack[idx_in_ss_stack].name; + return; + } + + out_text = ""; +} + void Plater::on_extruders_change(int num_extruders) { auto& choices = sidebar().combos_filament(); @@ -3808,18 +4653,42 @@ void Plater::on_config_change(const DynamicPrintConfig &config) bool update_scheduled = false; bool bed_shape_changed = false; for (auto opt_key : p->config->diff(config)) { + if (opt_key == "filament_colour") + { + update_scheduled = true; // update should be scheduled (for update 3DScene) #2738 + + /* There is a case, when we use filament_color instead of extruder_color (when extruder_color == ""). + * Thus plater config option "filament_colour" should be filled with filament_presets values. + * Otherwise, on 3dScene will be used last edited filament color for all volumes with extruder_color == "". + */ + const std::vector filament_presets = wxGetApp().preset_bundle->filament_presets; + if (filament_presets.size() > 1 && + p->config->option(opt_key)->values.size() != config.option(opt_key)->values.size()) + { + const PresetCollection& filaments = wxGetApp().preset_bundle->filaments; + std::vector filament_colors; + filament_colors.reserve(filament_presets.size()); + + for (const std::string& filament_preset : filament_presets) + filament_colors.push_back(filaments.find_preset(filament_preset, true)->config.opt_string("filament_colour", (unsigned)0)); + + p->config->option(opt_key)->values = filament_colors; + continue; + } + } + p->config->set_key_value(opt_key, config.option(opt_key)->clone()); if (opt_key == "printer_technology") this->set_printer_technology(config.opt_enum(opt_key)); - else if (opt_key == "bed_shape") { + else if ((opt_key == "bed_shape") || (opt_key == "bed_custom_texture") || (opt_key == "bed_custom_model")) { bed_shape_changed = true; update_scheduled = true; - } + } else if (boost::starts_with(opt_key, "wipe_tower") || // opt_key == "filament_minimal_purge_on_wipe_tower" // ? #ys_FIXME opt_key == "single_extruder_multi_material") { update_scheduled = true; - } + } else if(opt_key == "variable_layer_height") { if (p->config->opt_bool("variable_layer_height") != true) { p->view3D->enable_layers_editing(false); @@ -3845,9 +4714,11 @@ void Plater::on_config_change(const DynamicPrintConfig &config) } if (bed_shape_changed) - p->set_bed_shape(p->config->option("bed_shape")->values); + p->set_bed_shape(p->config->option("bed_shape")->values, + p->config->option("bed_custom_texture")->value, + p->config->option("bed_custom_model")->value); - if (update_scheduled) + if (update_scheduled) update(); if (p->main_frame->is_loaded()) @@ -3868,10 +4739,10 @@ void Plater::on_activate() #endif if (! this->p->delayed_error_message.empty()) { - std::string msg = std::move(this->p->delayed_error_message); - this->p->delayed_error_message.clear(); + std::string msg = std::move(this->p->delayed_error_message); + this->p->delayed_error_message.clear(); GUI::show_error(this, msg); - } + } } wxString Plater::get_project_filename(const wxString& extension) const @@ -3915,7 +4786,7 @@ void Plater::set_printer_technology(PrinterTechnology printer_technology) if (p->background_process.select_technology(printer_technology)) { // Update the active presets. } - //FIXME for SLA synchronize + //FIXME for SLA synchronize //p->background_process.apply(Model)! p->label_btn_export = printer_technology == ptFFF ? L("Export G-code") : L("Export"); @@ -3967,9 +4838,25 @@ void Plater::changed_objects(const std::vector& object_idxs) this->p->schedule_background_process(); } -void Plater::schedule_background_process() +void Plater::schedule_background_process(bool schedule/* = true*/) { - this->p->schedule_background_process(); + if (schedule) + this->p->schedule_background_process(); + + this->p->suppressed_backround_processing_update = false; +} + +bool Plater::is_background_process_running() const +{ + return this->p->background_process_timer.IsRunning(); +} + +void Plater::suppress_background_process(const bool stop_background_process) +{ + if (stop_background_process) + this->p->background_process_timer.Stop(); + + this->p->suppressed_backround_processing_update = true; } void Plater::fix_through_netfabb(const int obj_idx, const int vol_idx/* = -1*/) { p->fix_through_netfabb(obj_idx, vol_idx); } @@ -3978,32 +4865,19 @@ void Plater::update_object_menu() { p->update_object_menu(); } void Plater::copy_selection_to_clipboard() { - p->view3D->get_canvas3d()->get_selection().copy_to_clipboard(); + if (can_copy_to_clipboard()) + p->view3D->get_canvas3d()->get_selection().copy_to_clipboard(); } void Plater::paste_from_clipboard() { + if (!can_paste_from_clipboard()) + return; + + Plater::TakeSnapshot snapshot(this, _(L("Paste From Clipboard"))); p->view3D->get_canvas3d()->get_selection().paste_from_clipboard(); } -bool Plater::can_paste_from_clipboard() const -{ - const Selection& selection = p->view3D->get_canvas3d()->get_selection(); - const Selection::Clipboard& clipboard = selection.get_clipboard(); - Selection::EMode mode = clipboard.get_mode(); - - if (clipboard.is_empty()) - return false; - - if ((mode == Selection::Volume) && !selection.is_from_single_instance()) - return false; - - if ((mode == Selection::Instance) && (selection.get_mode() != Selection::Instance)) - return false; - - return true; -} - void Plater::msw_rescale() { p->preview->msw_rescale(); @@ -4018,6 +4892,11 @@ void Plater::msw_rescale() GetParent()->Layout(); } +const Camera& Plater::get_camera() const +{ + return p->camera; +} + bool Plater::can_delete() const { return p->can_delete(); } bool Plater::can_delete_all() const { return p->can_delete_all(); } bool Plater::can_increase_instances() const { return p->can_increase_instances(); } @@ -4028,7 +4907,54 @@ bool Plater::can_split_to_objects() const { return p->can_split_to_objects(); } bool Plater::can_split_to_volumes() const { return p->can_split_to_volumes(); } bool Plater::can_arrange() const { return p->can_arrange(); } bool Plater::can_layers_editing() const { return p->can_layers_editing(); } -bool Plater::can_copy() const { return !is_selection_empty(); } -bool Plater::can_paste() const { return can_paste_from_clipboard(); } +bool Plater::can_paste_from_clipboard() const +{ + const Selection& selection = p->view3D->get_canvas3d()->get_selection(); + const Selection::Clipboard& clipboard = selection.get_clipboard(); + + if (clipboard.is_empty()) + return false; + + if ((wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) && !clipboard.is_sla_compliant()) + return false; + + Selection::EMode mode = clipboard.get_mode(); + if ((mode == Selection::Volume) && !selection.is_from_single_instance()) + return false; + + if ((mode == Selection::Instance) && (selection.get_mode() != Selection::Instance)) + return false; + + return true; +} + +bool Plater::can_copy_to_clipboard() const +{ + if (is_selection_empty()) + return false; + + const Selection& selection = p->view3D->get_canvas3d()->get_selection(); + if ((wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) && !selection.is_sla_compliant()) + return false; + + return true; +} + +bool Plater::can_undo() const { return p->undo_redo_stack().has_undo_snapshot(); } +bool Plater::can_redo() const { return p->undo_redo_stack().has_redo_snapshot(); } +const UndoRedo::Stack& Plater::undo_redo_stack_main() const { return p->undo_redo_stack_main(); } +void Plater::enter_gizmos_stack() { p->enter_gizmos_stack(); } +void Plater::leave_gizmos_stack() { p->leave_gizmos_stack(); } + +SuppressBackgroundProcessingUpdate::SuppressBackgroundProcessingUpdate() : + m_was_running(wxGetApp().plater()->is_background_process_running()) +{ + wxGetApp().plater()->suppress_background_process(m_was_running); +} + +SuppressBackgroundProcessingUpdate::~SuppressBackgroundProcessingUpdate() +{ + wxGetApp().plater()->schedule_background_process(m_was_running); +} }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 16c9cbe649..0bd01835e7 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -27,12 +27,18 @@ class ModelObject; class Print; class SLAPrint; +namespace UndoRedo { + class Stack; + struct Snapshot; +}; + namespace GUI { class MainFrame; class ConfigOptionsGroup; class ObjectManipulation; class ObjectSettings; +class ObjectLayers; class ObjectList; class GLCanvas3D; @@ -93,6 +99,7 @@ public: ObjectManipulation* obj_manipul(); ObjectList* obj_list(); ObjectSettings* obj_settings(); + ObjectLayers* obj_layers(); wxScrolledWindow* scrolled_panel(); wxPanel* presets_panel(); @@ -136,6 +143,7 @@ public: void new_project(); void load_project(); + void load_project(const wxString& filename); void add_model(); void extract_config_from_project(); @@ -144,9 +152,14 @@ public: void load_files(const std::vector& input_files, bool load_model = true, bool load_config = true); void update(); + void stop_jobs(); void select_view(const std::string& direction); void select_view_3D(const std::string& name); + bool is_preview_shown() const; + bool is_preview_loaded() const; + bool is_view3D_shown() const; + // Called after the Preferences dialog is closed and the program settings are saved. // Update the UI based on the current preferences. void update_ui_from_settings(); @@ -170,14 +183,32 @@ public: void export_stl(bool extended = false, bool selection_only = false); void export_amf(); void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); + bool has_toolpaths_to_export() const; + void export_toolpaths_to_obj() const; void reslice(); void reslice_SLA_supports(const ModelObject &object); void changed_object(int obj_idx); void changed_objects(const std::vector& object_idxs); - void schedule_background_process(); + void schedule_background_process(bool schedule = true); + bool is_background_process_running() const; + void suppress_background_process(const bool stop_background_process) ; void fix_through_netfabb(const int obj_idx, const int vol_idx = -1); void send_gcode(); + void take_snapshot(const std::string &snapshot_name); + void take_snapshot(const wxString &snapshot_name); + void undo(); + void redo(); + void undo_to(int selection); + void redo_to(int selection); + bool undo_redo_string_getter(const bool is_undo, int idx, const char** out_text); + void undo_redo_topmost_string_getter(const bool is_undo, std::string& out_text); + // For the memory statistics. + const Slic3r::UndoRedo::Stack& undo_redo_stack_main() const; + // Enter / leave the Gizmos specific Undo / Redo stack. To be used by the SLA support point editing gizmo. + void enter_gizmos_stack(); + void leave_gizmos_stack(); + void on_extruders_change(int extruders_count); void on_config_change(const DynamicPrintConfig &config); // On activating the parent window. @@ -199,7 +230,6 @@ public: void copy_selection_to_clipboard(); void paste_from_clipboard(); - bool can_paste_from_clipboard() const; bool can_delete() const; bool can_delete_all() const; @@ -211,16 +241,66 @@ public: bool can_split_to_volumes() const; bool can_arrange() const; bool can_layers_editing() const; - bool can_copy() const; - bool can_paste() const; + bool can_paste_from_clipboard() const; + bool can_copy_to_clipboard() const; + bool can_undo() const; + bool can_redo() const; void msw_rescale(); + const Camera& get_camera() const; + + // ROII wrapper for suppressing the Undo / Redo snapshot to be taken. + class SuppressSnapshots + { + public: + SuppressSnapshots(Plater *plater) : m_plater(plater) + { + m_plater->suppress_snapshots(); + } + ~SuppressSnapshots() + { + m_plater->allow_snapshots(); + } + private: + Plater *m_plater; + }; + + // ROII wrapper for taking an Undo / Redo snapshot while disabling the snapshot taking by the methods called from inside this snapshot. + class TakeSnapshot + { + public: + TakeSnapshot(Plater *plater, const wxString &snapshot_name) : m_plater(plater) + { + m_plater->take_snapshot(snapshot_name); + m_plater->suppress_snapshots(); + } + ~TakeSnapshot() + { + m_plater->allow_snapshots(); + } + private: + Plater *m_plater; + }; + private: struct priv; std::unique_ptr p; + + void suppress_snapshots(); + void allow_snapshots(); + + friend class SuppressBackgroundProcessingUpdate; }; +class SuppressBackgroundProcessingUpdate +{ +public: + SuppressBackgroundProcessingUpdate(); + ~SuppressBackgroundProcessingUpdate(); +private: + bool m_was_running; +}; }} diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 52cbdbb1d9..827d1f4e05 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -28,7 +28,7 @@ void PreferencesDialog::build() m_icon_size_sizer->ShowItems(boost::any_cast(value)); this->layout(); } - }; + }; // TODO // $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( @@ -97,16 +97,6 @@ void PreferencesDialog::build() option = Option (def,"show_incompatible_presets"); m_optgroup->append_single_option_line(option); - // TODO: remove? - def.label = L("Use legacy OpenGL 1.1 rendering"); - def.type = coBool; - def.tooltip = L("If you have rendering issues caused by a buggy OpenGL 2.0 driver, " - "you may try to check this checkbox. This will disable the layer height " - "editing and anti aliasing, so it is likely better to upgrade your graphics driver."); - def.set_default_value(new ConfigOptionBool{ app_config->get("use_legacy_opengl") == "1" }); - option = Option (def,"use_legacy_opengl"); - m_optgroup->append_single_option_line(option); - #if __APPLE__ def.label = L("Use Retina resolution for the 3D scene"); def.type = coBool; @@ -117,6 +107,13 @@ void PreferencesDialog::build() m_optgroup->append_single_option_line(option); #endif + def.label = L("Use perspective camera"); + def.type = coBool; + def.tooltip = L("If enabled, use perspective camera. If not enabled, use orthographic camera."); + def.set_default_value(new ConfigOptionBool{ app_config->get("use_perspective_camera") == "1" }); + option = Option(def, "use_perspective_camera"); + m_optgroup->append_single_option_line(option); + def.label = L("Use custom size for toolbar icons"); def.type = coBool; def.tooltip = L("If enabled, you can change size of toolbar icons manually."); @@ -143,8 +140,7 @@ void PreferencesDialog::build() void PreferencesDialog::accept() { - if (m_values.find("no_defaults") != m_values.end() || - m_values.find("use_legacy_opengl") != m_values.end()) { + if (m_values.find("no_defaults") != m_values.end()) { warning_catcher(this, wxString::Format(_(L("You need to restart %s to make the changes effective.")), SLIC3R_APP_NAME)); } diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 22ddf3449e..64793630ca 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -162,11 +162,11 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem if (from_pre_map != pre_family_model_map.end()) { model.family = from_pre_map->second; } } #if 0 - // Remove SLA printers from the initial alpha. - if (model.technology == ptSLA) - continue; + // Remove SLA printers from the initial alpha. + if (model.technology == ptSLA) + continue; #endif - section.second.get("variants", ""); + section.second.get("variants", ""); const auto variants_field = section.second.get("variants", ""); std::vector variants; if (Slic3r::unescape_strings_cstyle(variants_field, variants)) { @@ -209,7 +209,7 @@ const std::string& Preset::suffix_modified() void Preset::update_suffix_modified() { - g_suffix_modified = (" (" + _(L("modified")) + ")").ToUTF8().data(); + g_suffix_modified = (" (" + _(L("modified")) + ")").ToUTF8().data(); } // Remove an optional "(modified)" suffix from a name. // This converts a UI name to a unique preset identifier. @@ -224,8 +224,8 @@ void Preset::set_num_extruders(DynamicPrintConfig &config, unsigned int num_extr { const auto &defaults = FullPrintConfig::defaults(); for (const std::string &key : Preset::nozzle_options()) { - if (key == "default_filament_profile") - continue; + if (key == "default_filament_profile") + continue; auto *opt = config.option(key, false); assert(opt != nullptr); assert(opt->is_vector()); @@ -247,8 +247,8 @@ void Preset::normalize(DynamicPrintConfig &config) size_t n = (nozzle_diameter == nullptr) ? 1 : nozzle_diameter->values.size(); const auto &defaults = FullPrintConfig::defaults(); for (const std::string &key : Preset::filament_options()) { - if (key == "compatible_prints" || key == "compatible_printers") - continue; + if (key == "compatible_prints" || key == "compatible_printers") + continue; auto *opt = config.option(key, false); /*assert(opt != nullptr); assert(opt->is_vector());*/ @@ -307,7 +307,7 @@ bool Preset::is_compatible_with_print(const Preset &active_print) const } } return this->is_default || active_print.name.empty() || ! has_compatible_prints || - std::find(compatible_prints->values.begin(), compatible_prints->values.end(), active_print.name) != + std::find(compatible_prints->values.begin(), compatible_prints->values.end(), active_print.name) != compatible_prints->values.end(); } @@ -326,7 +326,7 @@ bool Preset::is_compatible_with_printer(const Preset &active_printer, const Dyna } } return this->is_default || active_printer.name.empty() || ! has_compatible_printers || - std::find(compatible_printers->values.begin(), compatible_printers->values.end(), active_printer.name) != + std::find(compatible_printers->values.begin(), compatible_printers->values.end(), active_printer.name) != compatible_printers->values.end(); } @@ -334,9 +334,9 @@ bool Preset::is_compatible_with_printer(const Preset &active_printer) const { DynamicPrintConfig config; config.set_key_value("printer_preset", new ConfigOptionString(active_printer.name)); - const ConfigOption *opt = active_printer.config.option("nozzle_diameter"); - if (opt) - config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast(opt)->values.size())); + const ConfigOption *opt = active_printer.config.option("nozzle_diameter"); + if (opt) + config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast(opt)->values.size())); return this->is_compatible_with_printer(active_printer, &config); } @@ -358,40 +358,40 @@ void Preset::set_visible_from_appconfig(const AppConfig &app_config) } const std::vector& Preset::print_options() -{ +{ static std::vector s_opts { - "layer_height", "first_layer_height", "perimeters", "spiral_vase", "slice_closing_radius", "top_solid_layers", "bottom_solid_layers", - "extra_perimeters", "ensure_vertical_shell_thickness", "avoid_crossing_perimeters", "thin_walls", "overhangs", + "layer_height", "first_layer_height", "perimeters", "spiral_vase", "slice_closing_radius", "top_solid_layers", "bottom_solid_layers", + "extra_perimeters", "ensure_vertical_shell_thickness", "avoid_crossing_perimeters", "thin_walls", "overhangs", "seam_position", "external_perimeters_first", "fill_density", "fill_pattern", "top_fill_pattern", "bottom_fill_pattern", - "infill_every_layers", "infill_only_where_needed", "solid_infill_every_layers", "fill_angle", "bridge_angle", - "solid_infill_below_area", "only_retract_when_crossing_perimeters", "infill_first", "max_print_speed", - "max_volumetric_speed", + "infill_every_layers", "infill_only_where_needed", "solid_infill_every_layers", "fill_angle", "bridge_angle", + "solid_infill_below_area", "only_retract_when_crossing_perimeters", "infill_first", "max_print_speed", + "max_volumetric_speed", #ifdef HAS_PRESSURE_EQUALIZER - "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative", + "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative", #endif /* HAS_PRESSURE_EQUALIZER */ - "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "infill_speed", "solid_infill_speed", + "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "infill_speed", "solid_infill_speed", "top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed", - "bridge_speed", "gap_fill_speed", "travel_speed", "first_layer_speed", "perimeter_acceleration", "infill_acceleration", + "bridge_speed", "gap_fill_speed", "travel_speed", "first_layer_speed", "perimeter_acceleration", "infill_acceleration", "bridge_acceleration", "first_layer_acceleration", "default_acceleration", "skirts", "skirt_distance", "skirt_height", - "min_skirt_length", "brim_width", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers", - "raft_layers", "support_material_pattern", "support_material_with_sheath", "support_material_spacing", - "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", - "support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance", - "support_material_buildplate_only", "dont_support_bridges", "notes", "complete_objects", "extruder_clearance_radius", - "extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "perimeter_extruder", - "infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder", - "ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width", - "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", - "top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "bridge_flow_ratio", "clip_multipart_objects", + "min_skirt_length", "brim_width", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers", + "raft_layers", "support_material_pattern", "support_material_with_sheath", "support_material_spacing", + "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", + "support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance", + "support_material_buildplate_only", "dont_support_bridges", "notes", "complete_objects", "extruder_clearance_radius", + "extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "perimeter_extruder", + "infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder", + "ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width", + "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", + "top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "bridge_flow_ratio", "clip_multipart_objects", "elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y", - "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging", "single_extruder_multi_material_priming", + "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging", "single_extruder_multi_material_priming", "compatible_printers", "compatible_printers_condition", "inherits" }; return s_opts; } const std::vector& Preset::filament_options() -{ +{ static std::vector s_opts { "filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed", "extrusion_multiplier", "filament_density", "filament_cost", "filament_loading_speed", "filament_loading_speed_start", "filament_load_time", @@ -400,36 +400,40 @@ const std::vector& Preset::filament_options() "temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", "start_filament_gcode", "end_filament_gcode", + // Retract overrides + "filament_retract_length", "filament_retract_lift", "filament_retract_lift_above", "filament_retract_lift_below", "filament_retract_speed", "filament_deretract_speed", "filament_retract_restart_extra", "filament_retract_before_travel", + "filament_retract_layer_change", "filament_wipe", "filament_retract_before_wipe", + // Profile compatibility "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits" }; return s_opts; } const std::vector& Preset::printer_options() -{ +{ static std::vector s_opts; if (s_opts.empty()) { s_opts = { "printer_technology", - "bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", + "bed_shape", "bed_custom_texture", "bed_custom_model", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height", "host_type", "print_host", "printhost_apikey", "printhost_cafile", "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", "between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "cooling_tube_retraction", - "cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "max_print_height", + "cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "max_print_height", "default_print_profile", "inherits", "remaining_times", "silent_mode", "machine_max_acceleration_extruding", "machine_max_acceleration_retracting", - "machine_max_acceleration_x", "machine_max_acceleration_y", "machine_max_acceleration_z", "machine_max_acceleration_e", - "machine_max_feedrate_x", "machine_max_feedrate_y", "machine_max_feedrate_z", "machine_max_feedrate_e", - "machine_min_extruding_rate", "machine_min_travel_rate", - "machine_max_jerk_x", "machine_max_jerk_y", "machine_max_jerk_z", "machine_max_jerk_e" + "machine_max_acceleration_x", "machine_max_acceleration_y", "machine_max_acceleration_z", "machine_max_acceleration_e", + "machine_max_feedrate_x", "machine_max_feedrate_y", "machine_max_feedrate_z", "machine_max_feedrate_e", + "machine_min_extruding_rate", "machine_min_travel_rate", + "machine_max_jerk_x", "machine_max_jerk_y", "machine_max_jerk_z", "machine_max_jerk_e" }; s_opts.insert(s_opts.end(), Preset::nozzle_options().begin(), Preset::nozzle_options().end()); } return s_opts; } -// The following nozzle options of a printer profile will be adjusted to match the size +// The following nozzle options of a printer profile will be adjusted to match the size // of the nozzle_diameter vector. const std::vector& Preset::nozzle_options() { @@ -438,14 +442,14 @@ const std::vector& Preset::nozzle_options() "nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_offset", "retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed", "retract_before_wipe", "retract_restart_extra", "retract_before_travel", "wipe", - "retract_layer_change", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour", + "retract_layer_change", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour", "default_filament_profile" }; return s_opts; } const std::vector& Preset::sla_print_options() -{ +{ static std::vector s_opts; if (s_opts.empty()) { s_opts = { @@ -461,6 +465,7 @@ const std::vector& Preset::sla_print_options() "support_pillar_widening_factor", "support_base_diameter", "support_base_height", + "support_base_safety_distance", "support_critical_angle", "support_max_bridge_length", "support_max_pillar_link_distance", @@ -472,12 +477,17 @@ const std::vector& Preset::sla_print_options() "pad_wall_thickness", "pad_wall_height", "pad_max_merge_distance", - "pad_edge_radius", + // "pad_edge_radius", "pad_wall_slope", - "output_filename_format", + "pad_object_gap", + "pad_zero_elevation", + "pad_object_connector_stride", + "pad_object_connector_width", + "pad_object_connector_penetration", + "output_filename_format", "default_sla_print_profile", "compatible_printers", - "compatible_printers_condition", + "compatible_printers_condition", "inherits" }; } @@ -485,16 +495,17 @@ const std::vector& Preset::sla_print_options() } const std::vector& Preset::sla_material_options() -{ +{ static std::vector s_opts; if (s_opts.empty()) { s_opts = { "initial_layer_height", - "exposure_time", "initial_exposure_time", + "exposure_time", + "initial_exposure_time", "material_correction", "material_notes", "default_sla_material_profile", - "compatible_prints", "compatible_prints_condition", + "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits" }; } @@ -502,18 +513,22 @@ const std::vector& Preset::sla_material_options() } const std::vector& Preset::sla_printer_options() -{ +{ static std::vector s_opts; if (s_opts.empty()) { s_opts = { "printer_technology", + "bed_shape", "bed_custom_texture", "bed_custom_model", "max_print_height", "bed_shape", "max_print_height", "display_width", "display_height", "display_pixels_x", "display_pixels_y", + "display_mirror_x", "display_mirror_y", "display_orientation", "fast_tilt_time", "slow_tilt_time", "area_fill", "relative_correction", "absolute_correction", "gamma_correction", + "min_exposure_time", "max_exposure_time", + "min_initial_exposure_time", "max_initial_exposure_time", "print_host", "printhost_apikey", "printhost_cafile", "printer_notes", "inherits" @@ -528,7 +543,7 @@ PresetCollection::PresetCollection(Preset::Type type, const std::vectoradd_default_preset(keys, defaults, default_name); @@ -541,8 +556,8 @@ PresetCollection::~PresetCollection() m_bitmap_main_frame = nullptr; delete m_bitmap_add; m_bitmap_add = nullptr; - delete m_bitmap_cache; - m_bitmap_cache = nullptr; + delete m_bitmap_cache; + m_bitmap_cache = nullptr; } void PresetCollection::reset(bool delete_files) @@ -564,8 +579,8 @@ void PresetCollection::add_default_preset(const std::vector &keys, { // Insert just the default preset. m_presets.emplace_back(Preset(this->type(), preset_name, true)); - m_presets.back().config.apply_only(defaults, keys.empty() ? defaults.keys() : keys); - m_presets.back().loaded = true; + m_presets.back().config.apply_only(defaults, keys.empty() ? defaults.keys() : keys); + m_presets.back().loaded = true; ++ m_num_default_presets; } @@ -573,13 +588,13 @@ void PresetCollection::add_default_preset(const std::vector &keys, // Throws an exception on error. void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir) { - boost::filesystem::path dir = boost::filesystem::canonical(boost::filesystem::path(dir_path) / subdir).make_preferred(); - m_dir_path = dir.string(); + boost::filesystem::path dir = boost::filesystem::canonical(boost::filesystem::path(dir_path) / subdir).make_preferred(); + m_dir_path = dir.string(); std::string errors_cummulative; - // Store the loaded presets into a new vector, otherwise the binary search for already existing presets would be broken. - // (see the "Preset already present, not loading" message). - std::deque presets_loaded; - for (auto &dir_entry : boost::filesystem::directory_iterator(dir)) + // Store the loaded presets into a new vector, otherwise the binary search for already existing presets would be broken. + // (see the "Preset already present, not loading" message). + std::deque presets_loaded; + for (auto &dir_entry : boost::filesystem::directory_iterator(dir)) if (Slic3r::is_ini_file(dir_entry)) { std::string name = dir_entry.path().filename().string(); // Remove the .ini suffix. @@ -598,28 +613,28 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri DynamicPrintConfig config; config.load_from_ini(preset.file); // Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field. - const Preset &default_preset = this->default_preset_for(config); + const Preset &default_preset = this->default_preset_for(config); preset.config = default_preset.config; preset.config.apply(std::move(config)); Preset::normalize(preset.config); // Report configuration fields, which are misplaced into a wrong group. - std::string incorrect_keys = Preset::remove_invalid_keys(config, default_preset.config); + std::string incorrect_keys = Preset::remove_invalid_keys(config, default_preset.config); if (! incorrect_keys.empty()) BOOST_LOG_TRIVIAL(error) << "Error in a preset file: The preset \"" << preset.file << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed"; preset.loaded = true; } catch (const std::ifstream::failure &err) { - throw std::runtime_error(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what()); + throw std::runtime_error(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what()); } catch (const std::runtime_error &err) { - throw std::runtime_error(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what()); + throw std::runtime_error(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what()); } - presets_loaded.emplace_back(preset); + presets_loaded.emplace_back(preset); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); errors_cummulative += "\n"; - } + } } - m_presets.insert(m_presets.end(), std::make_move_iterator(presets_loaded.begin()), std::make_move_iterator(presets_loaded.end())); + m_presets.insert(m_presets.end(), std::make_move_iterator(presets_loaded.begin()), std::make_move_iterator(presets_loaded.end())); std::sort(m_presets.begin() + m_num_default_presets, m_presets.end()); this->select_preset(first_visible_idx()); if (! errors_cummulative.empty()) @@ -640,8 +655,8 @@ static bool profile_print_params_same(const DynamicPrintConfig &cfg1, const Dyna t_config_option_keys diff = cfg1.diff(cfg2); // Following keys are used by the UI, not by the slicing core, therefore they are not important // when comparing profiles for equality. Ignore them. - for (const char *key : { "compatible_prints", "compatible_prints_condition", - "compatible_printers", "compatible_printers_condition", "inherits", + for (const char *key : { "compatible_prints", "compatible_prints_condition", + "compatible_printers", "compatible_printers_condition", "inherits", "print_settings_id", "filament_settings_id", "sla_print_settings_id", "sla_material_settings_id", "printer_settings_id", "printer_model", "printer_variant", "default_print_profile", "default_filament_profile", "default_sla_print_profile", "default_sla_material_profile", "printhost_apikey", "printhost_cafile" }) @@ -652,7 +667,7 @@ static bool profile_print_params_same(const DynamicPrintConfig &cfg1, const Dyna // Load a preset from an already parsed config file, insert it into the sorted sequence of presets // and select it, losing previous modifications. -// In case +// In case Preset& PresetCollection::load_external_preset( // Path to the profile source file (a G-code, an AMF or 3MF file, a config file) const std::string &path, @@ -670,7 +685,7 @@ Preset& PresetCollection::load_external_preset( cfg.apply_only(config, cfg.keys(), true); // Is there a preset already loaded with the name stored inside the config? std::deque::iterator it = this->find_preset_internal(original_name); - bool found = it != m_presets.end() && it->name == original_name; + bool found = it != m_presets.end() && it->name == original_name; if (found && profile_print_params_same(it->config, cfg)) { // The preset exists and it matches the values stored inside config. if (select) @@ -695,7 +710,7 @@ Preset& PresetCollection::load_external_preset( suffix = " (" + std::to_string(idx) + ")"; } else { if (idx == 0) - suffix = " (" + original_name + ")"; + suffix = " (" + original_name + ")"; else suffix = " (" + original_name + "-" + std::to_string(idx) + ")"; } @@ -740,8 +755,8 @@ Preset& PresetCollection::load_preset(const std::string &path, const std::string void PresetCollection::save_current_preset(const std::string &new_name) { - // 1) Find the preset with a new_name or create a new one, - // initialize it with the edited config. + // 1) Find the preset with a new_name or create a new one, + // initialize it with the edited config. auto it = this->find_preset_internal(new_name); if (it != m_presets.end() && it->name == new_name) { // Preset with the same name found. @@ -751,15 +766,15 @@ void PresetCollection::save_current_preset(const std::string &new_name) return; // Overwriting an existing preset. preset.config = std::move(m_edited_preset.config); - // The newly saved preset will be activated -> make it visible. - preset.is_visible = true; + // The newly saved preset will be activated -> make it visible. + preset.is_visible = true; } else { // Creating a new preset. - Preset &preset = *m_presets.insert(it, m_edited_preset); + Preset &preset = *m_presets.insert(it, m_edited_preset); std::string &inherits = preset.inherits(); std::string old_name = preset.name; preset.name = new_name; - preset.file = this->path_from_name(new_name); + preset.file = this->path_from_name(new_name); preset.vendor = nullptr; if (preset.is_system) { // Inheriting from a system preset. @@ -768,30 +783,30 @@ void PresetCollection::save_current_preset(const std::string &new_name) // Inheriting from a user preset. Link the new preset to the old preset. // inherits = old_name; } else { - // Inherited from a user preset. Just maintain the "inherited" flag, + // Inherited from a user preset. Just maintain the "inherited" flag, // meaning it will inherit from either the system preset, or the inherited user preset. } preset.is_default = false; preset.is_system = false; preset.is_external = false; - // The newly saved preset will be activated -> make it visible. - preset.is_visible = true; - } - // 2) Activate the saved preset. - this->select_preset_by_name(new_name, true); - // 2) Store the active preset to disk. - this->get_selected_preset().save(); + // The newly saved preset will be activated -> make it visible. + preset.is_visible = true; + } + // 2) Activate the saved preset. + this->select_preset_by_name(new_name, true); + // 2) Store the active preset to disk. + this->get_selected_preset().save(); } bool PresetCollection::delete_current_preset() { const Preset &selected = this->get_selected_preset(); - if (selected.is_default) - return false; - if (! selected.is_external && ! selected.is_system) { - // Erase the preset file. - boost::nowide::remove(selected.file.c_str()); - } + if (selected.is_default) + return false; + if (! selected.is_external && ! selected.is_system) { + // Erase the preset file. + boost::nowide::remove(selected.file.c_str()); + } // Remove the preset from the list. m_presets.erase(m_presets.begin() + m_idx_selected); // Find the next visible preset. @@ -799,9 +814,9 @@ bool PresetCollection::delete_current_preset() if (new_selected_idx < m_presets.size()) for (; new_selected_idx < m_presets.size() && ! m_presets[new_selected_idx].is_visible; ++ new_selected_idx) ; if (new_selected_idx == m_presets.size()) - for (--new_selected_idx; new_selected_idx > 0 && !m_presets[new_selected_idx].is_visible; --new_selected_idx); + for (--new_selected_idx; new_selected_idx > 0 && !m_presets[new_selected_idx].is_visible; --new_selected_idx); this->select_preset(new_selected_idx); - return true; + return true; } void PresetCollection::load_bitmap_default(wxWindow *window, const std::string &file_name) @@ -823,25 +838,39 @@ const Preset* PresetCollection::get_selected_preset_parent() const if (this->get_selected_idx() == -1) // This preset collection has no preset activated yet. Only the get_edited_preset() is valid. return nullptr; - const std::string &inherits = this->get_edited_preset().inherits(); +// const std::string &inherits = this->get_edited_preset().inherits(); +// if (inherits.empty()) +// return this->get_selected_preset().is_system ? &this->get_selected_preset() : nullptr; + + std::string inherits = this->get_edited_preset().inherits(); if (inherits.empty()) - return this->get_selected_preset().is_system ? &this->get_selected_preset() : nullptr; + { + if (this->get_selected_preset().is_system || this->get_selected_preset().is_default) + return &this->get_selected_preset(); + if (this->get_selected_preset().is_external) + return nullptr; + + inherits = m_type != Preset::Type::TYPE_PRINTER ? "- default -" : + this->get_edited_preset().printer_technology() == ptFFF ? + "- default FFF -" : "- default SLA -" ; + } + const Preset* preset = this->find_preset(inherits, false); - return (preset == nullptr || preset->is_default || preset->is_external) ? nullptr : preset; + return (preset == nullptr/* || preset->is_default*/ || preset->is_external) ? nullptr : preset; } const Preset* PresetCollection::get_preset_parent(const Preset& child) const { const std::string &inherits = child.inherits(); if (inherits.empty()) -// return this->get_selected_preset().is_system ? &this->get_selected_preset() : nullptr; - return nullptr; +// return this->get_selected_preset().is_system ? &this->get_selected_preset() : nullptr; + return nullptr; const Preset* preset = this->find_preset(inherits, false); return (preset == nullptr/* || preset->is_default */|| preset->is_external) ? nullptr : preset; } const std::string& PresetCollection::get_suffix_modified() { - return g_suffix_modified; + return g_suffix_modified; } // Return a preset by its name. If the preset is active, a temporary copy is returned. @@ -851,7 +880,7 @@ Preset* PresetCollection::find_preset(const std::string &name, bool first_visibl Preset key(m_type, name, false); auto it = this->find_preset_internal(name); // Ensure that a temporary copy is returned if the preset found is currently selected. - return (it != m_presets.end() && it->name == key.name) ? &this->preset(it - m_presets.begin()) : + return (it != m_presets.end() && it->name == key.name) ? &this->preset(it - m_presets.begin()) : first_visible_if_not_found ? &this->first_visible() : nullptr; } @@ -871,9 +900,9 @@ void PresetCollection::set_default_suppressed(bool default_suppressed) { if (m_default_suppressed != default_suppressed) { m_default_suppressed = default_suppressed; - bool default_visible = ! default_suppressed || m_idx_selected < m_num_default_presets; - for (size_t i = 0; i < m_num_default_presets; ++ i) - m_presets[i].is_visible = default_visible; + bool default_visible = ! default_suppressed || m_idx_selected < m_num_default_presets; + for (size_t i = 0; i < m_num_default_presets; ++ i) + m_presets[i].is_visible = default_visible; } } @@ -906,7 +935,7 @@ size_t PresetCollection::update_compatible_internal(const Preset &active_printer //void PresetCollection::delete_current_preset(); // Update the wxChoice UI component from this list of presets. -// Hide the +// Hide the void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui) { if (ui == nullptr) @@ -915,12 +944,12 @@ void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui) // Otherwise fill in the list from scratch. ui->Freeze(); ui->Clear(); - size_t selected_preset_item = 0; + size_t selected_preset_item = 0; - const Preset &selected_preset = this->get_selected_preset(); - // Show wide icons if the currently selected preset is not compatible with the current printer, - // and draw a red flag in front of the selected preset. - bool wide_icons = ! selected_preset.is_compatible && m_bitmap_incompatible != nullptr; + const Preset &selected_preset = this->get_selected_preset(); + // Show wide icons if the currently selected preset is not compatible with the current printer, + // and draw a red flag in front of the selected preset. + bool wide_icons = ! selected_preset.is_compatible && m_bitmap_incompatible != nullptr; /* It's supposed that standard size of an icon is 16px*16px for 100% scaled display. * So set sizes for solid_colored icons used for filament preset @@ -932,87 +961,87 @@ void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui) const int thin_space_icon_width = 4 * scale_f + 0.5f; const int wide_space_icon_width = 6 * scale_f + 0.5f; - std::map nonsys_presets; - wxString selected = ""; - if (!this->m_presets.front().is_visible) + std::map nonsys_presets; + wxString selected = ""; + if (!this->m_presets.front().is_visible) ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap)); - for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++ i) { + for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++ i) { const Preset &preset = this->m_presets[i]; if (! preset.is_visible || (! preset.is_compatible && i != m_idx_selected)) continue; - std::string bitmap_key = ""; - // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left - // to the filament color image. - if (wide_icons) - bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; - bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; - wxBitmap *bmp = m_bitmap_cache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; - if (wide_icons) - // Paint a red flag for incompatible presets. - bmps.emplace_back(preset.is_compatible ? m_bitmap_cache->mkclear(icon_width, icon_height) : *m_bitmap_incompatible); - // Paint the color bars. - bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); - bmps.emplace_back(*m_bitmap_main_frame); - // Paint a lock at the system presets. - bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); - bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmap_lock : m_bitmap_cache->mkclear(icon_width, icon_height)); - bmp = m_bitmap_cache->insert(bitmap_key, bmps); - } + std::string bitmap_key = ""; + // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left + // to the filament color image. + if (wide_icons) + bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; + bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; + wxBitmap *bmp = m_bitmap_cache->find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + if (wide_icons) + // Paint a red flag for incompatible presets. + bmps.emplace_back(preset.is_compatible ? m_bitmap_cache->mkclear(icon_width, icon_height) : *m_bitmap_incompatible); + // Paint the color bars. + bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); + bmps.emplace_back(*m_bitmap_main_frame); + // Paint a lock at the system presets. + bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); + bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmap_lock : m_bitmap_cache->mkclear(icon_width, icon_height)); + bmp = m_bitmap_cache->insert(bitmap_key, bmps); + } - if (preset.is_default || preset.is_system) { - ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), - (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); - if (i == m_idx_selected) - selected_preset_item = ui->GetCount() - 1; - } - else - { - nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), bmp/*preset.is_compatible*/); - if (i == m_idx_selected) - selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); - } - if (i + 1 == m_num_default_presets) + if (preset.is_default || preset.is_system) { + ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), + (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); + if (i == m_idx_selected) + selected_preset_item = ui->GetCount() - 1; + } + else + { + nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), bmp/*preset.is_compatible*/); + if (i == m_idx_selected) + selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); + } + if (i + 1 == m_num_default_presets) ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap)); - } - if (!nonsys_presets.empty()) - { + } + if (!nonsys_presets.empty()) + { ui->set_label_marker(ui->Append(PresetCollection::separator(L("User presets")), wxNullBitmap)); - for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { - ui->Append(it->first, *it->second); - if (it->first == selected) - selected_preset_item = ui->GetCount() - 1; - } - } - if (m_type == Preset::TYPE_PRINTER) { - std::string bitmap_key = ""; - // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left - // to the filament color image. - if (wide_icons) - bitmap_key += "wide,"; - bitmap_key += "add_printer"; - wxBitmap *bmp = m_bitmap_cache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; - if (wide_icons) - // Paint a red flag for incompatible presets. - bmps.emplace_back(m_bitmap_cache->mkclear(icon_width, icon_height)); - // Paint the color bars. - bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); - bmps.emplace_back(*m_bitmap_main_frame); - // Paint a lock at the system presets. - bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); - bmps.emplace_back(m_bitmap_add ? *m_bitmap_add : wxNullBitmap); - bmp = m_bitmap_cache->insert(bitmap_key, bmps); - } - ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add a new printer")), *bmp), GUI::PresetComboBox::LABEL_ITEM_CONFIG_WIZARD); - } + for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + ui->Append(it->first, *it->second); + if (it->first == selected) + selected_preset_item = ui->GetCount() - 1; + } + } + if (m_type == Preset::TYPE_PRINTER) { + std::string bitmap_key = ""; + // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left + // to the filament color image. + if (wide_icons) + bitmap_key += "wide,"; + bitmap_key += "add_printer"; + wxBitmap *bmp = m_bitmap_cache->find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + if (wide_icons) + // Paint a red flag for incompatible presets. + bmps.emplace_back(m_bitmap_cache->mkclear(icon_width, icon_height)); + // Paint the color bars. + bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); + bmps.emplace_back(*m_bitmap_main_frame); + // Paint a lock at the system presets. + bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); + bmps.emplace_back(m_bitmap_add ? *m_bitmap_add : wxNullBitmap); + bmp = m_bitmap_cache->insert(bitmap_key, bmps); + } + ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add a new printer")), *bmp), GUI::PresetComboBox::LABEL_ITEM_CONFIG_WIZARD); + } - ui->SetSelection(selected_preset_item); - ui->SetToolTip(ui->GetString(selected_preset_item)); + ui->SetSelection(selected_preset_item); + ui->SetToolTip(ui->GetString(selected_preset_item)); ui->check_selection(); ui->Thaw(); @@ -1027,7 +1056,7 @@ size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompati return 0; ui->Freeze(); ui->Clear(); - size_t selected_preset_item = 0; + size_t selected_preset_item = 0; /* It's supposed that standard size of an icon is 16px*16px for 100% scaled display. * So set sizes for solid_colored(empty) icons used for preset @@ -1037,52 +1066,52 @@ size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompati const int icon_height = 16 * scale_f + 0.5f; const int icon_width = 16 * scale_f + 0.5f; - std::map nonsys_presets; - wxString selected = ""; - if (!this->m_presets.front().is_visible) - ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap); - for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++i) { + std::map nonsys_presets; + wxString selected = ""; + if (!this->m_presets.front().is_visible) + ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap); + for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++i) { const Preset &preset = this->m_presets[i]; if (! preset.is_visible || (! show_incompatible && ! preset.is_compatible && i != m_idx_selected)) continue; - std::string bitmap_key = "tab"; - bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; - bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; - wxBitmap *bmp = m_bitmap_cache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; - const wxBitmap* tmp_bmp = preset.is_compatible ? m_bitmap_compatible : m_bitmap_incompatible; - bmps.emplace_back((tmp_bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *tmp_bmp); - // Paint a lock at the system presets. - bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmap_lock : m_bitmap_cache->mkclear(icon_width, icon_height)); - bmp = m_bitmap_cache->insert(bitmap_key, bmps); - } + std::string bitmap_key = "tab"; + bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; + bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; + wxBitmap *bmp = m_bitmap_cache->find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + const wxBitmap* tmp_bmp = preset.is_compatible ? m_bitmap_compatible : m_bitmap_incompatible; + bmps.emplace_back((tmp_bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *tmp_bmp); + // Paint a lock at the system presets. + bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmap_lock : m_bitmap_cache->mkclear(icon_width, icon_height)); + bmp = m_bitmap_cache->insert(bitmap_key, bmps); + } - if (preset.is_default || preset.is_system) { - ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), - (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); - if (i == m_idx_selected) - selected_preset_item = ui->GetCount() - 1; - } - else - { - nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), bmp/*preset.is_compatible*/); - if (i == m_idx_selected) - selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); - } - if (i + 1 == m_num_default_presets) - ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap); + if (preset.is_default || preset.is_system) { + ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), + (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); + if (i == m_idx_selected) + selected_preset_item = ui->GetCount() - 1; + } + else + { + nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), bmp/*preset.is_compatible*/); + if (i == m_idx_selected) + selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); + } + if (i + 1 == m_num_default_presets) + ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap); + } + if (!nonsys_presets.empty()) + { + ui->Append(PresetCollection::separator(L("User presets")), wxNullBitmap); + for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + ui->Append(it->first, *it->second); + if (it->first == selected) + selected_preset_item = ui->GetCount() - 1; + } } - if (!nonsys_presets.empty()) - { - ui->Append(PresetCollection::separator(L("User presets")), wxNullBitmap); - for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { - ui->Append(it->first, *it->second); - if (it->first == selected) - selected_preset_item = ui->GetCount() - 1; - } - } if (m_type == Preset::TYPE_PRINTER) { wxBitmap *bmp = m_bitmap_cache->find("add_printer_tab"); if (bmp == nullptr) { @@ -1094,10 +1123,10 @@ size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompati } ui->Append(PresetCollection::separator("Add a new printer"), *bmp); } - ui->SetSelection(selected_preset_item); - ui->SetToolTip(ui->GetString(selected_preset_item)); + ui->SetSelection(selected_preset_item); + ui->SetToolTip(ui->GetString(selected_preset_item)); ui->Thaw(); - return selected_preset_item; + return selected_preset_item; } // Update a dirty floag of the current preset, update the labels of the UI component accordingly. @@ -1117,11 +1146,11 @@ bool PresetCollection::update_dirty_ui(wxBitmapComboBox *ui) const Preset *preset = this->find_preset(preset_name, false); // The old_label could be the "----- system presets ------" or the "------- user presets --------" separator. // assert(preset != nullptr); - if (preset != nullptr) { - std::string new_label = preset->is_dirty ? preset->name + g_suffix_modified : preset->name; - if (old_label != new_label) - ui->SetString(ui_id, wxString::FromUTF8(new_label.c_str())); - } + if (preset != nullptr) { + std::string new_label = preset->is_dirty ? preset->name + g_suffix_modified : preset->name; + if (old_label != new_label) + ui->SetString(ui_id, wxString::FromUTF8(new_label.c_str())); + } } #ifdef __APPLE__ // wxWidgets on OSX do not upload the text of the combo box line automatically. @@ -1134,15 +1163,15 @@ bool PresetCollection::update_dirty_ui(wxBitmapComboBox *ui) template void add_correct_opts_to_diff(const std::string &opt_key, t_config_option_keys& vec, const ConfigBase &other, const ConfigBase &this_c) { - const T* opt_init = static_cast(other.option(opt_key)); - const T* opt_cur = static_cast(this_c.option(opt_key)); - int opt_init_max_id = opt_init->values.size() - 1; - for (int i = 0; i < opt_cur->values.size(); i++) - { - int init_id = i <= opt_init_max_id ? i : 0; - if (opt_cur->values[i] != opt_init->values[init_id]) - vec.emplace_back(opt_key + "#" + std::to_string(i)); - } + const T* opt_init = static_cast(other.option(opt_key)); + const T* opt_cur = static_cast(this_c.option(opt_key)); + int opt_init_max_id = opt_init->values.size() - 1; + for (int i = 0; i < opt_cur->values.size(); i++) + { + int init_id = i <= opt_init_max_id ? i : 0; + if (opt_cur->values[i] != opt_init->values[init_id]) + vec.emplace_back(opt_key + "#" + std::to_string(i)); + } } // Use deep_diff to correct return of changed options, considering individual options for each extruder. @@ -1176,10 +1205,10 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi std::vector PresetCollection::dirty_options(const Preset *edited, const Preset *reference, const bool deep_compare /*= false*/) { std::vector changed; - if (edited != nullptr && reference != nullptr) { + if (edited != nullptr && reference != nullptr) { changed = deep_compare ? - deep_diff(edited->config, reference->config) : - reference->config.diff(edited->config); + deep_diff(edited->config, reference->config) : + reference->config.diff(edited->config); // The "compatible_printers" option key is handled differently from the others: // It is not mandatory. If the key is missing, it means it is compatible with any printer. // If the key exists and it is empty, it means it is compatible with no printer. @@ -1202,19 +1231,19 @@ Preset& PresetCollection::select_preset(size_t idx) idx = first_visible_idx(); m_idx_selected = idx; m_edited_preset = m_presets[idx]; - bool default_visible = ! m_default_suppressed || m_idx_selected < m_num_default_presets; - for (size_t i = 0; i < m_num_default_presets; ++i) - m_presets[i].is_visible = default_visible; + bool default_visible = ! m_default_suppressed || m_idx_selected < m_num_default_presets; + for (size_t i = 0; i < m_num_default_presets; ++i) + m_presets[i].is_visible = default_visible; return m_presets[idx]; } bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, bool force) -{ +{ std::string name = Preset::remove_suffix_modified(name_w_suffix); // 1) Try to find the preset by its name. auto it = this->find_preset_internal(name); size_t idx = 0; - if (it != m_presets.end() && it->name == name && it->is_visible) + if (it != m_presets.end() && it->name == name && it->is_visible) // Preset found by its name and it is visible. idx = it - m_presets.begin(); else { @@ -1237,11 +1266,11 @@ bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, b } bool PresetCollection::select_preset_by_name_strict(const std::string &name) -{ +{ // 1) Try to find the preset by its name. auto it = this->find_preset_internal(name); size_t idx = (size_t)-1; - if (it != m_presets.end() && it->name == name && it->is_visible) + if (it != m_presets.end() && it->name == name && it->is_visible) // Preset found by its name. idx = it - m_presets.begin(); // 2) Select the new preset. @@ -1308,9 +1337,9 @@ std::vector PresetCollection::system_preset_names() const ++ num; std::vector out; out.reserve(num); - for (const Preset &preset : m_presets) - if (preset.is_system) - out.emplace_back(preset.name); + for (const Preset &preset : m_presets) + if (preset.is_system) + out.emplace_back(preset.name); std::sort(out.begin(), out.end()); return out; } @@ -1318,7 +1347,7 @@ std::vector PresetCollection::system_preset_names() const // Generate a file path from a profile name. Add the ".ini" suffix if it is missing. std::string PresetCollection::path_from_name(const std::string &new_name) const { - std::string file_name = boost::iends_with(new_name, ".ini") ? new_name : (new_name + ".ini"); + std::string file_name = boost::iends_with(new_name, ".ini") ? new_name : (new_name + ".ini"); return (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string(); } @@ -1329,13 +1358,13 @@ void PresetCollection::clear_bitmap_cache() wxString PresetCollection::separator(const std::string &label) { - return wxString::FromUTF8(PresetCollection::separator_head()) + _(label) + wxString::FromUTF8(PresetCollection::separator_tail()); + return wxString::FromUTF8(PresetCollection::separator_head()) + _(label) + wxString::FromUTF8(PresetCollection::separator_tail()); } -const Preset& PrinterPresetCollection::default_preset_for(const DynamicPrintConfig &config) const -{ +const Preset& PrinterPresetCollection::default_preset_for(const DynamicPrintConfig &config) const +{ const ConfigOptionEnumGeneric *opt_printer_technology = config.opt("printer_technology"); - return this->default_preset((opt_printer_technology == nullptr || opt_printer_technology->value == ptFFF) ? 0 : 1); + return this->default_preset((opt_printer_technology == nullptr || opt_printer_technology->value == ptFFF) ? 0 : 1); } const Preset* PrinterPresetCollection::find_by_model_id(const std::string &model_id) const diff --git a/src/slic3r/GUI/Preset.hpp b/src/slic3r/GUI/Preset.hpp index 8fd1652a83..e1efdc1ef0 100644 --- a/src/slic3r/GUI/Preset.hpp +++ b/src/slic3r/GUI/Preset.hpp @@ -8,7 +8,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/PrintConfig.hpp" -#include "slic3r/Utils/Semver.hpp" +#include "libslic3r/Semver.hpp" class wxBitmap; class wxBitmapComboBox; diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index fb3b6f7a47..3aee71c4c7 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -72,6 +72,8 @@ PresetBundle::PresetBundle() : this->filaments.default_preset().config.option("filament_settings_id", true)->values = { "" }; this->filaments.default_preset().compatible_printers_condition(); this->filaments.default_preset().inherits(); + // Set all the nullable values to nils. + this->filaments.default_preset().config.null_nullables(); this->sla_materials.default_preset().config.optptr("sla_material_settings_id", true); this->sla_materials.default_preset().compatible_printers_condition(); @@ -761,8 +763,11 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool } } // Load the configs into this->filaments and make them active. - this->filament_presets.clear(); - for (size_t i = 0; i < configs.size(); ++ i) { + this->filament_presets = std::vector(configs.size()); + // To avoid incorrect selection of the first filament preset (means a value of Preset->m_idx_selected) + // in a case when next added preset take a place of previosly selected preset, + // we should add presets from last to first + for (int i = (int)configs.size()-1; i >= 0; i--) { DynamicPrintConfig &cfg = configs[i]; // Split the "compatible_printers_condition" and "inherits" from the cummulative vectors to separate filament presets. cfg.opt_string("compatible_printers_condition", true) = compatible_printers_condition_values[i + 1]; @@ -781,13 +786,13 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool if (i == 0) suffix[0] = 0; else - sprintf(suffix, "%d", i); + sprintf(suffix, "%d", (int)i); std::string new_name = name + suffix; loaded = &this->filaments.load_preset(this->filaments.path_from_name(new_name), new_name, std::move(cfg), i == 0); loaded->save(); } - this->filament_presets.emplace_back(loaded->name); + this->filament_presets[i] = loaded->name; } } // 4) Load the project config values (the per extruder wipe matrix etc). @@ -837,7 +842,7 @@ void PresetBundle::load_config_file_config_bundle(const std::string &path, const return preset_name_dst; // Try to generate another name. char buf[64]; - sprintf(buf, " (%d)", i); + sprintf(buf, " (%d)", (int)i); preset_name_dst = preset_name_src + buf + bundle_name; } } @@ -1366,7 +1371,7 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst continue; c << std::endl << "[" << presets->section_name() << ":" << preset.name << "]" << std::endl; for (const std::string &opt_key : preset.config.keys()) - c << opt_key << " = " << preset.config.serialize(opt_key) << std::endl; + c << opt_key << " = " << preset.config.opt_serialize(opt_key) << std::endl; } } @@ -1379,7 +1384,7 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst for (size_t i = 0; i < this->filament_presets.size(); ++ i) { char suffix[64]; if (i > 0) - sprintf(suffix, "_%d", i); + sprintf(suffix, "_%d", (int)i); else suffix[0] = 0; c << "filament" << suffix << " = " << this->filament_presets[i] << std::endl; diff --git a/src/slic3r/GUI/ProgressStatusBar.cpp b/src/slic3r/GUI/ProgressStatusBar.cpp index b48c5732b5..33e4855cdd 100644 --- a/src/slic3r/GUI/ProgressStatusBar.cpp +++ b/src/slic3r/GUI/ProgressStatusBar.cpp @@ -14,20 +14,20 @@ namespace Slic3r { -ProgressStatusBar::ProgressStatusBar(wxWindow *parent, int id): - self(new wxStatusBar(parent ? parent : GUI::wxGetApp().mainframe, - id == -1? wxID_ANY : id)), - m_timer(new wxTimer(self)), - m_prog (new wxGauge(self, - wxGA_HORIZONTAL, - 100, - wxDefaultPosition, - wxDefaultSize)), - m_cancelbutton(new wxButton(self, - -1, - _(L("Cancel")), - wxDefaultPosition, - wxDefaultSize)) +ProgressStatusBar::ProgressStatusBar(wxWindow *parent, int id) + : self{new wxStatusBar(parent ? parent : GUI::wxGetApp().mainframe, + id == -1 ? wxID_ANY : id)} + , m_prog{new wxGauge(self, + wxGA_HORIZONTAL, + 100, + wxDefaultPosition, + wxDefaultSize)} + , m_cancelbutton{new wxButton(self, + -1, + _(L("Cancel")), + wxDefaultPosition, + wxDefaultSize)} + , m_timer{new wxTimer(self)} { m_prog->Hide(); m_cancelbutton->Hide(); @@ -168,6 +168,11 @@ void ProgressStatusBar::set_status_text(const char *txt) this->set_status_text(wxString::FromUTF8(txt)); } +wxString ProgressStatusBar::get_status_text() const +{ + return self->GetStatusText(); +} + void ProgressStatusBar::show_cancel_button() { if(m_cancelbutton) m_cancelbutton->Show(); diff --git a/src/slic3r/GUI/ProgressStatusBar.hpp b/src/slic3r/GUI/ProgressStatusBar.hpp index 225b0331ef..1aab67d5aa 100644 --- a/src/slic3r/GUI/ProgressStatusBar.hpp +++ b/src/slic3r/GUI/ProgressStatusBar.hpp @@ -2,7 +2,9 @@ #define PROGRESSSTATUSBAR_HPP #include +#include #include +#include class wxTimer; class wxGauge; @@ -23,9 +25,9 @@ namespace Slic3r { class ProgressStatusBar { wxStatusBar *self; // we cheat! It should be the base class but: perl! - wxTimer *m_timer; wxGauge *m_prog; wxButton *m_cancelbutton; + std::unique_ptr m_timer; public: /// Cancel callback function type @@ -51,6 +53,7 @@ public: void set_status_text(const wxString& txt); void set_status_text(const std::string& txt); void set_status_text(const char *txt); + wxString get_status_text() const; // Temporary methods to satisfy Perl side void show_cancel_button(); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index ff994c32d5..937619a460 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -6,7 +6,8 @@ #include "GUI_ObjectManipulation.hpp" #include "GUI_ObjectList.hpp" #include "Gizmos/GLGizmoBase.hpp" -#include "slic3r/GUI/3DScene.hpp" +#include "3DScene.hpp" +#include "Camera.hpp" #include @@ -47,6 +48,26 @@ Selection::VolumeCache::VolumeCache(const Geometry::Transformation& volume_trans { } +bool Selection::Clipboard::is_sla_compliant() const +{ + if (m_mode == Selection::Volume) + return false; + + for (const ModelObject* o : m_model.objects) + { + if (o->is_multiparts()) + return false; + + for (const ModelVolume* v : o->volumes) + { + if (v->is_modifier()) + return false; + } + } + + return true; +} + Selection::Selection() : m_volumes(nullptr) , m_model(nullptr) @@ -79,14 +100,15 @@ void Selection::set_volumes(GLVolumePtrs* volumes) update_valid(); } -bool Selection::init(bool useVBOs) +// Init shall be called from the OpenGL render function, so that the OpenGL context is initialized! +bool Selection::init() { - if (!m_arrow.init(useVBOs)) + if (!m_arrow.init()) return false; m_arrow.set_scale(5.0 * Vec3d::Ones()); - if (!m_curved_arrow.init(useVBOs)) + if (!m_curved_arrow.init()) return false; m_curved_arrow.set_scale(5.0 * Vec3d::Ones()); @@ -119,11 +141,13 @@ void Selection::add(unsigned int volume_idx, bool as_single_selection, bool chec needs_reset |= as_single_selection && !is_any_modifier() && volume->is_modifier; needs_reset |= is_any_modifier() && !volume->is_modifier; - if (needs_reset) - clear(); - if (!already_contained || needs_reset) { + wxGetApp().plater()->take_snapshot(_(L("Selection-Add"))); + + if (needs_reset) + clear(); + if (!keep_instance_mode) m_mode = volume->is_modifier ? Volume : Instance; } @@ -142,7 +166,8 @@ void Selection::add(unsigned int volume_idx, bool as_single_selection, bool chec } case Instance: { - do_add_instance(volume->object_idx(), volume->instance_idx()); + Plater::SuppressSnapshots suppress(wxGetApp().plater()); + add_instance(volume->object_idx(), volume->instance_idx(), as_single_selection); break; } } @@ -156,6 +181,11 @@ void Selection::remove(unsigned int volume_idx) if (!m_valid || ((unsigned int)m_volumes->size() <= volume_idx)) return; + if (!contains_volume(volume_idx)) + return; + + wxGetApp().plater()->take_snapshot(_(L("Selection-Remove"))); + GLVolume* volume = (*m_volumes)[volume_idx]; switch (m_mode) @@ -181,13 +211,20 @@ void Selection::add_object(unsigned int object_idx, bool as_single_selection) if (!m_valid) return; + std::vector volume_idxs = get_volume_idxs_from_object(object_idx); + if ((!as_single_selection && contains_all_volumes(volume_idxs)) || + (as_single_selection && matches(volume_idxs))) + return; + + wxGetApp().plater()->take_snapshot(_(L("Selection-Add Object"))); + // resets the current list if needed if (as_single_selection) clear(); m_mode = Instance; - do_add_object(object_idx); + do_add_volumes(volume_idxs); update_type(); this->set_bounding_boxes_dirty(); @@ -198,6 +235,8 @@ void Selection::remove_object(unsigned int object_idx) if (!m_valid) return; + wxGetApp().plater()->take_snapshot(_(L("Selection-Remove Object"))); + do_remove_object(object_idx); update_type(); @@ -209,13 +248,20 @@ void Selection::add_instance(unsigned int object_idx, unsigned int instance_idx, if (!m_valid) return; + std::vector volume_idxs = get_volume_idxs_from_instance(object_idx, instance_idx); + if ((!as_single_selection && contains_all_volumes(volume_idxs)) || + (as_single_selection && matches(volume_idxs))) + return; + + wxGetApp().plater()->take_snapshot(_(L("Selection-Add Instance"))); + // resets the current list if needed if (as_single_selection) clear(); m_mode = Instance; - do_add_instance(object_idx, instance_idx); + do_add_volumes(volume_idxs); update_type(); this->set_bounding_boxes_dirty(); @@ -226,6 +272,8 @@ void Selection::remove_instance(unsigned int object_idx, unsigned int instance_i if (!m_valid) return; + wxGetApp().plater()->take_snapshot(_(L("Selection-Remove Instance"))); + do_remove_instance(object_idx, instance_idx); update_type(); @@ -237,21 +285,20 @@ void Selection::add_volume(unsigned int object_idx, unsigned int volume_idx, int if (!m_valid) return; + std::vector volume_idxs = get_volume_idxs_from_volume(object_idx, instance_idx, volume_idx); + if ((!as_single_selection && contains_all_volumes(volume_idxs)) || + (as_single_selection && matches(volume_idxs))) + return; + + wxGetApp().plater()->take_snapshot(_(L("Selection-Add Volume"))); + // resets the current list if needed if (as_single_selection) clear(); m_mode = Volume; - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { - GLVolume* v = (*m_volumes)[i]; - if ((v->object_idx() == object_idx) && (v->volume_idx() == volume_idx)) - { - if ((instance_idx != -1) && (v->instance_idx() == instance_idx)) - do_add_volume(i); - } - } + do_add_volumes(volume_idxs); update_type(); this->set_bounding_boxes_dirty(); @@ -262,6 +309,8 @@ void Selection::remove_volume(unsigned int object_idx, unsigned int volume_idx) if (!m_valid) return; + wxGetApp().plater()->take_snapshot(_(L("Selection-Remove Volume"))); + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { GLVolume* v = (*m_volumes)[i]; @@ -273,11 +322,67 @@ void Selection::remove_volume(unsigned int object_idx, unsigned int volume_idx) this->set_bounding_boxes_dirty(); } +void Selection::add_volumes(EMode mode, const std::vector& volume_idxs, bool as_single_selection) +{ + if (!m_valid) + return; + + if ((!as_single_selection && contains_all_volumes(volume_idxs)) || + (as_single_selection && matches(volume_idxs))) + return; + + wxGetApp().plater()->take_snapshot(_(L("Selection-Add Volumes"))); + + // resets the current list if needed + if (as_single_selection) + clear(); + + m_mode = mode; + for (unsigned int i : volume_idxs) + { + if (i < (unsigned int)m_volumes->size()) + do_add_volume(i); + } + + update_type(); + this->set_bounding_boxes_dirty(); +} + +void Selection::remove_volumes(EMode mode, const std::vector& volume_idxs) +{ + if (!m_valid) + return; + + wxGetApp().plater()->take_snapshot(_(L("Selection-Remove Volumes"))); + + m_mode = mode; + for (unsigned int i : volume_idxs) + { + if (i < (unsigned int)m_volumes->size()) + do_remove_volume(i); + } + + update_type(); + this->set_bounding_boxes_dirty(); +} + void Selection::add_all() { if (!m_valid) return; + unsigned int count = 0; + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + if (!(*m_volumes)[i]->is_wipe_tower) + ++count; + } + + if ((unsigned int)m_list.size() == count) + return; + + wxGetApp().plater()->take_snapshot(_(L("Selection-Add All"))); + m_mode = Instance; clear(); @@ -291,11 +396,47 @@ void Selection::add_all() this->set_bounding_boxes_dirty(); } +void Selection::remove_all() +{ + if (!m_valid) + return; + + if (is_empty()) + return; + +// Not taking the snapshot with non-empty Redo stack will likely be more confusing than losing the Redo stack. +// Let's wait for user feedback. +// if (!wxGetApp().plater()->can_redo()) + wxGetApp().plater()->take_snapshot(_(L("Selection-Remove All"))); + + m_mode = Instance; + clear(); +} + +void Selection::set_deserialized(EMode mode, const std::vector> &volumes_and_instances) +{ + if (! m_valid) + return; + + m_mode = mode; + for (unsigned int i : m_list) + (*m_volumes)[i]->selected = false; + m_list.clear(); + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++ i) + if (std::binary_search(volumes_and_instances.begin(), volumes_and_instances.end(), (*m_volumes)[i]->geometry_id)) + this->do_add_volume(i); + update_type(); + this->set_bounding_boxes_dirty(); +} + void Selection::clear() { if (!m_valid) return; + if (m_list.empty()) + return; + for (unsigned int i : m_list) { (*m_volumes)[i]->selected = false; @@ -308,6 +449,9 @@ void Selection::clear() // resets the cache in the sidebar wxGetApp().obj_manipul()->reset_cache(); + + // #et_FIXME fake KillFocus from sidebar + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); } // Update the selection based on the new instance IDs. @@ -382,6 +526,57 @@ bool Selection::is_from_single_object() const return (0 <= idx) && (idx < 1000); } +bool Selection::is_sla_compliant() const +{ + if (m_mode == Volume) + return false; + + for (unsigned int i : m_list) + { + if ((*m_volumes)[i]->is_modifier) + return false; + } + + return true; +} + +bool Selection::contains_all_volumes(const std::vector& volume_idxs) const +{ + for (unsigned int i : volume_idxs) + { + if (m_list.find(i) == m_list.end()) + return false; + } + + return true; +} + +bool Selection::contains_any_volume(const std::vector& volume_idxs) const +{ + for (unsigned int i : volume_idxs) + { + if (m_list.find(i) != m_list.end()) + return true; + } + + return false; +} + +bool Selection::matches(const std::vector& volume_idxs) const +{ + unsigned int count = 0; + + for (unsigned int i : volume_idxs) + { + if (m_list.find(i) != m_list.end()) + ++count; + else + return false; + } + + return count == (unsigned int)m_list.size(); +} + bool Selection::requires_uniform_scale() const { if (is_single_full_instance() || is_single_modifier() || is_single_volume()) @@ -749,6 +944,8 @@ void Selection::scale_to_fit_print_volume(const DynamicPrintConfig& config) double s = std::min(sx, std::min(sy, sz)); if (s != 1.0) { + wxGetApp().plater()->take_snapshot(_(L("Scale To Fit"))); + TransformationType type; type.set_world(); type.set_relative(); @@ -757,12 +954,12 @@ void Selection::scale_to_fit_print_volume(const DynamicPrintConfig& config) // apply scale start_dragging(); scale(s * Vec3d::Ones(), type); - wxGetApp().plater()->canvas3D()->do_scale(); + wxGetApp().plater()->canvas3D()->do_scale(L("")); // avoid storing another snapshot // center selection on print bed start_dragging(); translate(print_volume.center() - get_bounding_box().center()); - wxGetApp().plater()->canvas3D()->do_move(); + wxGetApp().plater()->canvas3D()->do_move(L("")); // avoid storing another snapshot wxGetApp().obj_manipul()->set_dirty(); } @@ -1033,61 +1230,68 @@ void Selection::render_center(bool gizmo_is_dragging) const } #endif // ENABLE_RENDER_SELECTION_CENTER -void Selection::render_sidebar_hints(const std::string& sidebar_field) const +void Selection::render_sidebar_hints(const std::string& sidebar_field, const Shader& shader) const { if (sidebar_field.empty()) return; - glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); - glsafe(::glEnable(GL_DEPTH_TEST)); + if (!boost::starts_with(sidebar_field, "layer")) + { + shader.start_using(); + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); + glsafe(::glEnable(GL_LIGHTING)); + } - glsafe(::glEnable(GL_LIGHTING)); + glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glPushMatrix()); - const Vec3d& center = get_bounding_box().center(); - - if (is_single_full_instance() && ! wxGetApp().obj_manipul()->get_world_coordinates()) + if (!boost::starts_with(sidebar_field, "layer")) { - glsafe(::glTranslated(center(0), center(1), center(2))); - if (!boost::starts_with(sidebar_field, "position")) + const Vec3d& center = get_bounding_box().center(); + + if (is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) { - Transform3d orient_matrix = Transform3d::Identity(); - if (boost::starts_with(sidebar_field, "scale")) - orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - else if (boost::starts_with(sidebar_field, "rotation")) + glsafe(::glTranslated(center(0), center(1), center(2))); + if (!boost::starts_with(sidebar_field, "position")) { - if (boost::ends_with(sidebar_field, "x")) + Transform3d orient_matrix = Transform3d::Identity(); + if (boost::starts_with(sidebar_field, "scale")) orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - else if (boost::ends_with(sidebar_field, "y")) + else if (boost::starts_with(sidebar_field, "rotation")) { - const Vec3d& rotation = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation(); - if (rotation(0) == 0.0) + if (boost::ends_with(sidebar_field, "x")) orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - else - orient_matrix.rotate(Eigen::AngleAxisd(rotation(2), Vec3d::UnitZ())); + else if (boost::ends_with(sidebar_field, "y")) + { + const Vec3d& rotation = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation(); + if (rotation(0) == 0.0) + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + else + orient_matrix.rotate(Eigen::AngleAxisd(rotation(2), Vec3d::UnitZ())); + } } + + glsafe(::glMultMatrixd(orient_matrix.data())); } + } + else if (is_single_volume() || is_single_modifier()) + { + glsafe(::glTranslated(center(0), center(1), center(2))); + Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + if (!boost::starts_with(sidebar_field, "position")) + orient_matrix = orient_matrix * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } - } - else if (is_single_volume() || is_single_modifier()) - { - glsafe(::glTranslated(center(0), center(1), center(2))); - Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - if (!boost::starts_with(sidebar_field, "position")) - orient_matrix = orient_matrix * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true); - - glsafe(::glMultMatrixd(orient_matrix.data())); - } - else - { - glsafe(::glTranslated(center(0), center(1), center(2))); - if (requires_local_axes()) + else { - Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - glsafe(::glMultMatrixd(orient_matrix.data())); + glsafe(::glTranslated(center(0), center(1), center(2))); + if (requires_local_axes()) + { + Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + glsafe(::glMultMatrixd(orient_matrix.data())); + } } } @@ -1099,10 +1303,16 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) const render_sidebar_scale_hints(sidebar_field); else if (boost::starts_with(sidebar_field, "size")) render_sidebar_size_hints(sidebar_field); + else if (boost::starts_with(sidebar_field, "layer")) + render_sidebar_layers_hints(sidebar_field); glsafe(::glPopMatrix()); - glsafe(::glDisable(GL_LIGHTING)); + if (!boost::starts_with(sidebar_field, "layer")) + { + glsafe(::glDisable(GL_LIGHTING)); + shader.stop_using(); + } } bool Selection::requires_local_axes() const @@ -1123,10 +1333,10 @@ void Selection::copy_to_clipboard() ModelObject* dst_object = m_clipboard.add_object(); dst_object->name = src_object->name; dst_object->input_file = src_object->input_file; - dst_object->config = src_object->config; + static_cast(dst_object->config) = static_cast(src_object->config); dst_object->sla_support_points = src_object->sla_support_points; dst_object->sla_points_status = src_object->sla_points_status; - dst_object->layer_height_ranges = src_object->layer_height_ranges; + dst_object->layer_config_ranges = src_object->layer_config_ranges; // #ys_FIXME_experiment dst_object->layer_height_profile = src_object->layer_height_profile; dst_object->origin_translation = src_object->origin_translation; @@ -1181,6 +1391,110 @@ void Selection::paste_from_clipboard() } } +std::vector Selection::get_volume_idxs_from_object(unsigned int object_idx) const +{ + std::vector idxs; + + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + if ((*m_volumes)[i]->object_idx() == object_idx) + idxs.push_back(i); + } + + return idxs; +} + +std::vector Selection::get_volume_idxs_from_instance(unsigned int object_idx, unsigned int instance_idx) const +{ + std::vector idxs; + + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + const GLVolume* v = (*m_volumes)[i]; + if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx)) + idxs.push_back(i); + } + + return idxs; +} + +std::vector Selection::get_volume_idxs_from_volume(unsigned int object_idx, unsigned int instance_idx, unsigned int volume_idx) const +{ + std::vector idxs; + + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + const GLVolume* v = (*m_volumes)[i]; + if ((v->object_idx() == object_idx) && (v->volume_idx() == volume_idx)) + { + if ((instance_idx != -1) && (v->instance_idx() == instance_idx)) + idxs.push_back(i); + } + } + + return idxs; +} + +std::vector Selection::get_missing_volume_idxs_from(const std::vector& volume_idxs) const +{ + std::vector idxs; + + for (unsigned int i : m_list) + { + std::vector::const_iterator it = std::find(volume_idxs.begin(), volume_idxs.end(), i); + if (it == volume_idxs.end()) + idxs.push_back(i); + } + + return idxs; +} + +std::vector Selection::get_unselected_volume_idxs_from(const std::vector& volume_idxs) const +{ + std::vector idxs; + + for (unsigned int i : volume_idxs) + { + if (m_list.find(i) == m_list.end()) + idxs.push_back(i); + } + + return idxs; +} + +void Selection::toggle_instance_printable_state() +{ + int instance_idx = get_instance_idx(); + if (instance_idx == -1) + return; + + int obj_idx = get_object_idx(); + if ((0 <= obj_idx) && (obj_idx < (int)m_model->objects.size())) + { + ModelObject* model_object = m_model->objects[obj_idx]; + if ((0 <= instance_idx) && (instance_idx < (int)model_object->instances.size())) + { + ModelInstance* instance = model_object->instances[instance_idx]; + const bool printable = !instance->printable; + + wxString snapshot_text = model_object->instances.size() == 1 ? wxString::Format("%s %s", + printable ? _(L("Set Printable")) : _(L("Set Unprintable")), model_object->name) : + printable ? _(L("Set Printable Instance")) : _(L("Set Unprintable Instance")); + wxGetApp().plater()->take_snapshot(snapshot_text); + + instance->printable = printable; + + for (GLVolume* volume : *m_volumes) + { + if ((volume->object_idx() == obj_idx) && (volume->instance_idx() == instance_idx)) + volume->printable = instance->printable; + } + + wxGetApp().obj_list()->update_printable_state(obj_idx, instance_idx); + } + } +} + void Selection::update_valid() { m_valid = (m_volumes != nullptr) && (m_model != nullptr); @@ -1427,22 +1741,11 @@ void Selection::do_add_volume(unsigned int volume_idx) (*m_volumes)[volume_idx]->selected = true; } -void Selection::do_add_instance(unsigned int object_idx, unsigned int instance_idx) +void Selection::do_add_volumes(const std::vector& volume_idxs) { - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + for (unsigned int i : volume_idxs) { - GLVolume* v = (*m_volumes)[i]; - if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx)) - do_add_volume(i); - } -} - -void Selection::do_add_object(unsigned int object_idx) -{ - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { - GLVolume* v = (*m_volumes)[i]; - if (v->object_idx() == object_idx) + if (i < (unsigned int)m_volumes->size()) do_add_volume(i); } } @@ -1672,6 +1975,78 @@ void Selection::render_sidebar_size_hints(const std::string& sidebar_field) cons render_sidebar_scale_hints(sidebar_field); } +void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) const +{ + static const double Margin = 10.0; + + std::string field = sidebar_field; + + // extract max_z + std::string::size_type pos = field.rfind("_"); + if (pos == std::string::npos) + return; + + double max_z = std::stod(field.substr(pos + 1)); + + // extract min_z + field = field.substr(0, pos); + pos = field.rfind("_"); + if (pos == std::string::npos) + return; + + double min_z = std::stod(field.substr(pos + 1)); + + // extract type + field = field.substr(0, pos); + pos = field.rfind("_"); + if (pos == std::string::npos) + return; + + int type = std::stoi(field.substr(pos + 1)); + + const BoundingBoxf3& box = get_bounding_box(); + + const float min_x = box.min(0) - Margin; + const float max_x = box.max(0) + Margin; + const float min_y = box.min(1) - Margin; + const float max_y = box.max(1) + Margin; + + // view dependend order of rendering to keep correct transparency + bool camera_on_top = wxGetApp().plater()->get_camera().get_theta() <= 90.0f; + float z1 = camera_on_top ? min_z : max_z; + float z2 = camera_on_top ? max_z : min_z; + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glDisable(GL_CULL_FACE)); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + + ::glBegin(GL_QUADS); + if ((camera_on_top && (type == 1)) || (!camera_on_top && (type == 2))) + ::glColor4f(1.0f, 0.38f, 0.0f, 1.0f); + else + ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); + ::glVertex3f(min_x, min_y, z1); + ::glVertex3f(max_x, min_y, z1); + ::glVertex3f(max_x, max_y, z1); + ::glVertex3f(min_x, max_y, z1); + glsafe(::glEnd()); + + ::glBegin(GL_QUADS); + if ((camera_on_top && (type == 2)) || (!camera_on_top && (type == 1))) + ::glColor4f(1.0f, 0.38f, 0.0f, 1.0f); + else + ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); + ::glVertex3f(min_x, min_y, z2); + ::glVertex3f(max_x, min_y, z2); + ::glVertex3f(max_x, max_y, z2); + ::glVertex3f(min_x, max_y, z2); + glsafe(::glEnd()); + + glsafe(::glEnable(GL_CULL_FACE)); + glsafe(::glDisable(GL_BLEND)); +} + void Selection::render_sidebar_position_hint(Axis axis) const { m_arrow.set_color(AXES_COLOR[axis], 3); @@ -1890,30 +2265,79 @@ bool Selection::is_from_fully_selected_instance(unsigned int volume_idx) const void Selection::paste_volumes_from_clipboard() { - int obj_idx = get_object_idx(); - if ((obj_idx < 0) || ((int)m_model->objects.size() <= obj_idx)) +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ + + int dst_obj_idx = get_object_idx(); + if ((dst_obj_idx < 0) || ((int)m_model->objects.size() <= dst_obj_idx)) + return; + + ModelObject* dst_object = m_model->objects[dst_obj_idx]; + + int dst_inst_idx = get_instance_idx(); + if ((dst_inst_idx < 0) || ((int)dst_object->instances.size() <= dst_inst_idx)) return; ModelObject* src_object = m_clipboard.get_object(0); if (src_object != nullptr) { - ModelObject* dst_object = m_model->objects[obj_idx]; + ModelInstance* dst_instance = dst_object->instances[dst_inst_idx]; + BoundingBoxf3 dst_instance_bb = dst_object->instance_bounding_box(dst_inst_idx); + Transform3d src_matrix = src_object->instances[0]->get_transformation().get_matrix(true); + Transform3d dst_matrix = dst_instance->get_transformation().get_matrix(true); + bool from_same_object = (src_object->input_file == dst_object->input_file) && src_matrix.isApprox(dst_matrix); + + // used to keep relative position of multivolume selections when pasting from another object + BoundingBoxf3 total_bb; ModelVolumePtrs volumes; for (ModelVolume* src_volume : src_object->volumes) { ModelVolume* dst_volume = dst_object->add_volume(*src_volume); dst_volume->set_new_unique_id(); - double offset = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.05); - dst_volume->translate(offset, offset, 0.0); + if (from_same_object) + { +// // if the volume comes from the same object, apply the offset in world system +// double offset = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.05); +// dst_volume->translate(dst_matrix.inverse() * Vec3d(offset, offset, 0.0)); + } + else + { + // if the volume comes from another object, apply the offset as done when adding modifiers + // see ObjectList::load_generic_subobject() + total_bb.merge(dst_volume->mesh().bounding_box().transformed(src_volume->get_matrix())); + } + volumes.push_back(dst_volume); +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ } - wxGetApp().obj_list()->paste_volumes_into_list(obj_idx, volumes); + + // keeps relative position of multivolume selections + if (!from_same_object) + { + for (ModelVolume* v : volumes) + { + v->set_offset((v->get_offset() - total_bb.center()) + dst_matrix.inverse() * (Vec3d(dst_instance_bb.max(0), dst_instance_bb.min(1), dst_instance_bb.min(2)) + 0.5 * total_bb.size() - dst_instance->get_transformation().get_offset())); + } + } + + wxGetApp().obj_list()->paste_volumes_into_list(dst_obj_idx, volumes); } + +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ } void Selection::paste_objects_from_clipboard() { +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ + std::vector object_idxs; const ModelObjectPtrs& src_objects = m_clipboard.get_objects(); for (const ModelObject* src_object : src_objects) @@ -1927,9 +2351,16 @@ void Selection::paste_objects_from_clipboard() } object_idxs.push_back(m_model->objects.size() - 1); +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ } wxGetApp().obj_list()->paste_objects_into_list(object_idxs); + +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ } } // namespace GUI diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 54bf52706b..c27b4cc29d 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -11,8 +11,8 @@ typedef class GLUquadric GLUquadricObj; #endif // ENABLE_RENDER_SELECTION_CENTER namespace Slic3r { +class Shader; namespace GUI { - class TransformationType { public: @@ -152,6 +152,8 @@ public: void reset() { m_model.clear_objects(); } bool is_empty() const { return m_model.objects.empty(); } + bool is_sla_compliant() const; + ModelObject* add_object() { return m_model.add_object(); } ModelObject* get_object(unsigned int id) { return (id < (unsigned int)m_model.objects.size()) ? m_model.objects[id] : nullptr; } const ModelObjectPtrs& get_objects() const { return m_model.objects; } @@ -169,7 +171,7 @@ private: Vec3d dragging_center; // Map from indices of ModelObject instances in Model::objects // to a set of indices of ModelVolume instances in ModelObject::instances - // Here the index means a position inside the respective std::vector, not ModelID. + // Here the index means a position inside the respective std::vector, not ObjectID. ObjectIdxsToInstanceIdxsMap content; }; @@ -210,7 +212,7 @@ public: #endif // ENABLE_RENDER_SELECTION_CENTER void set_volumes(GLVolumePtrs* volumes); - bool init(bool useVBOs); + bool init(); bool is_enabled() const { return m_enabled; } void set_enabled(bool enable) { m_enabled = enable; } @@ -233,7 +235,14 @@ public: void add_volume(unsigned int object_idx, unsigned int volume_idx, int instance_idx, bool as_single_selection = true); void remove_volume(unsigned int object_idx, unsigned int volume_idx); + void add_volumes(EMode mode, const std::vector& volume_idxs, bool as_single_selection = true); + void remove_volumes(EMode mode, const std::vector& volume_idxs); + void add_all(); + void remove_all(); + + // To be called after Undo or Redo once the volumes are updated. + void set_deserialized(EMode mode, const std::vector> &volumes_and_instances); // Update the selection based on the new instance IDs. void instances_changed(const std::vector &instance_ids_selected); @@ -257,8 +266,16 @@ public: bool is_mixed() const { return m_type == Mixed; } bool is_from_single_instance() const { return get_instance_idx() != -1; } bool is_from_single_object() const; + bool is_sla_compliant() const; bool contains_volume(unsigned int volume_idx) const { return m_list.find(volume_idx) != m_list.end(); } + // returns true if the selection contains all the given indices + bool contains_all_volumes(const std::vector& volume_idxs) const; + // returns true if the selection contains at least one of the given indices + bool contains_any_volume(const std::vector& volume_idxs) const; + // returns true if the selection contains all and only the given indices + bool matches(const std::vector& volume_idxs) const; + bool requires_uniform_scale() const; // Returns the the object id if the selection is from a single object, otherwise is -1 @@ -299,7 +316,7 @@ public: #if ENABLE_RENDER_SELECTION_CENTER void render_center(bool gizmo_is_dragging) const; #endif // ENABLE_RENDER_SELECTION_CENTER - void render_sidebar_hints(const std::string& sidebar_field) const; + void render_sidebar_hints(const std::string& sidebar_field, const Shader& shader) const; bool requires_local_axes() const; @@ -308,13 +325,25 @@ public: const Clipboard& get_clipboard() const { return m_clipboard; } + // returns the list of idxs of the volumes contained into the object with the given idx + std::vector get_volume_idxs_from_object(unsigned int object_idx) const; + // returns the list of idxs of the volumes contained into the instance with the given idxs + std::vector get_volume_idxs_from_instance(unsigned int object_idx, unsigned int instance_idx) const; + // returns the idx of the volume corresponding to the volume with the given idxs + std::vector get_volume_idxs_from_volume(unsigned int object_idx, unsigned int instance_idx, unsigned int volume_idx) const; + // returns the list of idxs of the volumes contained in the selection but not in the given list + std::vector get_missing_volume_idxs_from(const std::vector& volume_idxs) const; + // returns the list of idxs of the volumes contained in the given list but not in the selection + std::vector get_unselected_volume_idxs_from(const std::vector& volume_idxs) const; + + void toggle_instance_printable_state(); + private: void update_valid(); void update_type(); void set_caches(); void do_add_volume(unsigned int volume_idx); - void do_add_instance(unsigned int object_idx, unsigned int instance_idx); - void do_add_object(unsigned int object_idx); + void do_add_volumes(const std::vector& volume_idxs); void do_remove_volume(unsigned int volume_idx); void do_remove_instance(unsigned int object_idx, unsigned int instance_idx); void do_remove_object(unsigned int object_idx); @@ -329,10 +358,13 @@ private: void render_sidebar_rotation_hints(const std::string& sidebar_field) const; void render_sidebar_scale_hints(const std::string& sidebar_field) const; void render_sidebar_size_hints(const std::string& sidebar_field) const; + void render_sidebar_layers_hints(const std::string& sidebar_field) const; void render_sidebar_position_hint(Axis axis) const; void render_sidebar_rotation_hint(Axis axis) const; void render_sidebar_scale_hint(Axis axis) const; void render_sidebar_size_hint(Axis axis, double length) const; + +public: enum SyncRotationType { // Do not synchronize rotation. Either not rotating at all, or rotating by world Z axis. SYNC_ROTATION_NONE = 0, @@ -343,6 +375,8 @@ private: }; void synchronize_unselected_instances(SyncRotationType sync_rotation_type); void synchronize_unselected_volumes(); + +private: void ensure_on_bed(); bool is_from_fully_selected_instance(unsigned int volume_idx) const; diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp index bd19c38c38..a1bae8742e 100644 --- a/src/slic3r/GUI/SysInfoDialog.cpp +++ b/src/slic3r/GUI/SysInfoDialog.cpp @@ -2,6 +2,7 @@ #include "I18N.hpp" #include "3DScene.hpp" #include "GUI.hpp" +#include "../Utils/UndoRedo.hpp" #include @@ -10,6 +11,14 @@ #include "GUI_App.hpp" #include "wxExtensions.hpp" +#ifdef _WIN32 + // The standard Windows includes. + #define WIN32_LEAN_AND_MEAN + #define NOMINMAX + #include + #include +#endif /* _WIN32 */ + namespace Slic3r { namespace GUI { @@ -36,6 +45,31 @@ std::string get_main_info(bool format_as_html) "System Version: " #endif << b_end << wxPlatformInfo::Get().GetOperatingSystemDescription() << line_end; + out << b_start << "Total RAM size [MB]: " << b_end << Slic3r::format_memsize_MB(Slic3r::total_physical_memory()); + + return out.str(); +} + +std::string get_mem_info(bool format_as_html) +{ + std::stringstream out; + + std::string b_start = format_as_html ? "" : ""; + std::string b_end = format_as_html ? "" : ""; + std::string line_end = format_as_html ? "
" : "\n"; + + std::string mem_info_str = log_memory_info(true); + std::istringstream mem_info(mem_info_str); + std::string value; + while (std::getline(mem_info, value, ':')) { + out << b_start << (value+": ") << b_end; + std::getline(mem_info, value, ';'); + out << value << line_end; + } + + const Slic3r::UndoRedo::Stack &stack = wxGetApp().plater()->undo_redo_stack_main(); + out << b_start << "RAM size reserved for the Undo / Redo stack: " << b_end << Slic3r::format_memsize_MB(stack.get_memory_limit()) << line_end; + out << b_start << "RAM size occupied by the Undo / Redo stack: " << b_end << Slic3r::format_memsize_MB(stack.memsize()) << line_end << line_end; return out.str(); } @@ -111,7 +145,7 @@ SysInfoDialog::SysInfoDialog() "" "" "", bgr_clr_str, text_clr_str, text_clr_str, - _3DScene::get_gl_info(true, true)); + get_mem_info(true) + "
" + _3DScene::get_gl_info(true, true)); m_opengl_info_html->SetPage(text); main_sizer->Add(m_opengl_info_html, 1, wxEXPAND | wxBOTTOM, 15); } diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 6cd270e5b3..3688542224 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -38,36 +38,36 @@ namespace GUI { wxDEFINE_EVENT(EVT_TAB_VALUE_CHANGED, wxCommandEvent); wxDEFINE_EVENT(EVT_TAB_PRESETS_CHANGED, SimpleEvent); -// Tab::Tab(wxNotebook* parent, const wxString& title, const char* name) : +// Tab::Tab(wxNotebook* parent, const wxString& title, const char* name) : // m_parent(parent), m_title(title), m_name(name) Tab::Tab(wxNotebook* parent, const wxString& title, Preset::Type type) : - m_parent(parent), m_title(title), m_type(type) + m_parent(parent), m_title(title), m_type(type) { - Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL/*, name*/); - this->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL/*, name*/); + this->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - m_compatible_printers.type = Preset::TYPE_PRINTER; - m_compatible_printers.key_list = "compatible_printers"; - m_compatible_printers.key_condition = "compatible_printers_condition"; - m_compatible_printers.dialog_title = _(L("Compatible printers")); - m_compatible_printers.dialog_label = _(L("Select the printers this profile is compatible with.")); + m_compatible_printers.type = Preset::TYPE_PRINTER; + m_compatible_printers.key_list = "compatible_printers"; + m_compatible_printers.key_condition = "compatible_printers_condition"; + m_compatible_printers.dialog_title = _(L("Compatible printers")); + m_compatible_printers.dialog_label = _(L("Select the printers this profile is compatible with.")); - m_compatible_prints.type = Preset::TYPE_PRINT; - m_compatible_prints.key_list = "compatible_prints"; - m_compatible_prints.key_condition = "compatible_prints_condition"; - m_compatible_prints.dialog_title = _(L("Compatible print profiles")); - m_compatible_prints.dialog_label = _(L("Select the print profiles this profile is compatible with.")); + m_compatible_prints.type = Preset::TYPE_PRINT; + m_compatible_prints.key_list = "compatible_prints"; + m_compatible_prints.key_condition = "compatible_prints_condition"; + m_compatible_prints.dialog_title = _(L("Compatible print profiles")); + m_compatible_prints.dialog_label = _(L("Select the print profiles this profile is compatible with.")); - wxGetApp().tabs_list.push_back(this); + wxGetApp().tabs_list.push_back(this); m_em_unit = wxGetApp().em_unit(); - Bind(wxEVT_SIZE, ([this](wxSizeEvent &evt) { - for (auto page : m_pages) - if (! page.get()->IsShown()) - page->layout_valid = false; - evt.Skip(); - })); + Bind(wxEVT_SIZE, ([this](wxSizeEvent &evt) { + for (auto page : m_pages) + if (! page.get()->IsShown()) + page->layout_valid = false; + evt.Skip(); + })); } void Tab::set_type() @@ -89,169 +89,169 @@ void Tab::create_preset_tab() m_preset_bundle = wxGetApp().preset_bundle; - // Vertical sizer to hold the choice menu and the rest of the page. + // Vertical sizer to hold the choice menu and the rest of the page. #ifdef __WXOSX__ - auto *main_sizer = new wxBoxSizer(wxVERTICAL); - main_sizer->SetSizeHints(this); - this->SetSizer(main_sizer); + auto *main_sizer = new wxBoxSizer(wxVERTICAL); + main_sizer->SetSizeHints(this); + this->SetSizer(main_sizer); - // Create additional panel to Fit() it from OnActivate() - // It's needed for tooltip showing on OSX - m_tmp_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); - auto panel = m_tmp_panel; - auto sizer = new wxBoxSizer(wxVERTICAL); - m_tmp_panel->SetSizer(sizer); - m_tmp_panel->Layout(); + // Create additional panel to Fit() it from OnActivate() + // It's needed for tooltip showing on OSX + m_tmp_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); + auto panel = m_tmp_panel; + auto sizer = new wxBoxSizer(wxVERTICAL); + m_tmp_panel->SetSizer(sizer); + m_tmp_panel->Layout(); - main_sizer->Add(m_tmp_panel, 1, wxEXPAND | wxALL, 0); + main_sizer->Add(m_tmp_panel, 1, wxEXPAND | wxALL, 0); #else - Tab *panel = this; - auto *sizer = new wxBoxSizer(wxVERTICAL); - sizer->SetSizeHints(panel); - panel->SetSizer(sizer); + Tab *panel = this; + auto *sizer = new wxBoxSizer(wxVERTICAL); + sizer->SetSizeHints(panel); + panel->SetSizer(sizer); #endif //__WXOSX__ - // preset chooser + // preset chooser m_presets_choice = new wxBitmapComboBox(panel, wxID_ANY, "", wxDefaultPosition, wxSize(35 * m_em_unit, -1), 0, 0, wxCB_READONLY); - auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); + auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); - //buttons + //buttons m_scaled_buttons.reserve(6); m_scaled_buttons.reserve(2); add_scaled_button(panel, &m_btn_save_preset, "save"); add_scaled_button(panel, &m_btn_delete_preset, "cross"); - m_show_incompatible_presets = false; - add_scaled_bitmap(this, m_bmp_show_incompatible_presets, "flag_red"); - add_scaled_bitmap(this, m_bmp_hide_incompatible_presets, "flag_green"); + m_show_incompatible_presets = false; + add_scaled_bitmap(this, m_bmp_show_incompatible_presets, "flag_red"); + add_scaled_bitmap(this, m_bmp_hide_incompatible_presets, "flag_green"); add_scaled_button(panel, &m_btn_hide_incompatible_presets, m_bmp_hide_incompatible_presets.name()); // TRN "Save current Settings" - m_btn_save_preset->SetToolTip(wxString::Format(_(L("Save current %s")),m_title)); - m_btn_delete_preset->SetToolTip(_(L("Delete this preset"))); - m_btn_delete_preset->Disable(); + m_btn_save_preset->SetToolTip(wxString::Format(_(L("Save current %s")),m_title)); + m_btn_delete_preset->SetToolTip(_(L("Delete this preset"))); + m_btn_delete_preset->Disable(); add_scaled_button(panel, &m_question_btn, "question"); - m_question_btn->SetToolTip(_(L("Hover the cursor over buttons to find more information \n" - "or click this button."))); + m_question_btn->SetToolTip(_(L("Hover the cursor over buttons to find more information \n" + "or click this button."))); - // Determine the theme color of OS (dark or light) + // Determine the theme color of OS (dark or light) auto luma = wxGetApp().get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - // Bitmaps to be shown on the "Revert to system" aka "Lock to system" button next to each input field. - add_scaled_bitmap(this, m_bmp_value_lock , luma >= 128 ? "lock_closed" : "lock_closed_white"); - add_scaled_bitmap(this, m_bmp_value_unlock, "lock_open"); - m_bmp_non_system = &m_bmp_white_bullet; - // Bitmaps to be shown on the "Undo user changes" button next to each input field. - add_scaled_bitmap(this, m_bmp_value_revert, "undo"); - add_scaled_bitmap(this, m_bmp_white_bullet, luma >= 128 ? "dot" : "dot_white"); + // Bitmaps to be shown on the "Revert to system" aka "Lock to system" button next to each input field. + add_scaled_bitmap(this, m_bmp_value_lock , luma >= 128 ? "lock_closed" : "lock_closed_white"); + add_scaled_bitmap(this, m_bmp_value_unlock, "lock_open"); + m_bmp_non_system = &m_bmp_white_bullet; + // Bitmaps to be shown on the "Undo user changes" button next to each input field. + add_scaled_bitmap(this, m_bmp_value_revert, "undo"); + add_scaled_bitmap(this, m_bmp_white_bullet, luma >= 128 ? "dot" : "dot_white"); - fill_icon_descriptions(); - set_tooltips_text(); + fill_icon_descriptions(); + set_tooltips_text(); add_scaled_button(panel, &m_undo_btn, m_bmp_white_bullet.name()); add_scaled_button(panel, &m_undo_to_sys_btn, m_bmp_white_bullet.name()); - m_undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) { on_roll_back_value(); })); - m_undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) { on_roll_back_value(true); })); - m_question_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) - { - auto dlg = new ButtonsDescription(this, m_icon_descriptions); - if (dlg->ShowModal() == wxID_OK) { - // Colors for ui "decoration" + m_undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) { on_roll_back_value(); })); + m_undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) { on_roll_back_value(true); })); + m_question_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) + { + ButtonsDescription dlg(this, m_icon_descriptions); + if (dlg.ShowModal() == wxID_OK) { + // Colors for ui "decoration" for (Tab *tab : wxGetApp().tabs_list) { tab->m_sys_label_clr = wxGetApp().get_label_clr_sys(); tab->m_modified_label_clr = wxGetApp().get_label_clr_modified(); - tab->update_labels_colour(); - } - } - })); + tab->update_labels_colour(); + } + } + })); - // Colors for ui "decoration" - m_sys_label_clr = wxGetApp().get_label_clr_sys(); - m_modified_label_clr = wxGetApp().get_label_clr_modified(); - m_default_text_clr = wxGetApp().get_label_clr_default(); + // Colors for ui "decoration" + m_sys_label_clr = wxGetApp().get_label_clr_sys(); + m_modified_label_clr = wxGetApp().get_label_clr_modified(); + m_default_text_clr = wxGetApp().get_label_clr_default(); // Sizer with buttons for mode changing m_mode_sizer = new ModeSizer(panel); const float scale_factor = /*wxGetApp().*/em_unit(this)*0.1;// GetContentScaleFactor(); - m_hsizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(m_hsizer, 0, wxEXPAND | wxBOTTOM, 3); - m_hsizer->Add(m_presets_choice, 0, wxLEFT | wxRIGHT | wxTOP | wxALIGN_CENTER_VERTICAL, 3); - m_hsizer->AddSpacer(int(4*scale_factor)); - m_hsizer->Add(m_btn_save_preset, 0, wxALIGN_CENTER_VERTICAL); + m_hsizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(m_hsizer, 0, wxEXPAND | wxBOTTOM, 3); + m_hsizer->Add(m_presets_choice, 0, wxLEFT | wxRIGHT | wxTOP | wxALIGN_CENTER_VERTICAL, 3); + m_hsizer->AddSpacer(int(4*scale_factor)); + m_hsizer->Add(m_btn_save_preset, 0, wxALIGN_CENTER_VERTICAL); m_hsizer->AddSpacer(int(4 * scale_factor)); - m_hsizer->Add(m_btn_delete_preset, 0, wxALIGN_CENTER_VERTICAL); + m_hsizer->Add(m_btn_delete_preset, 0, wxALIGN_CENTER_VERTICAL); m_hsizer->AddSpacer(int(16 * scale_factor)); - m_hsizer->Add(m_btn_hide_incompatible_presets, 0, wxALIGN_CENTER_VERTICAL); + m_hsizer->Add(m_btn_hide_incompatible_presets, 0, wxALIGN_CENTER_VERTICAL); m_hsizer->AddSpacer(int(64 * scale_factor)); - m_hsizer->Add(m_undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL); - m_hsizer->Add(m_undo_btn, 0, wxALIGN_CENTER_VERTICAL); + m_hsizer->Add(m_undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL); + m_hsizer->Add(m_undo_btn, 0, wxALIGN_CENTER_VERTICAL); m_hsizer->AddSpacer(int(32 * scale_factor)); - m_hsizer->Add(m_question_btn, 0, wxALIGN_CENTER_VERTICAL); + m_hsizer->Add(m_question_btn, 0, wxALIGN_CENTER_VERTICAL); // m_hsizer->AddStretchSpacer(32); - // StretchSpacer has a strange behavior under OSX, so + // StretchSpacer has a strange behavior under OSX, so // There is used just additional sizer for m_mode_sizer with right alignment auto mode_sizer = new wxBoxSizer(wxVERTICAL); mode_sizer->Add(m_mode_sizer, 1, wxALIGN_RIGHT); - m_hsizer->Add(mode_sizer, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, wxOSX ? 15 : 5); + m_hsizer->Add(mode_sizer, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, wxOSX ? 15 : 10); - //Horizontal sizer to hold the tree and the selected page. - m_hsizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(m_hsizer, 1, wxEXPAND, 0); + //Horizontal sizer to hold the tree and the selected page. + m_hsizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(m_hsizer, 1, wxEXPAND, 0); - //left vertical sizer - m_left_sizer = new wxBoxSizer(wxVERTICAL); - m_hsizer->Add(m_left_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 3); + //left vertical sizer + m_left_sizer = new wxBoxSizer(wxVERTICAL); + m_hsizer->Add(m_left_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 3); - // tree + // tree m_treectrl = new wxTreeCtrl(panel, wxID_ANY, wxDefaultPosition, wxSize(20 * m_em_unit, -1), - wxTR_NO_BUTTONS | wxTR_HIDE_ROOT | wxTR_SINGLE | wxTR_NO_LINES | wxBORDER_SUNKEN | wxWANTS_CHARS); - m_left_sizer->Add(m_treectrl, 1, wxEXPAND); + wxTR_NO_BUTTONS | wxTR_HIDE_ROOT | wxTR_SINGLE | wxTR_NO_LINES | wxBORDER_SUNKEN | wxWANTS_CHARS); + m_left_sizer->Add(m_treectrl, 1, wxEXPAND); const int img_sz = int(16 * scale_factor + 0.5f); m_icons = new wxImageList(img_sz, img_sz, true, 1); - // Index of the last icon inserted into $self->{icons}. - m_icon_count = -1; - m_treectrl->AssignImageList(m_icons); - m_treectrl->AddRoot("root"); - m_treectrl->SetIndent(0); - m_disable_tree_sel_changed_event = 0; + // Index of the last icon inserted into $self->{icons}. + m_icon_count = -1; + m_treectrl->AssignImageList(m_icons); + m_treectrl->AddRoot("root"); + m_treectrl->SetIndent(0); + m_disable_tree_sel_changed_event = 0; - m_treectrl->Bind(wxEVT_TREE_SEL_CHANGED, &Tab::OnTreeSelChange, this); - m_treectrl->Bind(wxEVT_KEY_DOWN, &Tab::OnKeyDown, this); + m_treectrl->Bind(wxEVT_TREE_SEL_CHANGED, &Tab::OnTreeSelChange, this); + m_treectrl->Bind(wxEVT_KEY_DOWN, &Tab::OnKeyDown, this); - m_presets_choice->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent e) { - //! Because of The MSW and GTK version of wxBitmapComboBox derived from wxComboBox, - //! but the OSX version derived from wxOwnerDrawnCombo, instead of: - //! select_preset(m_presets_choice->GetStringSelection().ToUTF8().data()); - //! we doing next: - int selected_item = m_presets_choice->GetSelection(); - if (m_selected_preset_item == selected_item && !m_presets->current_is_dirty()) - return; - if (selected_item >= 0) { - std::string selected_string = m_presets_choice->GetString(selected_item).ToUTF8().data(); - if (selected_string.find(PresetCollection::separator_head()) == 0 - /*selected_string == "------- System presets -------" || - selected_string == "------- User presets -------"*/) { - m_presets_choice->SetSelection(m_selected_preset_item); - if (wxString::FromUTF8(selected_string.c_str()) == PresetCollection::separator(L("Add a new printer"))) - wxTheApp->CallAfter([]() { Slic3r::GUI::config_wizard(Slic3r::GUI::ConfigWizard::RR_USER); }); - return; - } - m_selected_preset_item = selected_item; - select_preset(selected_string); - } - })); + m_presets_choice->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent e) { + //! Because of The MSW and GTK version of wxBitmapComboBox derived from wxComboBox, + //! but the OSX version derived from wxOwnerDrawnCombo, instead of: + //! select_preset(m_presets_choice->GetStringSelection().ToUTF8().data()); + //! we doing next: + int selected_item = m_presets_choice->GetSelection(); + if (m_selected_preset_item == selected_item && !m_presets->current_is_dirty()) + return; + if (selected_item >= 0) { + std::string selected_string = m_presets_choice->GetString(selected_item).ToUTF8().data(); + if (selected_string.find(PresetCollection::separator_head()) == 0 + /*selected_string == "------- System presets -------" || + selected_string == "------- User presets -------"*/) { + m_presets_choice->SetSelection(m_selected_preset_item); + if (wxString::FromUTF8(selected_string.c_str()) == PresetCollection::separator(L("Add a new printer"))) + wxTheApp->CallAfter([]() { Slic3r::GUI::config_wizard(Slic3r::GUI::ConfigWizard::RR_USER); }); + return; + } + m_selected_preset_item = selected_item; + select_preset(selected_string); + } + })); - m_btn_save_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { save_preset(); })); - m_btn_delete_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { delete_preset(); })); - m_btn_hide_incompatible_presets->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { - toggle_show_hide_incompatible(); - })); + m_btn_save_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { save_preset(); })); + m_btn_delete_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { delete_preset(); })); + m_btn_hide_incompatible_presets->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { + toggle_show_hide_incompatible(); + })); // Fill cache for mode bitmaps m_mode_bitmap_cache.reserve(3); @@ -259,24 +259,24 @@ void Tab::create_preset_tab() m_mode_bitmap_cache.push_back(ScalableBitmap(this, "mode_advanced_.png")); m_mode_bitmap_cache.push_back(ScalableBitmap(this, "mode_expert_.png")); - // Initialize the DynamicPrintConfig by default keys/values. - build(); - rebuild_page_tree(); + // Initialize the DynamicPrintConfig by default keys/values. + build(); + rebuild_page_tree(); m_complited = true; } -void Tab::add_scaled_button(wxWindow* parent, - ScalableButton** btn, - const std::string& icon_name, - const wxString& label/* = wxEmptyString*/, +void Tab::add_scaled_button(wxWindow* parent, + ScalableButton** btn, + const std::string& icon_name, + const wxString& label/* = wxEmptyString*/, long style /*= wxBU_EXACTFIT | wxNO_BORDER*/) { *btn = new ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, style); m_scaled_buttons.push_back(*btn); } -void Tab::add_scaled_bitmap(wxWindow* parent, - ScalableBitmap& bmp, +void Tab::add_scaled_bitmap(wxWindow* parent, + ScalableBitmap& bmp, const std::string& icon_name) { bmp = ScalableBitmap(parent, icon_name); @@ -285,233 +285,235 @@ void Tab::add_scaled_bitmap(wxWindow* parent, void Tab::load_initial_data() { - m_config = &m_presets->get_edited_preset().config; - bool has_parent = m_presets->get_selected_preset_parent() != nullptr; - m_bmp_non_system = has_parent ? &m_bmp_value_unlock : &m_bmp_white_bullet; - m_ttg_non_system = has_parent ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; - m_tt_non_system = has_parent ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; + m_config = &m_presets->get_edited_preset().config; + bool has_parent = m_presets->get_selected_preset_parent() != nullptr; + m_bmp_non_system = has_parent ? &m_bmp_value_unlock : &m_bmp_white_bullet; + m_ttg_non_system = has_parent ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; + m_tt_non_system = has_parent ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; } Slic3r::GUI::PageShp Tab::add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages /*= false*/) { - // Index of icon in an icon list $self->{icons}. - auto icon_idx = 0; - if (!icon.empty()) { - icon_idx = (m_icon_index.find(icon) == m_icon_index.end()) ? -1 : m_icon_index.at(icon); - if (icon_idx == -1) { - // Add a new icon to the icon list. + // Index of icon in an icon list $self->{icons}. + auto icon_idx = 0; + if (!icon.empty()) { + icon_idx = (m_icon_index.find(icon) == m_icon_index.end()) ? -1 : m_icon_index.at(icon); + if (icon_idx == -1) { + // Add a new icon to the icon list. m_scaled_icons_list.push_back(ScalableBitmap(this, icon)); m_icons->Add(m_scaled_icons_list.back().bmp()); icon_idx = ++m_icon_count; - m_icon_index[icon] = icon_idx; - } - } - // Initialize the page. + m_icon_index[icon] = icon_idx; + } + } + // Initialize the page. #ifdef __WXOSX__ - auto panel = m_tmp_panel; + auto panel = m_tmp_panel; #else - auto panel = this; + auto panel = this; #endif - PageShp page(new Page(panel, title, icon_idx, m_mode_bitmap_cache)); + PageShp page(new Page(panel, title, icon_idx, m_mode_bitmap_cache)); // page->SetBackgroundStyle(wxBG_STYLE_SYSTEM); #ifdef __WINDOWS__ // page->SetDoubleBuffered(true); #endif //__WINDOWS__ - page->SetScrollbars(1, 20, 1, 2); - page->Hide(); - m_hsizer->Add(page.get(), 1, wxEXPAND | wxLEFT, 5); + page->SetScrollbars(1, 20, 1, 2); + page->Hide(); + m_hsizer->Add(page.get(), 1, wxEXPAND | wxLEFT, 5); - if (!is_extruder_pages) - m_pages.push_back(page); + if (!is_extruder_pages) + m_pages.push_back(page); - page->set_config(m_config); - return page; + page->set_config(m_config); + return page; } void Tab::OnActivate() { -#ifdef __WXOSX__ - wxWindowUpdateLocker noUpdates(this); +#ifdef __WXOSX__ + wxWindowUpdateLocker noUpdates(this); - auto size = GetSizer()->GetSize(); - m_tmp_panel->GetSizer()->SetMinSize(size.x + m_size_move, size.y); - Fit(); - m_size_move *= -1; + auto size = GetSizer()->GetSize(); + m_tmp_panel->GetSizer()->SetMinSize(size.x + m_size_move, size.y); + Fit(); + m_size_move *= -1; #endif // __WXOSX__ } void Tab::update_labels_colour() { // Freeze(); - //update options "decoration" - for (const auto opt : m_options_list) - { - const wxColour *color = &m_sys_label_clr; + //update options "decoration" + for (const auto opt : m_options_list) + { + const wxColour *color = &m_sys_label_clr; - // value isn't equal to system value - if ((opt.second & osSystemValue) == 0) { - // value is equal to last saved - if ((opt.second & osInitValue) != 0) - color = &m_default_text_clr; - // value is modified - else - color = &m_modified_label_clr; - } - if (opt.first == "bed_shape" || opt.first == "compatible_prints" || opt.first == "compatible_printers") { - if (m_colored_Label != nullptr) { - m_colored_Label->SetForegroundColour(*color); - m_colored_Label->Refresh(true); - } - continue; - } + // value isn't equal to system value + if ((opt.second & osSystemValue) == 0) { + // value is equal to last saved + if ((opt.second & osInitValue) != 0) + color = &m_default_text_clr; + // value is modified + else + color = &m_modified_label_clr; + } + if (opt.first == "bed_shape" || opt.first == "compatible_prints" || opt.first == "compatible_printers") { + if (m_colored_Label != nullptr) { + m_colored_Label->SetForegroundColour(*color); + m_colored_Label->Refresh(true); + } + continue; + } - Field* field = get_field(opt.first); - if (field == nullptr) continue; - field->set_label_colour_force(color); - } + Field* field = get_field(opt.first); + if (field == nullptr) continue; + field->set_label_colour_force(color); + } // Thaw(); - auto cur_item = m_treectrl->GetFirstVisibleItem(); - while (cur_item) { - auto title = m_treectrl->GetItemText(cur_item); - for (auto page : m_pages) - { - if (page->title() != title) - continue; - - const wxColor *clr = !page->m_is_nonsys_values ? &m_sys_label_clr : - page->m_is_modified_values ? &m_modified_label_clr : - &m_default_text_clr; + auto cur_item = m_treectrl->GetFirstVisibleItem(); + if (!cur_item || !m_treectrl->IsVisible(cur_item)) + return; + while (cur_item) { + auto title = m_treectrl->GetItemText(cur_item); + for (auto page : m_pages) + { + if (page->title() != title) + continue; - m_treectrl->SetItemTextColour(cur_item, *clr); - break; - } - cur_item = m_treectrl->GetNextVisible(cur_item); - } + const wxColor *clr = !page->m_is_nonsys_values ? &m_sys_label_clr : + page->m_is_modified_values ? &m_modified_label_clr : + &m_default_text_clr; + + m_treectrl->SetItemTextColour(cur_item, *clr); + break; + } + cur_item = m_treectrl->GetNextVisible(cur_item); + } } // Update UI according to changes void Tab::update_changed_ui() { - if (m_postpone_update_ui) - return; + if (m_postpone_update_ui) + return; - const bool deep_compare = (m_type == Slic3r::Preset::TYPE_PRINTER || m_type == Slic3r::Preset::TYPE_SLA_MATERIAL); - auto dirty_options = m_presets->current_dirty_options(deep_compare); - auto nonsys_options = m_presets->current_different_from_parent_options(deep_compare); + const bool deep_compare = (m_type == Slic3r::Preset::TYPE_PRINTER || m_type == Slic3r::Preset::TYPE_SLA_MATERIAL); + auto dirty_options = m_presets->current_dirty_options(deep_compare); + auto nonsys_options = m_presets->current_different_from_parent_options(deep_compare); if (m_type == Slic3r::Preset::TYPE_PRINTER) { - TabPrinter* tab = static_cast(this); - if (tab->m_initial_extruders_count != tab->m_extruders_count) - dirty_options.emplace_back("extruders_count"); - if (tab->m_sys_extruders_count != tab->m_extruders_count) - nonsys_options.emplace_back("extruders_count"); - } + TabPrinter* tab = static_cast(this); + if (tab->m_initial_extruders_count != tab->m_extruders_count) + dirty_options.emplace_back("extruders_count"); + if (tab->m_sys_extruders_count != tab->m_extruders_count) + nonsys_options.emplace_back("extruders_count"); + } - for (auto& it : m_options_list) - it.second = m_opt_status_value; + for (auto& it : m_options_list) + it.second = m_opt_status_value; - for (auto opt_key : dirty_options) m_options_list[opt_key] &= ~osInitValue; - for (auto opt_key : nonsys_options) m_options_list[opt_key] &= ~osSystemValue; + for (auto opt_key : dirty_options) m_options_list[opt_key] &= ~osInitValue; + for (auto opt_key : nonsys_options) m_options_list[opt_key] &= ~osSystemValue; // Freeze(); - //update options "decoration" - for (const auto opt : m_options_list) - { - bool is_nonsys_value = false; - bool is_modified_value = true; - const ScalableBitmap *sys_icon = &m_bmp_value_lock; - const ScalableBitmap *icon = &m_bmp_value_revert; + //update options "decoration" + for (const auto opt : m_options_list) + { + bool is_nonsys_value = false; + bool is_modified_value = true; + const ScalableBitmap *sys_icon = &m_bmp_value_lock; + const ScalableBitmap *icon = &m_bmp_value_revert; - const wxColour *color = &m_sys_label_clr; + const wxColour *color = m_is_default_preset ? &m_default_text_clr : &m_sys_label_clr; - const wxString *sys_tt = &m_tt_value_lock; - const wxString *tt = &m_tt_value_revert; + const wxString *sys_tt = &m_tt_value_lock; + const wxString *tt = &m_tt_value_revert; - // value isn't equal to system value - if ((opt.second & osSystemValue) == 0) { - is_nonsys_value = true; - sys_icon = m_bmp_non_system; - sys_tt = m_tt_non_system; - // value is equal to last saved - if ((opt.second & osInitValue) != 0) - color = &m_default_text_clr; - // value is modified - else - color = &m_modified_label_clr; - } - if ((opt.second & osInitValue) != 0) - { - is_modified_value = false; - icon = &m_bmp_white_bullet; - tt = &m_tt_white_bullet; - } - if (opt.first == "bed_shape" || opt.first == "compatible_prints" || opt.first == "compatible_printers") { - if (m_colored_Label != nullptr) { - m_colored_Label->SetForegroundColour(*color); - m_colored_Label->Refresh(true); - } - continue; - } + // value isn't equal to system value + if ((opt.second & osSystemValue) == 0) { + is_nonsys_value = true; + sys_icon = m_bmp_non_system; + sys_tt = m_tt_non_system; + // value is equal to last saved + if ((opt.second & osInitValue) != 0) + color = &m_default_text_clr; + // value is modified + else + color = &m_modified_label_clr; + } + if ((opt.second & osInitValue) != 0) + { + is_modified_value = false; + icon = &m_bmp_white_bullet; + tt = &m_tt_white_bullet; + } + if (opt.first == "bed_shape" || opt.first == "compatible_prints" || opt.first == "compatible_printers") { + if (m_colored_Label != nullptr) { + m_colored_Label->SetForegroundColour(*color); + m_colored_Label->Refresh(true); + } + continue; + } - Field* field = get_field(opt.first); - if (field == nullptr) continue; - field->m_is_nonsys_value = is_nonsys_value; - field->m_is_modified_value = is_modified_value; - field->set_undo_bitmap(icon); - field->set_undo_to_sys_bitmap(sys_icon); - field->set_undo_tooltip(tt); - field->set_undo_to_sys_tooltip(sys_tt); - field->set_label_colour(color); - } + Field* field = get_field(opt.first); + if (field == nullptr) continue; + field->m_is_nonsys_value = is_nonsys_value; + field->m_is_modified_value = is_modified_value; + field->set_undo_bitmap(icon); + field->set_undo_to_sys_bitmap(sys_icon); + field->set_undo_tooltip(tt); + field->set_undo_to_sys_tooltip(sys_tt); + field->set_label_colour(color); + } // Thaw(); - wxTheApp->CallAfter([this]() { + wxTheApp->CallAfter([this]() { if (parent()) //To avoid a crash, parent should be exist for a moment of a tree updating - update_changed_tree_ui(); - }); + update_changed_tree_ui(); + }); } void Tab::init_options_list() { - if (!m_options_list.empty()) - m_options_list.clear(); + if (!m_options_list.empty()) + m_options_list.clear(); - for (const auto opt_key : m_config->keys()) - m_options_list.emplace(opt_key, m_opt_status_value); + for (const auto opt_key : m_config->keys()) + m_options_list.emplace(opt_key, m_opt_status_value); } template void add_correct_opts_to_options_list(const std::string &opt_key, std::map& map, Tab *tab, const int& value) { - T *opt_cur = static_cast(tab->m_config->option(opt_key)); - for (int i = 0; i < opt_cur->values.size(); i++) - map.emplace(opt_key + "#" + std::to_string(i), value); + T *opt_cur = static_cast(tab->m_config->option(opt_key)); + for (int i = 0; i < opt_cur->values.size(); i++) + map.emplace(opt_key + "#" + std::to_string(i), value); } void TabPrinter::init_options_list() { - if (!m_options_list.empty()) - m_options_list.clear(); + if (!m_options_list.empty()) + m_options_list.clear(); - for (const auto opt_key : m_config->keys()) - { - if (opt_key == "bed_shape") { - m_options_list.emplace(opt_key, m_opt_status_value); - continue; - } - switch (m_config->option(opt_key)->type()) - { - case coInts: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; - case coBools: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; - case coFloats: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; - case coStrings: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; - case coPercents:add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; - case coPoints: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; - default: m_options_list.emplace(opt_key, m_opt_status_value); break; - } - } - m_options_list.emplace("extruders_count", m_opt_status_value); + for (const auto opt_key : m_config->keys()) + { + if (opt_key == "bed_shape") { + m_options_list.emplace(opt_key, m_opt_status_value); + continue; + } + switch (m_config->option(opt_key)->type()) + { + case coInts: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coBools: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coFloats: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coStrings: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coPercents:add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coPoints: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + default: m_options_list.emplace(opt_key, m_opt_status_value); break; + } + } + m_options_list.emplace("extruders_count", m_opt_status_value); } void TabSLAMaterial::init_options_list() @@ -540,184 +542,184 @@ void TabSLAMaterial::init_options_list() void Tab::get_sys_and_mod_flags(const std::string& opt_key, bool& sys_page, bool& modified_page) { - auto opt = m_options_list.find(opt_key); - if (sys_page) sys_page = (opt->second & osSystemValue) != 0; - modified_page |= (opt->second & osInitValue) == 0; + auto opt = m_options_list.find(opt_key); + if (sys_page) sys_page = (opt->second & osSystemValue) != 0; + modified_page |= (opt->second & osInitValue) == 0; } void Tab::update_changed_tree_ui() { - if (m_options_list.empty()) + if (m_options_list.empty()) return; - auto cur_item = m_treectrl->GetFirstVisibleItem(); + auto cur_item = m_treectrl->GetFirstVisibleItem(); if (!cur_item || !m_treectrl->IsVisible(cur_item)) return; - auto selected_item = m_treectrl->GetSelection(); - auto selection = selected_item ? m_treectrl->GetItemText(selected_item) : ""; + auto selected_item = m_treectrl->GetSelection(); + auto selection = selected_item ? m_treectrl->GetItemText(selected_item) : ""; - while (cur_item) { - auto title = m_treectrl->GetItemText(cur_item); - for (auto page : m_pages) - { - if (page->title() != title) - continue; - bool sys_page = true; - bool modified_page = false; - if (title == _("General")) { - std::initializer_list optional_keys{ "extruders_count", "bed_shape" }; - for (auto &opt_key : optional_keys) { - get_sys_and_mod_flags(opt_key, sys_page, modified_page); - } - } - if (title == _("Dependencies")) { - if (m_type == Slic3r::Preset::TYPE_PRINTER) { - sys_page = m_presets->get_selected_preset_parent() != nullptr; - modified_page = false; - } else { - if (m_type == Slic3r::Preset::TYPE_FILAMENT || m_type == Slic3r::Preset::TYPE_SLA_MATERIAL) - get_sys_and_mod_flags("compatible_prints", sys_page, modified_page); - get_sys_and_mod_flags("compatible_printers", sys_page, modified_page); - } - } - for (auto group : page->m_optgroups) - { - if (!sys_page && modified_page) - break; - for (t_opt_map::iterator it = group->m_opt_map.begin(); it != group->m_opt_map.end(); ++it) { - const std::string& opt_key = it->first; - get_sys_and_mod_flags(opt_key, sys_page, modified_page); - } - } + while (cur_item) { + auto title = m_treectrl->GetItemText(cur_item); + for (auto page : m_pages) + { + if (page->title() != title) + continue; + bool sys_page = true; + bool modified_page = false; + if (title == _("General")) { + std::initializer_list optional_keys{ "extruders_count", "bed_shape" }; + for (auto &opt_key : optional_keys) { + get_sys_and_mod_flags(opt_key, sys_page, modified_page); + } + } + if (title == _("Dependencies")) { + if (m_type == Slic3r::Preset::TYPE_PRINTER) { + sys_page = m_presets->get_selected_preset_parent() != nullptr; + modified_page = false; + } else { + if (m_type == Slic3r::Preset::TYPE_FILAMENT || m_type == Slic3r::Preset::TYPE_SLA_MATERIAL) + get_sys_and_mod_flags("compatible_prints", sys_page, modified_page); + get_sys_and_mod_flags("compatible_printers", sys_page, modified_page); + } + } + for (auto group : page->m_optgroups) + { + if (!sys_page && modified_page) + break; + for (t_opt_map::iterator it = group->m_opt_map.begin(); it != group->m_opt_map.end(); ++it) { + const std::string& opt_key = it->first; + get_sys_and_mod_flags(opt_key, sys_page, modified_page); + } + } - const wxColor *clr = sys_page ? &m_sys_label_clr : - modified_page ? &m_modified_label_clr : - &m_default_text_clr; + const wxColor *clr = sys_page ? (m_is_default_preset ? &m_default_text_clr : &m_sys_label_clr) : + modified_page ? &m_modified_label_clr : + &m_default_text_clr; - if (page->set_item_colour(clr)) - m_treectrl->SetItemTextColour(cur_item, *clr); + if (page->set_item_colour(clr)) + m_treectrl->SetItemTextColour(cur_item, *clr); - page->m_is_nonsys_values = !sys_page; - page->m_is_modified_values = modified_page; + page->m_is_nonsys_values = !sys_page; + page->m_is_modified_values = modified_page; - if (selection == title) { - m_is_nonsys_values = page->m_is_nonsys_values; - m_is_modified_values = page->m_is_modified_values; - } - break; - } + if (selection == title) { + m_is_nonsys_values = page->m_is_nonsys_values; + m_is_modified_values = page->m_is_modified_values; + } + break; + } auto next_item = m_treectrl->GetNextVisible(cur_item); cur_item = next_item; - } - update_undo_buttons(); + } + update_undo_buttons(); } void Tab::update_undo_buttons() { - m_undo_btn-> SetBitmap_(m_is_modified_values ? m_bmp_value_revert: m_bmp_white_bullet); - m_undo_to_sys_btn-> SetBitmap_(m_is_nonsys_values ? *m_bmp_non_system : m_bmp_value_lock); + m_undo_btn-> SetBitmap_(m_is_modified_values ? m_bmp_value_revert: m_bmp_white_bullet); + m_undo_to_sys_btn-> SetBitmap_(m_is_nonsys_values ? *m_bmp_non_system : m_bmp_value_lock); - m_undo_btn->SetToolTip(m_is_modified_values ? m_ttg_value_revert : m_ttg_white_bullet); - m_undo_to_sys_btn->SetToolTip(m_is_nonsys_values ? *m_ttg_non_system : m_ttg_value_lock); + m_undo_btn->SetToolTip(m_is_modified_values ? m_ttg_value_revert : m_ttg_white_bullet); + m_undo_to_sys_btn->SetToolTip(m_is_nonsys_values ? *m_ttg_non_system : m_ttg_value_lock); } void Tab::on_roll_back_value(const bool to_sys /*= true*/) { - int os; - if (to_sys) { - if (!m_is_nonsys_values) return; - os = osSystemValue; - } - else { - if (!m_is_modified_values) return; - os = osInitValue; - } + int os; + if (to_sys) { + if (!m_is_nonsys_values) return; + os = osSystemValue; + } + else { + if (!m_is_modified_values) return; + os = osInitValue; + } - m_postpone_update_ui = true; + m_postpone_update_ui = true; - auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); - for (auto page : m_pages) - if (page->title() == selection) { - for (auto group : page->m_optgroups) { - if (group->title == _("Capabilities")) { - if ((m_options_list["extruders_count"] & os) == 0) - to_sys ? group->back_to_sys_value("extruders_count") : group->back_to_initial_value("extruders_count"); - } - if (group->title == _("Size and coordinates")) { - if ((m_options_list["bed_shape"] & os) == 0) { - to_sys ? group->back_to_sys_value("bed_shape") : group->back_to_initial_value("bed_shape"); - load_key_value("bed_shape", true/*some value*/, true); - } + auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); + for (auto page : m_pages) + if (page->title() == selection) { + for (auto group : page->m_optgroups) { + if (group->title == _("Capabilities")) { + if ((m_options_list["extruders_count"] & os) == 0) + to_sys ? group->back_to_sys_value("extruders_count") : group->back_to_initial_value("extruders_count"); + } + if (group->title == _("Size and coordinates")) { + if ((m_options_list["bed_shape"] & os) == 0) { + to_sys ? group->back_to_sys_value("bed_shape") : group->back_to_initial_value("bed_shape"); + load_key_value("bed_shape", true/*some value*/, true); + } - } - if (group->title == _("Profile dependencies")) { - if (m_type != Slic3r::Preset::TYPE_PRINTER && (m_options_list["compatible_printers"] & os) == 0) { - to_sys ? group->back_to_sys_value("compatible_printers") : group->back_to_initial_value("compatible_printers"); - load_key_value("compatible_printers", true/*some value*/, true); + } + if (group->title == _("Profile dependencies")) { + if (m_type != Slic3r::Preset::TYPE_PRINTER && (m_options_list["compatible_printers"] & os) == 0) { + to_sys ? group->back_to_sys_value("compatible_printers") : group->back_to_initial_value("compatible_printers"); + load_key_value("compatible_printers", true/*some value*/, true); - bool is_empty = m_config->option("compatible_printers")->values.empty(); - m_compatible_printers.checkbox->SetValue(is_empty); - is_empty ? m_compatible_printers.btn->Disable() : m_compatible_printers.btn->Enable(); - } - if ((m_type == Slic3r::Preset::TYPE_PRINT || m_type == Slic3r::Preset::TYPE_SLA_PRINT) && (m_options_list["compatible_prints"] & os) == 0) { - to_sys ? group->back_to_sys_value("compatible_prints") : group->back_to_initial_value("compatible_prints"); - load_key_value("compatible_prints", true/*some value*/, true); + bool is_empty = m_config->option("compatible_printers")->values.empty(); + m_compatible_printers.checkbox->SetValue(is_empty); + is_empty ? m_compatible_printers.btn->Disable() : m_compatible_printers.btn->Enable(); + } + if ((m_type == Slic3r::Preset::TYPE_PRINT || m_type == Slic3r::Preset::TYPE_SLA_PRINT) && (m_options_list["compatible_prints"] & os) == 0) { + to_sys ? group->back_to_sys_value("compatible_prints") : group->back_to_initial_value("compatible_prints"); + load_key_value("compatible_prints", true/*some value*/, true); - bool is_empty = m_config->option("compatible_prints")->values.empty(); - m_compatible_prints.checkbox->SetValue(is_empty); - is_empty ? m_compatible_prints.btn->Disable() : m_compatible_prints.btn->Enable(); - } - } - for (auto kvp : group->m_opt_map) { - const std::string& opt_key = kvp.first; - if ((m_options_list[opt_key] & os) == 0) - to_sys ? group->back_to_sys_value(opt_key) : group->back_to_initial_value(opt_key); - } - } - break; - } + bool is_empty = m_config->option("compatible_prints")->values.empty(); + m_compatible_prints.checkbox->SetValue(is_empty); + is_empty ? m_compatible_prints.btn->Disable() : m_compatible_prints.btn->Enable(); + } + } + for (auto kvp : group->m_opt_map) { + const std::string& opt_key = kvp.first; + if ((m_options_list[opt_key] & os) == 0) + to_sys ? group->back_to_sys_value(opt_key) : group->back_to_initial_value(opt_key); + } + } + break; + } - m_postpone_update_ui = false; - update_changed_ui(); + m_postpone_update_ui = false; + update_changed_ui(); } // Update the combo box label of the selected preset based on its "dirty" state, // comparing the selected preset config with $self->{config}. void Tab::update_dirty() { - m_presets->update_dirty_ui(m_presets_choice); - on_presets_changed(); - update_changed_ui(); + m_presets->update_dirty_ui(m_presets_choice); + on_presets_changed(); + update_changed_ui(); } void Tab::update_tab_ui() { - m_selected_preset_item = m_presets->update_tab_ui(m_presets_choice, m_show_incompatible_presets, m_em_unit); + m_selected_preset_item = m_presets->update_tab_ui(m_presets_choice, m_show_incompatible_presets, m_em_unit); } // Load a provied DynamicConfig into the tab, modifying the active preset. // This could be used for example by setting a Wipe Tower position by interactive manipulation in the 3D view. void Tab::load_config(const DynamicPrintConfig& config) { - bool modified = 0; - for(auto opt_key : m_config->diff(config)) { - m_config->set_key_value(opt_key, config.option(opt_key)->clone()); - modified = 1; - } - if (modified) { - update_dirty(); - //# Initialize UI components with the config values. - reload_config(); - update(); - } + bool modified = 0; + for(auto opt_key : m_config->diff(config)) { + m_config->set_key_value(opt_key, config.option(opt_key)->clone()); + modified = 1; + } + if (modified) { + update_dirty(); + //# Initialize UI components with the config values. + reload_config(); + update(); + } } // Reload current $self->{config} (aka $self->{presets}->edited_preset->config) into the UI fields. void Tab::reload_config() { // Freeze(); - for (auto page : m_pages) - page->reload_config(); + for (auto page : m_pages) + page->reload_config(); // Thaw(); } @@ -729,20 +731,20 @@ void Tab::update_mode() m_mode_sizer->SetMode(m_mode); update_visibility(); + + update_changed_tree_ui(); } void Tab::update_visibility() { Freeze(); // There is needed Freeze/Thaw to avoid a flashing after Show/Layout - for (auto page : m_pages) + for (auto page : m_pages) page->update_visibility(m_mode); update_page_tree_visibility(); Layout(); - Thaw(); - - update_changed_tree_ui(); + Thaw(); } void Tab::msw_rescale() @@ -783,25 +785,25 @@ void Tab::msw_rescale() Field* Tab::get_field(const t_config_option_key& opt_key, int opt_index/* = -1*/) const { - Field* field = nullptr; - for (auto page : m_pages) { - field = page->get_field(opt_key, opt_index); - if (field != nullptr) - return field; - } - return field; + Field* field = nullptr; + for (auto page : m_pages) { + field = page->get_field(opt_key, opt_index); + if (field != nullptr) + return field; + } + return field; } // Set a key/value pair on this page. Return true if the value has been modified. // Currently used for distributing extruders_count over preset pages of Slic3r::GUI::Tab::Printer // after a preset is loaded. bool Tab::set_value(const t_config_option_key& opt_key, const boost::any& value) { - bool changed = false; - for(auto page: m_pages) { - if (page->set_value(opt_key, value)) - changed = true; - } - return changed; + bool changed = false; + for(auto page: m_pages) { + if (page->set_value(opt_key, value)) + changed = true; + } + return changed; } // To be called by custom widgets, load a value into a config, @@ -810,54 +812,62 @@ bool Tab::set_value(const t_config_option_key& opt_key, const boost::any& value) // and value can be some random value because in this case it will not been used void Tab::load_key_value(const std::string& opt_key, const boost::any& value, bool saved_value /*= false*/) { - if (!saved_value) change_opt_value(*m_config, opt_key, value); - // Mark the print & filament enabled if they are compatible with the currently selected preset. - if (opt_key == "compatible_printers" || opt_key == "compatible_prints") { - // Don't select another profile if this profile happens to become incompatible. - m_preset_bundle->update_compatible(false); - } - m_presets->update_dirty_ui(m_presets_choice); - on_presets_changed(); - update(); + if (!saved_value) change_opt_value(*m_config, opt_key, value); + // Mark the print & filament enabled if they are compatible with the currently selected preset. + if (opt_key == "compatible_printers" || opt_key == "compatible_prints") { + // Don't select another profile if this profile happens to become incompatible. + m_preset_bundle->update_compatible(false); + } + m_presets->update_dirty_ui(m_presets_choice); + on_presets_changed(); + update(); } static wxString support_combo_value_for_config(const DynamicPrintConfig &config, bool is_fff) { const std::string support = is_fff ? "support_material" : "supports_enable"; const std::string buildplate_only = is_fff ? "support_material_buildplate_only" : "support_buildplate_only"; - return - ! config.opt_bool(support) ? - _("None") : - (is_fff && !config.opt_bool("support_material_auto")) ? - _("For support enforcers only") : + return + ! config.opt_bool(support) ? + _("None") : + (is_fff && !config.opt_bool("support_material_auto")) ? + _("For support enforcers only") : (config.opt_bool(buildplate_only) ? _("Support on build plate only") : - _("Everywhere")); + _("Everywhere")); +} + +static wxString pad_combo_value_for_config(const DynamicPrintConfig &config) +{ + return config.opt_bool("pad_enable") ? (config.opt_bool("pad_zero_elevation") ? _("Around object") : _("Below object")) : _("None"); } void Tab::on_value_change(const std::string& opt_key, const boost::any& value) { - if (wxGetApp().plater() == nullptr) { - return; - } + if (wxGetApp().plater() == nullptr) { + return; + } const bool is_fff = supports_printer_technology(ptFFF); - ConfigOptionsGroup* og_freq_chng_params = wxGetApp().sidebar().og_freq_chng_params(is_fff); + ConfigOptionsGroup* og_freq_chng_params = wxGetApp().sidebar().og_freq_chng_params(is_fff); if (opt_key == "fill_density" || opt_key == "pad_enable") - { + { boost::any val = og_freq_chng_params->get_config_value(*m_config, opt_key); og_freq_chng_params->set_value(opt_key, val); - } + } - if (is_fff ? - (opt_key == "support_material" || opt_key == "support_material_auto" || opt_key == "support_material_buildplate_only") : - (opt_key == "supports_enable" || opt_key == "support_buildplate_only")) - og_freq_chng_params->set_value("support", support_combo_value_for_config(*m_config, is_fff)); + if (is_fff ? + (opt_key == "support_material" || opt_key == "support_material_auto" || opt_key == "support_material_buildplate_only") : + (opt_key == "supports_enable" || opt_key == "support_buildplate_only")) + og_freq_chng_params->set_value("support", support_combo_value_for_config(*m_config, is_fff)); - if (opt_key == "brim_width") - { - bool val = m_config->opt_float("brim_width") > 0.0 ? true : false; + if (! is_fff && (opt_key == "pad_enable" || opt_key == "pad_zero_elevation")) + og_freq_chng_params->set_value("pad", pad_combo_value_for_config(*m_config)); + + if (opt_key == "brim_width") + { + bool val = m_config->opt_float("brim_width") > 0.0 ? true : false; og_freq_chng_params->set_value("brim", val); - } + } if (opt_key == "wipe_tower" || opt_key == "single_extruder_multi_material" || opt_key == "extruders_count" ) update_wiping_button_visibility(); @@ -865,7 +875,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) if (opt_key == "extruders_count") wxGetApp().plater()->on_extruders_change(boost::any_cast(value)); - update(); + update(); } // Show/hide the 'purging volumes' button @@ -874,11 +884,10 @@ void Tab::update_wiping_button_visibility() { return; // ys_FIXME bool wipe_tower_enabled = dynamic_cast( (m_preset_bundle->prints.get_edited_preset().config ).option("wipe_tower"))->value; bool multiple_extruders = dynamic_cast((m_preset_bundle->printers.get_edited_preset().config).option("nozzle_diameter"))->values.size() > 1; - bool single_extruder_mm = dynamic_cast( (m_preset_bundle->printers.get_edited_preset().config).option("single_extruder_multi_material"))->value; auto wiping_dialog_button = wxGetApp().sidebar().get_wiping_dialog_button(); if (wiping_dialog_button) { - wiping_dialog_button->Show(wipe_tower_enabled && multiple_extruders && single_extruder_mm); + wiping_dialog_button->Show(wipe_tower_enabled && multiple_extruders); wiping_dialog_button->GetParent()->Layout(); } } @@ -891,13 +900,13 @@ void Tab::update_wiping_button_visibility() { // to update number of "filament" selection boxes when the number of extruders change. void Tab::on_presets_changed() { - if (wxGetApp().plater() == nullptr) { - return; - } + if (wxGetApp().plater() == nullptr) { + return; + } // Instead of PostEvent (EVT_TAB_PRESETS_CHANGED) just call update_presets wxGetApp().plater()->sidebar().update_presets(m_type); - update_preset_description_line(); + update_preset_description_line(); // Printer selected at the Printer tab, update "compatible" marks at the print and filament selectors. for (auto t: m_dependent_tabs) @@ -913,75 +922,83 @@ void Tab::on_presets_changed() void Tab::update_preset_description_line() { - const Preset* parent = m_presets->get_selected_preset_parent(); - const Preset& preset = m_presets->get_edited_preset(); - - wxString description_line = preset.is_default ? - _(L("It's a default preset.")) : preset.is_system ? - _(L("It's a system preset.")) : - wxString::Format(_(L("Current preset is inherited from %s")), (parent == nullptr ? - _(L("default preset"))+"." : - ":\n\t" + parent->name)); - - if (preset.is_default || preset.is_system) - description_line += "\n\t" + _(L("It can't be deleted or modified.")) + - "\n\t" + _(L("Any modifications should be saved as a new preset inherited from this one.")) + - "\n\t" + _(L("To do that please specify a new name for the preset.")); - - if (parent && parent->vendor) - { - description_line += "\n\n" + _(L("Additional information:")) + "\n"; - description_line += "\t" + _(L("vendor")) + ": " + (m_type == Slic3r::Preset::TYPE_PRINTER ? "\n\t\t" : "") + parent->vendor->name + - ", ver: " + parent->vendor->config_version.to_string(); - if (m_type == Slic3r::Preset::TYPE_PRINTER) { - const std::string &printer_model = preset.config.opt_string("printer_model"); - if (! printer_model.empty()) - description_line += "\n\n\t" + _(L("printer model")) + ": \n\t\t" + printer_model; - switch (preset.printer_technology()) { - case ptFFF: - { - //FIXME add prefered_sla_material_profile for SLA - const std::string &default_print_profile = preset.config.opt_string("default_print_profile"); - const std::vector &default_filament_profiles = preset.config.option("default_filament_profile")->values; - if (!default_print_profile.empty()) - description_line += "\n\n\t" + _(L("default print profile")) + ": \n\t\t" + default_print_profile; - if (!default_filament_profiles.empty()) - { - description_line += "\n\n\t" + _(L("default filament profile")) + ": \n\t\t"; - for (auto& profile : default_filament_profiles) { - if (&profile != &*default_filament_profiles.begin()) - description_line += ", "; - description_line += profile; - } - } - break; - } - case ptSLA: - { - //FIXME add prefered_sla_material_profile for SLA - const std::string &default_sla_material_profile = preset.config.opt_string("default_sla_material_profile"); - if (!default_sla_material_profile.empty()) - description_line += "\n\n\t" + _(L("default SLA material profile")) + ": \n\t\t" + default_sla_material_profile; + const Preset* parent = m_presets->get_selected_preset_parent(); + const Preset& preset = m_presets->get_edited_preset(); - const std::string &default_sla_print_profile = preset.config.opt_string("default_sla_print_profile"); - if (!default_sla_print_profile.empty()) - description_line += "\n\n\t" + _(L("default SLA print profile")) + ": \n\t\t" + default_sla_print_profile; - break; - } - } - } - } + wxString description_line; - m_parent_preset_description_line->SetText(description_line, false); + if (preset.is_default) { + description_line = _(L("This is a default preset.")); + } else if (preset.is_system) { + description_line = _(L("This is a system preset.")); + } else if (parent == nullptr) { + description_line = _(L("Current preset is inherited from the default preset.")); + } else { + description_line = wxString::Format( + _(L("Current preset is inherited from:\n\t%s")), GUI::from_u8(parent->name)); + } + + if (preset.is_default || preset.is_system) + description_line += "\n\t" + _(L("It can't be deleted or modified.")) + + "\n\t" + _(L("Any modifications should be saved as a new preset inherited from this one.")) + + "\n\t" + _(L("To do that please specify a new name for the preset.")); + + if (parent && parent->vendor) + { + description_line += "\n\n" + _(L("Additional information:")) + "\n"; + description_line += "\t" + _(L("vendor")) + ": " + (m_type == Slic3r::Preset::TYPE_PRINTER ? "\n\t\t" : "") + parent->vendor->name + + ", ver: " + parent->vendor->config_version.to_string(); + if (m_type == Slic3r::Preset::TYPE_PRINTER) { + const std::string &printer_model = preset.config.opt_string("printer_model"); + if (! printer_model.empty()) + description_line += "\n\n\t" + _(L("printer model")) + ": \n\t\t" + printer_model; + switch (preset.printer_technology()) { + case ptFFF: + { + //FIXME add prefered_sla_material_profile for SLA + const std::string &default_print_profile = preset.config.opt_string("default_print_profile"); + const std::vector &default_filament_profiles = preset.config.option("default_filament_profile")->values; + if (!default_print_profile.empty()) + description_line += "\n\n\t" + _(L("default print profile")) + ": \n\t\t" + default_print_profile; + if (!default_filament_profiles.empty()) + { + description_line += "\n\n\t" + _(L("default filament profile")) + ": \n\t\t"; + for (auto& profile : default_filament_profiles) { + if (&profile != &*default_filament_profiles.begin()) + description_line += ", "; + description_line += profile; + } + } + break; + } + case ptSLA: + { + //FIXME add prefered_sla_material_profile for SLA + const std::string &default_sla_material_profile = preset.config.opt_string("default_sla_material_profile"); + if (!default_sla_material_profile.empty()) + description_line += "\n\n\t" + _(L("default SLA material profile")) + ": \n\t\t" + default_sla_material_profile; + + const std::string &default_sla_print_profile = preset.config.opt_string("default_sla_print_profile"); + if (!default_sla_print_profile.empty()) + description_line += "\n\n\t" + _(L("default SLA print profile")) + ": \n\t\t" + default_sla_print_profile; + break; + } + } + } + } + + m_parent_preset_description_line->SetText(description_line, false); } void Tab::update_frequently_changed_parameters() { - const bool is_fff = supports_printer_technology(ptFFF); - auto og_freq_chng_params = wxGetApp().sidebar().og_freq_chng_params(is_fff); + const bool is_fff = supports_printer_technology(ptFFF); + auto og_freq_chng_params = wxGetApp().sidebar().og_freq_chng_params(is_fff); if (!og_freq_chng_params) return; - og_freq_chng_params->set_value("support", support_combo_value_for_config(*m_config, is_fff)); + og_freq_chng_params->set_value("support", support_combo_value_for_config(*m_config, is_fff)); + if (! is_fff) + og_freq_chng_params->set_value("pad", pad_combo_value_for_config(*m_config)); const std::string updated_value_key = is_fff ? "fill_density" : "pad_enable"; @@ -997,236 +1014,236 @@ void Tab::update_frequently_changed_parameters() void TabPrint::build() { - m_presets = &m_preset_bundle->prints; - load_initial_data(); + m_presets = &m_preset_bundle->prints; + load_initial_data(); - auto page = add_options_page(_(L("Layers and perimeters")), "layers"); - auto optgroup = page->new_optgroup(_(L("Layer height"))); - optgroup->append_single_option_line("layer_height"); - optgroup->append_single_option_line("first_layer_height"); + auto page = add_options_page(_(L("Layers and perimeters")), "layers"); + auto optgroup = page->new_optgroup(_(L("Layer height"))); + optgroup->append_single_option_line("layer_height"); + optgroup->append_single_option_line("first_layer_height"); - optgroup = page->new_optgroup(_(L("Vertical shells"))); - optgroup->append_single_option_line("perimeters"); - optgroup->append_single_option_line("spiral_vase"); + optgroup = page->new_optgroup(_(L("Vertical shells"))); + optgroup->append_single_option_line("perimeters"); + optgroup->append_single_option_line("spiral_vase"); - Line line { "", "" }; - line.full_width = 1; - line.widget = [this](wxWindow* parent) { - return description_line_widget(parent, &m_recommended_thin_wall_thickness_description_line); - }; - optgroup->append_line(line); + Line line { "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_recommended_thin_wall_thickness_description_line); + }; + optgroup->append_line(line); - optgroup = page->new_optgroup(_(L("Horizontal shells"))); - line = { _(L("Solid layers")), "" }; - line.append_option(optgroup->get_option("top_solid_layers")); - line.append_option(optgroup->get_option("bottom_solid_layers")); - optgroup->append_line(line); + optgroup = page->new_optgroup(_(L("Horizontal shells"))); + line = { _(L("Solid layers")), "" }; + line.append_option(optgroup->get_option("top_solid_layers")); + line.append_option(optgroup->get_option("bottom_solid_layers")); + optgroup->append_line(line); - optgroup = page->new_optgroup(_(L("Quality (slower slicing)"))); - optgroup->append_single_option_line("extra_perimeters"); - optgroup->append_single_option_line("ensure_vertical_shell_thickness"); - optgroup->append_single_option_line("avoid_crossing_perimeters"); - optgroup->append_single_option_line("thin_walls"); - optgroup->append_single_option_line("overhangs"); + optgroup = page->new_optgroup(_(L("Quality (slower slicing)"))); + optgroup->append_single_option_line("extra_perimeters"); + optgroup->append_single_option_line("ensure_vertical_shell_thickness"); + optgroup->append_single_option_line("avoid_crossing_perimeters"); + optgroup->append_single_option_line("thin_walls"); + optgroup->append_single_option_line("overhangs"); - optgroup = page->new_optgroup(_(L("Advanced"))); - optgroup->append_single_option_line("seam_position"); - optgroup->append_single_option_line("external_perimeters_first"); + optgroup = page->new_optgroup(_(L("Advanced"))); + optgroup->append_single_option_line("seam_position"); + optgroup->append_single_option_line("external_perimeters_first"); - page = add_options_page(_(L("Infill")), "infill"); - optgroup = page->new_optgroup(_(L("Infill"))); - optgroup->append_single_option_line("fill_density"); - optgroup->append_single_option_line("fill_pattern"); - optgroup->append_single_option_line("top_fill_pattern"); - optgroup->append_single_option_line("bottom_fill_pattern"); + page = add_options_page(_(L("Infill")), "infill"); + optgroup = page->new_optgroup(_(L("Infill"))); + optgroup->append_single_option_line("fill_density"); + optgroup->append_single_option_line("fill_pattern"); + optgroup->append_single_option_line("top_fill_pattern"); + optgroup->append_single_option_line("bottom_fill_pattern"); - optgroup = page->new_optgroup(_(L("Reducing printing time"))); - optgroup->append_single_option_line("infill_every_layers"); - optgroup->append_single_option_line("infill_only_where_needed"); + optgroup = page->new_optgroup(_(L("Reducing printing time"))); + optgroup->append_single_option_line("infill_every_layers"); + optgroup->append_single_option_line("infill_only_where_needed"); - optgroup = page->new_optgroup(_(L("Advanced"))); - optgroup->append_single_option_line("solid_infill_every_layers"); - optgroup->append_single_option_line("fill_angle"); - optgroup->append_single_option_line("solid_infill_below_area"); - optgroup->append_single_option_line("bridge_angle"); - optgroup->append_single_option_line("only_retract_when_crossing_perimeters"); - optgroup->append_single_option_line("infill_first"); + optgroup = page->new_optgroup(_(L("Advanced"))); + optgroup->append_single_option_line("solid_infill_every_layers"); + optgroup->append_single_option_line("fill_angle"); + optgroup->append_single_option_line("solid_infill_below_area"); + optgroup->append_single_option_line("bridge_angle"); + optgroup->append_single_option_line("only_retract_when_crossing_perimeters"); + optgroup->append_single_option_line("infill_first"); - page = add_options_page(_(L("Skirt and brim")), "skirt+brim"); - optgroup = page->new_optgroup(_(L("Skirt"))); - optgroup->append_single_option_line("skirts"); - optgroup->append_single_option_line("skirt_distance"); - optgroup->append_single_option_line("skirt_height"); - optgroup->append_single_option_line("min_skirt_length"); + page = add_options_page(_(L("Skirt and brim")), "skirt+brim"); + optgroup = page->new_optgroup(_(L("Skirt"))); + optgroup->append_single_option_line("skirts"); + optgroup->append_single_option_line("skirt_distance"); + optgroup->append_single_option_line("skirt_height"); + optgroup->append_single_option_line("min_skirt_length"); - optgroup = page->new_optgroup(_(L("Brim"))); - optgroup->append_single_option_line("brim_width"); + optgroup = page->new_optgroup(_(L("Brim"))); + optgroup->append_single_option_line("brim_width"); - page = add_options_page(_(L("Support material")), "support"); - optgroup = page->new_optgroup(_(L("Support material"))); - optgroup->append_single_option_line("support_material"); - optgroup->append_single_option_line("support_material_auto"); - optgroup->append_single_option_line("support_material_threshold"); - optgroup->append_single_option_line("support_material_enforce_layers"); + page = add_options_page(_(L("Support material")), "support"); + optgroup = page->new_optgroup(_(L("Support material"))); + optgroup->append_single_option_line("support_material"); + optgroup->append_single_option_line("support_material_auto"); + optgroup->append_single_option_line("support_material_threshold"); + optgroup->append_single_option_line("support_material_enforce_layers"); - optgroup = page->new_optgroup(_(L("Raft"))); - optgroup->append_single_option_line("raft_layers"); + optgroup = page->new_optgroup(_(L("Raft"))); + optgroup->append_single_option_line("raft_layers"); // # optgroup->append_single_option_line(get_option_("raft_contact_distance"); - optgroup = page->new_optgroup(_(L("Options for support material and raft"))); - optgroup->append_single_option_line("support_material_contact_distance"); - optgroup->append_single_option_line("support_material_pattern"); - optgroup->append_single_option_line("support_material_with_sheath"); - optgroup->append_single_option_line("support_material_spacing"); - optgroup->append_single_option_line("support_material_angle"); - optgroup->append_single_option_line("support_material_interface_layers"); - optgroup->append_single_option_line("support_material_interface_spacing"); - optgroup->append_single_option_line("support_material_interface_contact_loops"); - optgroup->append_single_option_line("support_material_buildplate_only"); - optgroup->append_single_option_line("support_material_xy_spacing"); - optgroup->append_single_option_line("dont_support_bridges"); - optgroup->append_single_option_line("support_material_synchronize_layers"); + optgroup = page->new_optgroup(_(L("Options for support material and raft"))); + optgroup->append_single_option_line("support_material_contact_distance"); + optgroup->append_single_option_line("support_material_pattern"); + optgroup->append_single_option_line("support_material_with_sheath"); + optgroup->append_single_option_line("support_material_spacing"); + optgroup->append_single_option_line("support_material_angle"); + optgroup->append_single_option_line("support_material_interface_layers"); + optgroup->append_single_option_line("support_material_interface_spacing"); + optgroup->append_single_option_line("support_material_interface_contact_loops"); + optgroup->append_single_option_line("support_material_buildplate_only"); + optgroup->append_single_option_line("support_material_xy_spacing"); + optgroup->append_single_option_line("dont_support_bridges"); + optgroup->append_single_option_line("support_material_synchronize_layers"); - page = add_options_page(_(L("Speed")), "time"); - optgroup = page->new_optgroup(_(L("Speed for print moves"))); - optgroup->append_single_option_line("perimeter_speed"); - optgroup->append_single_option_line("small_perimeter_speed"); - optgroup->append_single_option_line("external_perimeter_speed"); - optgroup->append_single_option_line("infill_speed"); - optgroup->append_single_option_line("solid_infill_speed"); - optgroup->append_single_option_line("top_solid_infill_speed"); - optgroup->append_single_option_line("support_material_speed"); - optgroup->append_single_option_line("support_material_interface_speed"); - optgroup->append_single_option_line("bridge_speed"); - optgroup->append_single_option_line("gap_fill_speed"); + page = add_options_page(_(L("Speed")), "time"); + optgroup = page->new_optgroup(_(L("Speed for print moves"))); + optgroup->append_single_option_line("perimeter_speed"); + optgroup->append_single_option_line("small_perimeter_speed"); + optgroup->append_single_option_line("external_perimeter_speed"); + optgroup->append_single_option_line("infill_speed"); + optgroup->append_single_option_line("solid_infill_speed"); + optgroup->append_single_option_line("top_solid_infill_speed"); + optgroup->append_single_option_line("support_material_speed"); + optgroup->append_single_option_line("support_material_interface_speed"); + optgroup->append_single_option_line("bridge_speed"); + optgroup->append_single_option_line("gap_fill_speed"); - optgroup = page->new_optgroup(_(L("Speed for non-print moves"))); - optgroup->append_single_option_line("travel_speed"); + optgroup = page->new_optgroup(_(L("Speed for non-print moves"))); + optgroup->append_single_option_line("travel_speed"); - optgroup = page->new_optgroup(_(L("Modifiers"))); - optgroup->append_single_option_line("first_layer_speed"); + optgroup = page->new_optgroup(_(L("Modifiers"))); + optgroup->append_single_option_line("first_layer_speed"); - optgroup = page->new_optgroup(_(L("Acceleration control (advanced)"))); - optgroup->append_single_option_line("perimeter_acceleration"); - optgroup->append_single_option_line("infill_acceleration"); - optgroup->append_single_option_line("bridge_acceleration"); - optgroup->append_single_option_line("first_layer_acceleration"); - optgroup->append_single_option_line("default_acceleration"); + optgroup = page->new_optgroup(_(L("Acceleration control (advanced)"))); + optgroup->append_single_option_line("perimeter_acceleration"); + optgroup->append_single_option_line("infill_acceleration"); + optgroup->append_single_option_line("bridge_acceleration"); + optgroup->append_single_option_line("first_layer_acceleration"); + optgroup->append_single_option_line("default_acceleration"); - optgroup = page->new_optgroup(_(L("Autospeed (advanced)"))); - optgroup->append_single_option_line("max_print_speed"); - optgroup->append_single_option_line("max_volumetric_speed"); + optgroup = page->new_optgroup(_(L("Autospeed (advanced)"))); + optgroup->append_single_option_line("max_print_speed"); + optgroup->append_single_option_line("max_volumetric_speed"); #ifdef HAS_PRESSURE_EQUALIZER - optgroup->append_single_option_line("max_volumetric_extrusion_rate_slope_positive"); - optgroup->append_single_option_line("max_volumetric_extrusion_rate_slope_negative"); + optgroup->append_single_option_line("max_volumetric_extrusion_rate_slope_positive"); + optgroup->append_single_option_line("max_volumetric_extrusion_rate_slope_negative"); #endif /* HAS_PRESSURE_EQUALIZER */ - page = add_options_page(_(L("Multiple Extruders")), "funnel"); - optgroup = page->new_optgroup(_(L("Extruders"))); - optgroup->append_single_option_line("perimeter_extruder"); - optgroup->append_single_option_line("infill_extruder"); - optgroup->append_single_option_line("solid_infill_extruder"); - optgroup->append_single_option_line("support_material_extruder"); - optgroup->append_single_option_line("support_material_interface_extruder"); + page = add_options_page(_(L("Multiple Extruders")), "funnel"); + optgroup = page->new_optgroup(_(L("Extruders"))); + optgroup->append_single_option_line("perimeter_extruder"); + optgroup->append_single_option_line("infill_extruder"); + optgroup->append_single_option_line("solid_infill_extruder"); + optgroup->append_single_option_line("support_material_extruder"); + optgroup->append_single_option_line("support_material_interface_extruder"); - optgroup = page->new_optgroup(_(L("Ooze prevention"))); - optgroup->append_single_option_line("ooze_prevention"); - optgroup->append_single_option_line("standby_temperature_delta"); + optgroup = page->new_optgroup(_(L("Ooze prevention"))); + optgroup->append_single_option_line("ooze_prevention"); + optgroup->append_single_option_line("standby_temperature_delta"); - optgroup = page->new_optgroup(_(L("Wipe tower"))); - optgroup->append_single_option_line("wipe_tower"); - optgroup->append_single_option_line("wipe_tower_x"); - optgroup->append_single_option_line("wipe_tower_y"); - optgroup->append_single_option_line("wipe_tower_width"); - optgroup->append_single_option_line("wipe_tower_rotation_angle"); + optgroup = page->new_optgroup(_(L("Wipe tower"))); + optgroup->append_single_option_line("wipe_tower"); + optgroup->append_single_option_line("wipe_tower_x"); + optgroup->append_single_option_line("wipe_tower_y"); + optgroup->append_single_option_line("wipe_tower_width"); + optgroup->append_single_option_line("wipe_tower_rotation_angle"); optgroup->append_single_option_line("wipe_tower_bridging"); optgroup->append_single_option_line("single_extruder_multi_material_priming"); - optgroup = page->new_optgroup(_(L("Advanced"))); - optgroup->append_single_option_line("interface_shells"); + optgroup = page->new_optgroup(_(L("Advanced"))); + optgroup->append_single_option_line("interface_shells"); - page = add_options_page(_(L("Advanced")), "wrench"); - optgroup = page->new_optgroup(_(L("Extrusion width"))); - optgroup->append_single_option_line("extrusion_width"); - optgroup->append_single_option_line("first_layer_extrusion_width"); - optgroup->append_single_option_line("perimeter_extrusion_width"); - optgroup->append_single_option_line("external_perimeter_extrusion_width"); - optgroup->append_single_option_line("infill_extrusion_width"); - optgroup->append_single_option_line("solid_infill_extrusion_width"); - optgroup->append_single_option_line("top_infill_extrusion_width"); - optgroup->append_single_option_line("support_material_extrusion_width"); + page = add_options_page(_(L("Advanced")), "wrench"); + optgroup = page->new_optgroup(_(L("Extrusion width"))); + optgroup->append_single_option_line("extrusion_width"); + optgroup->append_single_option_line("first_layer_extrusion_width"); + optgroup->append_single_option_line("perimeter_extrusion_width"); + optgroup->append_single_option_line("external_perimeter_extrusion_width"); + optgroup->append_single_option_line("infill_extrusion_width"); + optgroup->append_single_option_line("solid_infill_extrusion_width"); + optgroup->append_single_option_line("top_infill_extrusion_width"); + optgroup->append_single_option_line("support_material_extrusion_width"); - optgroup = page->new_optgroup(_(L("Overlap"))); - optgroup->append_single_option_line("infill_overlap"); + optgroup = page->new_optgroup(_(L("Overlap"))); + optgroup->append_single_option_line("infill_overlap"); - optgroup = page->new_optgroup(_(L("Flow"))); - optgroup->append_single_option_line("bridge_flow_ratio"); + optgroup = page->new_optgroup(_(L("Flow"))); + optgroup->append_single_option_line("bridge_flow_ratio"); - optgroup = page->new_optgroup(_(L("Slicing"))); - optgroup->append_single_option_line("slice_closing_radius"); - optgroup->append_single_option_line("resolution"); - optgroup->append_single_option_line("xy_size_compensation"); - optgroup->append_single_option_line("elefant_foot_compensation"); + optgroup = page->new_optgroup(_(L("Slicing"))); + optgroup->append_single_option_line("slice_closing_radius"); + optgroup->append_single_option_line("resolution"); + optgroup->append_single_option_line("xy_size_compensation"); + optgroup->append_single_option_line("elefant_foot_compensation"); - optgroup = page->new_optgroup(_(L("Other"))); - optgroup->append_single_option_line("clip_multipart_objects"); + optgroup = page->new_optgroup(_(L("Other"))); + optgroup->append_single_option_line("clip_multipart_objects"); - page = add_options_page(_(L("Output options")), "output+page_white"); - optgroup = page->new_optgroup(_(L("Sequential printing"))); - optgroup->append_single_option_line("complete_objects"); - line = { _(L("Extruder clearance (mm)")), "" }; - Option option = optgroup->get_option("extruder_clearance_radius"); - option.opt.width = 6; - line.append_option(option); - option = optgroup->get_option("extruder_clearance_height"); - option.opt.width = 6; - line.append_option(option); - optgroup->append_line(line); + page = add_options_page(_(L("Output options")), "output+page_white"); + optgroup = page->new_optgroup(_(L("Sequential printing"))); + optgroup->append_single_option_line("complete_objects"); + line = { _(L("Extruder clearance (mm)")), "" }; + Option option = optgroup->get_option("extruder_clearance_radius"); + option.opt.width = 6; + line.append_option(option); + option = optgroup->get_option("extruder_clearance_height"); + option.opt.width = 6; + line.append_option(option); + optgroup->append_line(line); - optgroup = page->new_optgroup(_(L("Output file"))); - optgroup->append_single_option_line("gcode_comments"); - optgroup->append_single_option_line("gcode_label_objects"); - option = optgroup->get_option("output_filename_format"); - option.opt.full_width = true; - optgroup->append_single_option_line(option); + optgroup = page->new_optgroup(_(L("Output file"))); + optgroup->append_single_option_line("gcode_comments"); + optgroup->append_single_option_line("gcode_label_objects"); + option = optgroup->get_option("output_filename_format"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); - optgroup = page->new_optgroup(_(L("Post-processing scripts")), 0); - option = optgroup->get_option("post_process"); - option.opt.full_width = true; + optgroup = page->new_optgroup(_(L("Post-processing scripts")), 0); + option = optgroup->get_option("post_process"); + option.opt.full_width = true; option.opt.height = 5;//50; - optgroup->append_single_option_line(option); + optgroup->append_single_option_line(option); - page = add_options_page(_(L("Notes")), "note.png"); - optgroup = page->new_optgroup(_(L("Notes")), 0); - option = optgroup->get_option("notes"); - option.opt.full_width = true; + page = add_options_page(_(L("Notes")), "note.png"); + optgroup = page->new_optgroup(_(L("Notes")), 0); + option = optgroup->get_option("notes"); + option.opt.full_width = true; option.opt.height = 25;//250; - optgroup->append_single_option_line(option); + optgroup->append_single_option_line(option); - page = add_options_page(_(L("Dependencies")), "wrench.png"); - optgroup = page->new_optgroup(_(L("Profile dependencies"))); + page = add_options_page(_(L("Dependencies")), "wrench.png"); + optgroup = page->new_optgroup(_(L("Profile dependencies"))); line = optgroup->create_single_option_line("compatible_printers"); line.widget = [this](wxWindow* parent) { - return compatible_widget_create(parent, m_compatible_printers); - }; - optgroup->append_line(line, &m_colored_Label); - option = optgroup->get_option("compatible_printers_condition"); - option.opt.full_width = true; - optgroup->append_single_option_line(option); + return compatible_widget_create(parent, m_compatible_printers); + }; + optgroup->append_line(line, &m_colored_Label); + option = optgroup->get_option("compatible_printers_condition"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); - line = Line{ "", "" }; - line.full_width = 1; - line.widget = [this](wxWindow* parent) { - return description_line_widget(parent, &m_parent_preset_description_line); - }; - optgroup->append_line(line); + line = Line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_parent_preset_description_line); + }; + optgroup->append_line(line); } // Reload current config (aka presets->edited_preset->config) into the UI fields. void TabPrint::reload_config() { - this->compatible_widget_reload(m_compatible_printers); - Tab::reload_config(); + this->compatible_widget_reload(m_compatible_printers); + Tab::reload_config(); } void TabPrint::update() @@ -1249,10 +1266,10 @@ void TabPrint::update() if (m_config->opt_float("layer_height") < EPSILON) { const wxString msg_text = _(L("Zero layer height is not valid.\n\nThe layer height will be reset to 0.01.")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Layer height")), wxICON_WARNING | wxOK); + wxMessageDialog dialog(parent(), msg_text, _(L("Layer height")), wxICON_WARNING | wxOK); DynamicPrintConfig new_conf = *m_config; is_msg_dlg_already_exist = true; - dialog->ShowModal(); + dialog.ShowModal(); new_conf.set_key_value("layer_height", new ConfigOptionFloat(0.01)); load_config(new_conf); is_msg_dlg_already_exist = false; @@ -1261,221 +1278,221 @@ void TabPrint::update() if (fabs(m_config->option("first_layer_height")->value - 0) < EPSILON) { const wxString msg_text = _(L("Zero first layer height is not valid.\n\nThe first layer height will be reset to 0.01.")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("First layer height")), wxICON_WARNING | wxOK); + wxMessageDialog dialog(parent(), msg_text, _(L("First layer height")), wxICON_WARNING | wxOK); DynamicPrintConfig new_conf = *m_config; is_msg_dlg_already_exist = true; - dialog->ShowModal(); + dialog.ShowModal(); new_conf.set_key_value("first_layer_height", new ConfigOptionFloatOrPercent(0.01, false)); load_config(new_conf); is_msg_dlg_already_exist = false; } - double fill_density = m_config->option("fill_density")->value; + double fill_density = m_config->option("fill_density")->value; - if (m_config->opt_bool("spiral_vase") && - !(m_config->opt_int("perimeters") == 1 && m_config->opt_int("top_solid_layers") == 0 && - fill_density == 0)) { - wxString msg_text = _(L("The Spiral Vase mode requires:\n" - "- one perimeter\n" - "- no top solid layers\n" - "- 0% fill density\n" - "- no support material\n" - "- no ensure_vertical_shell_thickness\n" - "\nShall I adjust those settings in order to enable Spiral Vase?")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Spiral Vase")), wxICON_WARNING | wxYES | wxNO); - DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_YES) { - new_conf.set_key_value("perimeters", new ConfigOptionInt(1)); - new_conf.set_key_value("top_solid_layers", new ConfigOptionInt(0)); - new_conf.set_key_value("fill_density", new ConfigOptionPercent(0)); - new_conf.set_key_value("support_material", new ConfigOptionBool(false)); - new_conf.set_key_value("support_material_enforce_layers", new ConfigOptionInt(0)); - new_conf.set_key_value("ensure_vertical_shell_thickness", new ConfigOptionBool(false)); - fill_density = 0; - } - else { - new_conf.set_key_value("spiral_vase", new ConfigOptionBool(false)); - } - load_config(new_conf); - on_value_change("fill_density", fill_density); - } + if (m_config->opt_bool("spiral_vase") && + !(m_config->opt_int("perimeters") == 1 && m_config->opt_int("top_solid_layers") == 0 && + fill_density == 0)) { + wxString msg_text = _(L("The Spiral Vase mode requires:\n" + "- one perimeter\n" + "- no top solid layers\n" + "- 0% fill density\n" + "- no support material\n" + "- no ensure_vertical_shell_thickness\n" + "\nShall I adjust those settings in order to enable Spiral Vase?")); + wxMessageDialog dialog(parent(), msg_text, _(L("Spiral Vase")), wxICON_WARNING | wxYES | wxNO); + DynamicPrintConfig new_conf = *m_config; + if (dialog.ShowModal() == wxID_YES) { + new_conf.set_key_value("perimeters", new ConfigOptionInt(1)); + new_conf.set_key_value("top_solid_layers", new ConfigOptionInt(0)); + new_conf.set_key_value("fill_density", new ConfigOptionPercent(0)); + new_conf.set_key_value("support_material", new ConfigOptionBool(false)); + new_conf.set_key_value("support_material_enforce_layers", new ConfigOptionInt(0)); + new_conf.set_key_value("ensure_vertical_shell_thickness", new ConfigOptionBool(false)); + fill_density = 0; + } + else { + new_conf.set_key_value("spiral_vase", new ConfigOptionBool(false)); + } + load_config(new_conf); + on_value_change("fill_density", fill_density); + } - if (m_config->opt_bool("wipe_tower") && m_config->opt_bool("support_material") && - m_config->opt_float("support_material_contact_distance") > 0. && - (m_config->opt_int("support_material_extruder") != 0 || m_config->opt_int("support_material_interface_extruder") != 0)) { - wxString msg_text = _(L("The Wipe Tower currently supports the non-soluble supports only\n" - "if they are printed with the current extruder without triggering a tool change.\n" - "(both support_material_extruder and support_material_interface_extruder need to be set to 0).\n" - "\nShall I adjust those settings in order to enable the Wipe Tower?")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Wipe Tower")), wxICON_WARNING | wxYES | wxNO); - DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_YES) { - new_conf.set_key_value("support_material_extruder", new ConfigOptionInt(0)); - new_conf.set_key_value("support_material_interface_extruder", new ConfigOptionInt(0)); - } - else - new_conf.set_key_value("wipe_tower", new ConfigOptionBool(false)); - load_config(new_conf); - } + if (m_config->opt_bool("wipe_tower") && m_config->opt_bool("support_material") && + m_config->opt_float("support_material_contact_distance") > 0. && + (m_config->opt_int("support_material_extruder") != 0 || m_config->opt_int("support_material_interface_extruder") != 0)) { + wxString msg_text = _(L("The Wipe Tower currently supports the non-soluble supports only\n" + "if they are printed with the current extruder without triggering a tool change.\n" + "(both support_material_extruder and support_material_interface_extruder need to be set to 0).\n" + "\nShall I adjust those settings in order to enable the Wipe Tower?")); + wxMessageDialog dialog(parent(), msg_text, _(L("Wipe Tower")), wxICON_WARNING | wxYES | wxNO); + DynamicPrintConfig new_conf = *m_config; + if (dialog.ShowModal() == wxID_YES) { + new_conf.set_key_value("support_material_extruder", new ConfigOptionInt(0)); + new_conf.set_key_value("support_material_interface_extruder", new ConfigOptionInt(0)); + } + else + new_conf.set_key_value("wipe_tower", new ConfigOptionBool(false)); + load_config(new_conf); + } - if (m_config->opt_bool("wipe_tower") && m_config->opt_bool("support_material") && - m_config->opt_float("support_material_contact_distance") == 0 && - !m_config->opt_bool("support_material_synchronize_layers")) { - wxString msg_text = _(L("For the Wipe Tower to work with the soluble supports, the support layers\n" - "need to be synchronized with the object layers.\n" - "\nShall I synchronize support layers in order to enable the Wipe Tower?")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Wipe Tower")), wxICON_WARNING | wxYES | wxNO); - DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_YES) { - new_conf.set_key_value("support_material_synchronize_layers", new ConfigOptionBool(true)); - } - else - new_conf.set_key_value("wipe_tower", new ConfigOptionBool(false)); - load_config(new_conf); - } + if (m_config->opt_bool("wipe_tower") && m_config->opt_bool("support_material") && + m_config->opt_float("support_material_contact_distance") == 0 && + !m_config->opt_bool("support_material_synchronize_layers")) { + wxString msg_text = _(L("For the Wipe Tower to work with the soluble supports, the support layers\n" + "need to be synchronized with the object layers.\n" + "\nShall I synchronize support layers in order to enable the Wipe Tower?")); + wxMessageDialog dialog(parent(), msg_text, _(L("Wipe Tower")), wxICON_WARNING | wxYES | wxNO); + DynamicPrintConfig new_conf = *m_config; + if (dialog.ShowModal() == wxID_YES) { + new_conf.set_key_value("support_material_synchronize_layers", new ConfigOptionBool(true)); + } + else + new_conf.set_key_value("wipe_tower", new ConfigOptionBool(false)); + load_config(new_conf); + } - if (m_config->opt_bool("support_material")) { - // Ask only once. - if (!m_support_material_overhangs_queried) { - m_support_material_overhangs_queried = true; - if (!m_config->opt_bool("overhangs")/* != 1*/) { - wxString msg_text = _(L("Supports work better, if the following feature is enabled:\n" - "- Detect bridging perimeters\n" - "\nShall I adjust those settings for supports?")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Support Generator")), wxICON_WARNING | wxYES | wxNO | wxCANCEL); - DynamicPrintConfig new_conf = *m_config; - auto answer = dialog->ShowModal(); - if (answer == wxID_YES) { - // Enable "detect bridging perimeters". - new_conf.set_key_value("overhangs", new ConfigOptionBool(true)); - } else if (answer == wxID_NO) { - // Do nothing, leave supports on and "detect bridging perimeters" off. - } else if (answer == wxID_CANCEL) { - // Disable supports. - new_conf.set_key_value("support_material", new ConfigOptionBool(false)); - m_support_material_overhangs_queried = false; - } - load_config(new_conf); - } - } - } - else { - m_support_material_overhangs_queried = false; - } + if (m_config->opt_bool("support_material")) { + // Ask only once. + if (!m_support_material_overhangs_queried) { + m_support_material_overhangs_queried = true; + if (!m_config->opt_bool("overhangs")/* != 1*/) { + wxString msg_text = _(L("Supports work better, if the following feature is enabled:\n" + "- Detect bridging perimeters\n" + "\nShall I adjust those settings for supports?")); + wxMessageDialog dialog(parent(), msg_text, _(L("Support Generator")), wxICON_WARNING | wxYES | wxNO | wxCANCEL); + DynamicPrintConfig new_conf = *m_config; + auto answer = dialog.ShowModal(); + if (answer == wxID_YES) { + // Enable "detect bridging perimeters". + new_conf.set_key_value("overhangs", new ConfigOptionBool(true)); + } else if (answer == wxID_NO) { + // Do nothing, leave supports on and "detect bridging perimeters" off. + } else if (answer == wxID_CANCEL) { + // Disable supports. + new_conf.set_key_value("support_material", new ConfigOptionBool(false)); + m_support_material_overhangs_queried = false; + } + load_config(new_conf); + } + } + } + else { + m_support_material_overhangs_queried = false; + } - if (m_config->option("fill_density")->value == 100) { - auto fill_pattern = m_config->option>("fill_pattern")->value; - std::string str_fill_pattern = ""; - t_config_enum_values map_names = m_config->option>("fill_pattern")->get_enum_values(); - for (auto it : map_names) { - if (fill_pattern == it.second) { - str_fill_pattern = it.first; - break; - } - } - if (!str_fill_pattern.empty()) { - const std::vector &external_fill_pattern = m_config->def()->get("top_fill_pattern")->enum_values; - bool correct_100p_fill = false; - for (const std::string &fill : external_fill_pattern) - { - if (str_fill_pattern == fill) - correct_100p_fill = true; - } - // get fill_pattern name from enum_labels for using this one at dialog_msg - str_fill_pattern = _utf8(m_config->def()->get("fill_pattern")->enum_labels[fill_pattern]); - if (!correct_100p_fill) { - wxString msg_text = GUI::from_u8((boost::format(_utf8(L("The %1% infill pattern is not supposed to work at 100%% density.\n\n" - "Shall I switch to rectilinear fill pattern?"))) % str_fill_pattern).str()); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Infill")), wxICON_WARNING | wxYES | wxNO); - DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_YES) { - new_conf.set_key_value("fill_pattern", new ConfigOptionEnum(ipRectilinear)); - fill_density = 100; - } - else - fill_density = m_presets->get_selected_preset().config.option("fill_density")->value; - new_conf.set_key_value("fill_density", new ConfigOptionPercent(fill_density)); - load_config(new_conf); - on_value_change("fill_density", fill_density); - } - } - } + if (m_config->option("fill_density")->value == 100) { + auto fill_pattern = m_config->option>("fill_pattern")->value; + std::string str_fill_pattern = ""; + t_config_enum_values map_names = m_config->option>("fill_pattern")->get_enum_values(); + for (auto it : map_names) { + if (fill_pattern == it.second) { + str_fill_pattern = it.first; + break; + } + } + if (!str_fill_pattern.empty()) { + const std::vector &external_fill_pattern = m_config->def()->get("top_fill_pattern")->enum_values; + bool correct_100p_fill = false; + for (const std::string &fill : external_fill_pattern) + { + if (str_fill_pattern == fill) + correct_100p_fill = true; + } + // get fill_pattern name from enum_labels for using this one at dialog_msg + str_fill_pattern = _utf8(m_config->def()->get("fill_pattern")->enum_labels[fill_pattern]); + if (!correct_100p_fill) { + wxString msg_text = GUI::from_u8((boost::format(_utf8(L("The %1% infill pattern is not supposed to work at 100%% density.\n\n" + "Shall I switch to rectilinear fill pattern?"))) % str_fill_pattern).str()); + wxMessageDialog dialog(parent(), msg_text, _(L("Infill")), wxICON_WARNING | wxYES | wxNO); + DynamicPrintConfig new_conf = *m_config; + if (dialog.ShowModal() == wxID_YES) { + new_conf.set_key_value("fill_pattern", new ConfigOptionEnum(ipRectilinear)); + fill_density = 100; + } + else + fill_density = m_presets->get_selected_preset().config.option("fill_density")->value; + new_conf.set_key_value("fill_density", new ConfigOptionPercent(fill_density)); + load_config(new_conf); + on_value_change("fill_density", fill_density); + } + } + } - bool have_perimeters = m_config->opt_int("perimeters") > 0; - for (auto el : {"extra_perimeters", "ensure_vertical_shell_thickness", "thin_walls", "overhangs", - "seam_position", "external_perimeters_first", "external_perimeter_extrusion_width", - "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed" }) - get_field(el)->toggle(have_perimeters); + bool have_perimeters = m_config->opt_int("perimeters") > 0; + for (auto el : {"extra_perimeters", "ensure_vertical_shell_thickness", "thin_walls", "overhangs", + "seam_position", "external_perimeters_first", "external_perimeter_extrusion_width", + "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed" }) + get_field(el)->toggle(have_perimeters); - bool have_infill = m_config->option("fill_density")->value > 0; - // infill_extruder uses the same logic as in Print::extruders() - for (auto el : {"fill_pattern", "infill_every_layers", "infill_only_where_needed", - "solid_infill_every_layers", "solid_infill_below_area", "infill_extruder" }) - get_field(el)->toggle(have_infill); + bool have_infill = m_config->option("fill_density")->value > 0; + // infill_extruder uses the same logic as in Print::extruders() + for (auto el : {"fill_pattern", "infill_every_layers", "infill_only_where_needed", + "solid_infill_every_layers", "solid_infill_below_area", "infill_extruder" }) + get_field(el)->toggle(have_infill); - bool have_solid_infill = m_config->opt_int("top_solid_layers") > 0 || m_config->opt_int("bottom_solid_layers") > 0; - // solid_infill_extruder uses the same logic as in Print::extruders() - for (auto el : {"top_fill_pattern", "bottom_fill_pattern", "infill_first", "solid_infill_extruder", - "solid_infill_extrusion_width", "solid_infill_speed" }) - get_field(el)->toggle(have_solid_infill); + bool have_solid_infill = m_config->opt_int("top_solid_layers") > 0 || m_config->opt_int("bottom_solid_layers") > 0; + // solid_infill_extruder uses the same logic as in Print::extruders() + for (auto el : {"top_fill_pattern", "bottom_fill_pattern", "infill_first", "solid_infill_extruder", + "solid_infill_extrusion_width", "solid_infill_speed" }) + get_field(el)->toggle(have_solid_infill); - for (auto el : {"fill_angle", "bridge_angle", "infill_extrusion_width", - "infill_speed", "bridge_speed" }) - get_field(el)->toggle(have_infill || have_solid_infill); + for (auto el : {"fill_angle", "bridge_angle", "infill_extrusion_width", + "infill_speed", "bridge_speed" }) + get_field(el)->toggle(have_infill || have_solid_infill); - get_field("gap_fill_speed")->toggle(have_perimeters && have_infill); + get_field("gap_fill_speed")->toggle(have_perimeters && have_infill); - bool have_top_solid_infill = m_config->opt_int("top_solid_layers") > 0; - for (auto el : { "top_infill_extrusion_width", "top_solid_infill_speed" }) - get_field(el)->toggle(have_top_solid_infill); + bool have_top_solid_infill = m_config->opt_int("top_solid_layers") > 0; + for (auto el : { "top_infill_extrusion_width", "top_solid_infill_speed" }) + get_field(el)->toggle(have_top_solid_infill); - bool have_default_acceleration = m_config->opt_float("default_acceleration") > 0; - for (auto el : {"perimeter_acceleration", "infill_acceleration", - "bridge_acceleration", "first_layer_acceleration" }) - get_field(el)->toggle(have_default_acceleration); + bool have_default_acceleration = m_config->opt_float("default_acceleration") > 0; + for (auto el : {"perimeter_acceleration", "infill_acceleration", + "bridge_acceleration", "first_layer_acceleration" }) + get_field(el)->toggle(have_default_acceleration); - bool have_skirt = m_config->opt_int("skirts") > 0 || m_config->opt_float("min_skirt_length") > 0; - for (auto el : { "skirt_distance", "skirt_height" }) - get_field(el)->toggle(have_skirt); + bool have_skirt = m_config->opt_int("skirts") > 0 || m_config->opt_float("min_skirt_length") > 0; + for (auto el : { "skirt_distance", "skirt_height" }) + get_field(el)->toggle(have_skirt); - bool have_brim = m_config->opt_float("brim_width") > 0; - // perimeter_extruder uses the same logic as in Print::extruders() - get_field("perimeter_extruder")->toggle(have_perimeters || have_brim); + bool have_brim = m_config->opt_float("brim_width") > 0; + // perimeter_extruder uses the same logic as in Print::extruders() + get_field("perimeter_extruder")->toggle(have_perimeters || have_brim); - bool have_raft = m_config->opt_int("raft_layers") > 0; - bool have_support_material = m_config->opt_bool("support_material") || have_raft; - bool have_support_material_auto = have_support_material && m_config->opt_bool("support_material_auto"); - bool have_support_interface = m_config->opt_int("support_material_interface_layers") > 0; - bool have_support_soluble = have_support_material && m_config->opt_float("support_material_contact_distance") == 0; - for (auto el : {"support_material_pattern", "support_material_with_sheath", - "support_material_spacing", "support_material_angle", "support_material_interface_layers", - "dont_support_bridges", "support_material_extrusion_width", "support_material_contact_distance", - "support_material_xy_spacing" }) - get_field(el)->toggle(have_support_material); - get_field("support_material_threshold")->toggle(have_support_material_auto); + bool have_raft = m_config->opt_int("raft_layers") > 0; + bool have_support_material = m_config->opt_bool("support_material") || have_raft; + bool have_support_material_auto = have_support_material && m_config->opt_bool("support_material_auto"); + bool have_support_interface = m_config->opt_int("support_material_interface_layers") > 0; + bool have_support_soluble = have_support_material && m_config->opt_float("support_material_contact_distance") == 0; + for (auto el : {"support_material_pattern", "support_material_with_sheath", + "support_material_spacing", "support_material_angle", "support_material_interface_layers", + "dont_support_bridges", "support_material_extrusion_width", "support_material_contact_distance", + "support_material_xy_spacing" }) + get_field(el)->toggle(have_support_material); + get_field("support_material_threshold")->toggle(have_support_material_auto); - for (auto el : {"support_material_interface_spacing", "support_material_interface_extruder", - "support_material_interface_speed", "support_material_interface_contact_loops" }) - get_field(el)->toggle(have_support_material && have_support_interface); - get_field("support_material_synchronize_layers")->toggle(have_support_soluble); + for (auto el : {"support_material_interface_spacing", "support_material_interface_extruder", + "support_material_interface_speed", "support_material_interface_contact_loops" }) + get_field(el)->toggle(have_support_material && have_support_interface); + get_field("support_material_synchronize_layers")->toggle(have_support_soluble); - get_field("perimeter_extrusion_width")->toggle(have_perimeters || have_skirt || have_brim); - get_field("support_material_extruder")->toggle(have_support_material || have_skirt); - get_field("support_material_speed")->toggle(have_support_material || have_brim || have_skirt); + get_field("perimeter_extrusion_width")->toggle(have_perimeters || have_skirt || have_brim); + get_field("support_material_extruder")->toggle(have_support_material || have_skirt); + get_field("support_material_speed")->toggle(have_support_material || have_brim || have_skirt); - bool have_sequential_printing = m_config->opt_bool("complete_objects"); - for (auto el : { "extruder_clearance_radius", "extruder_clearance_height" }) - get_field(el)->toggle(have_sequential_printing); + bool have_sequential_printing = m_config->opt_bool("complete_objects"); + for (auto el : { "extruder_clearance_radius", "extruder_clearance_height" }) + get_field(el)->toggle(have_sequential_printing); - bool have_ooze_prevention = m_config->opt_bool("ooze_prevention"); - get_field("standby_temperature_delta")->toggle(have_ooze_prevention); + bool have_ooze_prevention = m_config->opt_bool("ooze_prevention"); + get_field("standby_temperature_delta")->toggle(have_ooze_prevention); - bool have_wipe_tower = m_config->opt_bool("wipe_tower"); - for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging"}) - get_field(el)->toggle(have_wipe_tower); + bool have_wipe_tower = m_config->opt_bool("wipe_tower"); + for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging"}) + get_field(el)->toggle(have_wipe_tower); - m_recommended_thin_wall_thickness_description_line->SetText( - from_u8(PresetHints::recommended_thin_wall_thickness(*m_preset_bundle))); + m_recommended_thin_wall_thickness_description_line->SetText( + from_u8(PresetHints::recommended_thin_wall_thickness(*m_preset_bundle))); Layout(); // Thaw(); @@ -1487,165 +1504,281 @@ void TabPrint::update() void TabPrint::OnActivate() { - m_recommended_thin_wall_thickness_description_line->SetText( - from_u8(PresetHints::recommended_thin_wall_thickness(*m_preset_bundle))); - Tab::OnActivate(); + m_recommended_thin_wall_thickness_description_line->SetText( + from_u8(PresetHints::recommended_thin_wall_thickness(*m_preset_bundle))); + Tab::OnActivate(); +} + +void TabFilament::add_filament_overrides_page() +{ + PageShp page = add_options_page(_(L("Filament Overrides")), "wrench"); + ConfigOptionsGroupShp optgroup = page->new_optgroup(_(L("Retraction"))); + + auto append_single_option_line = [optgroup, this](const std::string& opt_key, int opt_index) + { + Line line {"",""}; + if (opt_key == "filament_retract_lift_above" || opt_key == "filament_retract_lift_below") { + Option opt = optgroup->get_option(opt_key); + opt.opt.label = opt.opt.full_label; + line = optgroup->create_single_option_line(opt); + } + else + line = optgroup->create_single_option_line(optgroup->get_option(opt_key)); + + line.near_label_widget = [this, optgroup, opt_key, opt_index](wxWindow* parent) { + wxCheckBox* check_box = new wxCheckBox(parent, wxID_ANY, ""); + + check_box->Bind(wxEVT_CHECKBOX, [this, optgroup, opt_key, opt_index](wxCommandEvent& evt) { + const bool is_checked = evt.IsChecked(); + Field* field = optgroup->get_fieldc(opt_key, opt_index); + if (field != nullptr) { + field->toggle(is_checked); + if (is_checked) + field->set_last_meaningful_value(); + else + field->set_na_value(); + } + }, check_box->GetId()); + + m_overrides_options[opt_key] = check_box; + return check_box; + }; + + optgroup->append_line(line); + }; + + const int extruder_idx = 0; // #ys_FIXME + + for (const std::string opt_key : { "filament_retract_length", + "filament_retract_lift", + "filament_retract_lift_above", + "filament_retract_lift_below", + "filament_retract_speed", + "filament_deretract_speed", + "filament_retract_restart_extra", + "filament_retract_before_travel", + "filament_retract_layer_change", + "filament_wipe", + "filament_retract_before_wipe" + }) + append_single_option_line(opt_key, extruder_idx); +} + +void TabFilament::update_filament_overrides_page() +{ + const auto page_it = std::find_if(m_pages.begin(), m_pages.end(), [](const PageShp page) {return page->title() == _(L("Filament Overrides")); }); + if (page_it == m_pages.end()) + return; + PageShp page = *page_it; + + const auto og_it = std::find_if(page->m_optgroups.begin(), page->m_optgroups.end(), [](const ConfigOptionsGroupShp og) {return og->title == _(L("Retraction")); }); + if (og_it == page->m_optgroups.end()) + return; + ConfigOptionsGroupShp optgroup = *og_it; + + std::vector opt_keys = { "filament_retract_length", + "filament_retract_lift", + "filament_retract_lift_above", + "filament_retract_lift_below", + "filament_retract_speed", + "filament_deretract_speed", + "filament_retract_restart_extra", + "filament_retract_before_travel", + "filament_retract_layer_change", + "filament_wipe", + "filament_retract_before_wipe" + }; + + const int extruder_idx = 0; // #ys_FIXME + + const bool have_retract_length = m_config->option("filament_retract_length")->is_nil() || + m_config->opt_float("filament_retract_length", extruder_idx) > 0; + + for (const std::string& opt_key : opt_keys) + { + bool is_checked = opt_key=="filament_retract_length" ? true : have_retract_length; + m_overrides_options[opt_key]->Enable(is_checked); + + is_checked &= !m_config->option(opt_key)->is_nil(); + m_overrides_options[opt_key]->SetValue(is_checked); + + Field* field = optgroup->get_fieldc(opt_key, extruder_idx); + if (field != nullptr) + field->toggle(is_checked); + } } void TabFilament::build() { - m_presets = &m_preset_bundle->filaments; - load_initial_data(); + m_presets = &m_preset_bundle->filaments; + load_initial_data(); - auto page = add_options_page(_(L("Filament")), "spool.png"); - auto optgroup = page->new_optgroup(_(L("Filament"))); - optgroup->append_single_option_line("filament_colour"); - optgroup->append_single_option_line("filament_diameter"); - optgroup->append_single_option_line("extrusion_multiplier"); - optgroup->append_single_option_line("filament_density"); - optgroup->append_single_option_line("filament_cost"); + auto page = add_options_page(_(L("Filament")), "spool.png"); + auto optgroup = page->new_optgroup(_(L("Filament"))); + optgroup->append_single_option_line("filament_colour"); + optgroup->append_single_option_line("filament_diameter"); + optgroup->append_single_option_line("extrusion_multiplier"); + optgroup->append_single_option_line("filament_density"); + optgroup->append_single_option_line("filament_cost"); - optgroup = page->new_optgroup(_(L("Temperature")) + wxString(" °C", wxConvUTF8)); - Line line = { _(L("Extruder")), "" }; - line.append_option(optgroup->get_option("first_layer_temperature")); - line.append_option(optgroup->get_option("temperature")); - optgroup->append_line(line); + optgroup = page->new_optgroup(_(L("Temperature")) + wxString(" °C", wxConvUTF8)); + Line line = { _(L("Extruder")), "" }; + line.append_option(optgroup->get_option("first_layer_temperature")); + line.append_option(optgroup->get_option("temperature")); + optgroup->append_line(line); - line = { _(L("Bed")), "" }; - line.append_option(optgroup->get_option("first_layer_bed_temperature")); - line.append_option(optgroup->get_option("bed_temperature")); - optgroup->append_line(line); + line = { _(L("Bed")), "" }; + line.append_option(optgroup->get_option("first_layer_bed_temperature")); + line.append_option(optgroup->get_option("bed_temperature")); + optgroup->append_line(line); - page = add_options_page(_(L("Cooling")), "cooling"); - optgroup = page->new_optgroup(_(L("Enable"))); - optgroup->append_single_option_line("fan_always_on"); - optgroup->append_single_option_line("cooling"); + page = add_options_page(_(L("Cooling")), "cooling"); + optgroup = page->new_optgroup(_(L("Enable"))); + optgroup->append_single_option_line("fan_always_on"); + optgroup->append_single_option_line("cooling"); - line = { "", "" }; - line.full_width = 1; - line.widget = [this](wxWindow* parent) { - return description_line_widget(parent, &m_cooling_description_line); - }; - optgroup->append_line(line); + line = { "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_cooling_description_line); + }; + optgroup->append_line(line); - optgroup = page->new_optgroup(_(L("Fan settings"))); - line = { _(L("Fan speed")), "" }; - line.append_option(optgroup->get_option("min_fan_speed")); - line.append_option(optgroup->get_option("max_fan_speed")); - optgroup->append_line(line); + optgroup = page->new_optgroup(_(L("Fan settings"))); + line = { _(L("Fan speed")), "" }; + line.append_option(optgroup->get_option("min_fan_speed")); + line.append_option(optgroup->get_option("max_fan_speed")); + optgroup->append_line(line); - optgroup->append_single_option_line("bridge_fan_speed"); - optgroup->append_single_option_line("disable_fan_first_layers"); + optgroup->append_single_option_line("bridge_fan_speed"); + optgroup->append_single_option_line("disable_fan_first_layers"); - optgroup = page->new_optgroup(_(L("Cooling thresholds")), 25); - optgroup->append_single_option_line("fan_below_layer_time"); - optgroup->append_single_option_line("slowdown_below_layer_time"); - optgroup->append_single_option_line("min_print_speed"); + optgroup = page->new_optgroup(_(L("Cooling thresholds")), 25); + optgroup->append_single_option_line("fan_below_layer_time"); + optgroup->append_single_option_line("slowdown_below_layer_time"); + optgroup->append_single_option_line("min_print_speed"); - page = add_options_page(_(L("Advanced")), "wrench"); - optgroup = page->new_optgroup(_(L("Filament properties"))); - optgroup->append_single_option_line("filament_type"); - optgroup->append_single_option_line("filament_soluble"); + page = add_options_page(_(L("Advanced")), "wrench"); + optgroup = page->new_optgroup(_(L("Filament properties"))); + optgroup->append_single_option_line("filament_type"); + optgroup->append_single_option_line("filament_soluble"); - optgroup = page->new_optgroup(_(L("Print speed override"))); - optgroup->append_single_option_line("filament_max_volumetric_speed"); + optgroup = page->new_optgroup(_(L("Print speed override"))); + optgroup->append_single_option_line("filament_max_volumetric_speed"); - line = { "", "" }; - line.full_width = 1; - line.widget = [this](wxWindow* parent) { - return description_line_widget(parent, &m_volumetric_speed_description_line); - }; - optgroup->append_line(line); + line = { "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_volumetric_speed_description_line); + }; + optgroup->append_line(line); + + optgroup = page->new_optgroup(_(L("Wipe tower parameters"))); + optgroup->append_single_option_line("filament_minimal_purge_on_wipe_tower"); optgroup = page->new_optgroup(_(L("Toolchange parameters with single extruder MM printers"))); - optgroup->append_single_option_line("filament_loading_speed_start"); + optgroup->append_single_option_line("filament_loading_speed_start"); optgroup->append_single_option_line("filament_loading_speed"); optgroup->append_single_option_line("filament_unloading_speed_start"); optgroup->append_single_option_line("filament_unloading_speed"); - optgroup->append_single_option_line("filament_load_time"); - optgroup->append_single_option_line("filament_unload_time"); + optgroup->append_single_option_line("filament_load_time"); + optgroup->append_single_option_line("filament_unload_time"); optgroup->append_single_option_line("filament_toolchange_delay"); optgroup->append_single_option_line("filament_cooling_moves"); optgroup->append_single_option_line("filament_cooling_initial_speed"); optgroup->append_single_option_line("filament_cooling_final_speed"); - optgroup->append_single_option_line("filament_minimal_purge_on_wipe_tower"); line = optgroup->create_single_option_line("filament_ramming_parameters");// { _(L("Ramming")), "" }; line.widget = [this](wxWindow* parent) { - auto ramming_dialog_btn = new wxButton(parent, wxID_ANY, _(L("Ramming settings"))+dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); - ramming_dialog_btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + auto ramming_dialog_btn = new wxButton(parent, wxID_ANY, _(L("Ramming settings"))+dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); + ramming_dialog_btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(ramming_dialog_btn); - + sizer->Add(ramming_dialog_btn); + ramming_dialog_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) - { + { RammingDialog dlg(this,(m_config->option("filament_ramming_parameters"))->get_at(0)); if (dlg.ShowModal() == wxID_OK) (m_config->option("filament_ramming_parameters"))->get_at(0) = dlg.get_parameters(); - })); - return sizer; - }; - optgroup->append_line(line); + })); + return sizer; + }; + optgroup->append_line(line); + + + add_filament_overrides_page(); + const int gcode_field_height = 15; // 150 const int notes_field_height = 25; // 250 - page = add_options_page(_(L("Custom G-code")), "cog"); - optgroup = page->new_optgroup(_(L("Start G-code")), 0); - Option option = optgroup->get_option("start_filament_gcode"); - option.opt.full_width = true; + page = add_options_page(_(L("Custom G-code")), "cog"); + optgroup = page->new_optgroup(_(L("Start G-code")), 0); + Option option = optgroup->get_option("start_filament_gcode"); + option.opt.full_width = true; option.opt.height = gcode_field_height;// 150; - optgroup->append_single_option_line(option); + optgroup->append_single_option_line(option); - optgroup = page->new_optgroup(_(L("End G-code")), 0); - option = optgroup->get_option("end_filament_gcode"); - option.opt.full_width = true; - option.opt.height = gcode_field_height;// 150; - optgroup->append_single_option_line(option); + optgroup = page->new_optgroup(_(L("End G-code")), 0); + option = optgroup->get_option("end_filament_gcode"); + option.opt.full_width = true; + option.opt.height = gcode_field_height;// 150; + optgroup->append_single_option_line(option); - page = add_options_page(_(L("Notes")), "note.png"); - optgroup = page->new_optgroup(_(L("Notes")), 0); - optgroup->label_width = 0; - option = optgroup->get_option("filament_notes"); - option.opt.full_width = true; - option.opt.height = notes_field_height;// 250; - optgroup->append_single_option_line(option); + page = add_options_page(_(L("Notes")), "note.png"); + optgroup = page->new_optgroup(_(L("Notes")), 0); + optgroup->label_width = 0; + option = optgroup->get_option("filament_notes"); + option.opt.full_width = true; + option.opt.height = notes_field_height;// 250; + optgroup->append_single_option_line(option); + + page = add_options_page(_(L("Dependencies")), "wrench.png"); + optgroup = page->new_optgroup(_(L("Profile dependencies"))); - page = add_options_page(_(L("Dependencies")), "wrench.png"); - optgroup = page->new_optgroup(_(L("Profile dependencies"))); - line = optgroup->create_single_option_line("compatible_printers"); line.widget = [this](wxWindow* parent) { - return compatible_widget_create(parent, m_compatible_printers); - }; - optgroup->append_line(line, &m_colored_Label); - option = optgroup->get_option("compatible_printers_condition"); - option.opt.full_width = true; - optgroup->append_single_option_line(option); + return compatible_widget_create(parent, m_compatible_printers); + }; + optgroup->append_line(line, &m_colored_Label); + option = optgroup->get_option("compatible_printers_condition"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); line = optgroup->create_single_option_line("compatible_prints"); line.widget = [this](wxWindow* parent) { - return compatible_widget_create(parent, m_compatible_prints); - }; - optgroup->append_line(line, &m_colored_Label); - option = optgroup->get_option("compatible_prints_condition"); - option.opt.full_width = true; - optgroup->append_single_option_line(option); + return compatible_widget_create(parent, m_compatible_prints); + }; + optgroup->append_line(line, &m_colored_Label); + option = optgroup->get_option("compatible_prints_condition"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); - line = Line{ "", "" }; - line.full_width = 1; - line.widget = [this](wxWindow* parent) { - return description_line_widget(parent, &m_parent_preset_description_line); - }; - optgroup->append_line(line); + line = Line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_parent_preset_description_line); + }; + optgroup->append_line(line); } // Reload current config (aka presets->edited_preset->config) into the UI fields. void TabFilament::reload_config() { - this->compatible_widget_reload(m_compatible_printers); - this->compatible_widget_reload(m_compatible_prints); - Tab::reload_config(); + this->compatible_widget_reload(m_compatible_printers); + this->compatible_widget_reload(m_compatible_prints); + Tab::reload_config(); +} + +void TabFilament::update_volumetric_flow_preset_hints() +{ + wxString text; + try { + text = from_u8(PresetHints::maximum_volumetric_flow_description(*m_preset_bundle)); + } catch (std::exception &ex) { + text = _(L("Volumetric flow hints not available\n\n")) + from_u8(ex.what()); + } + m_volumetric_speed_description_line->SetText(text); } void TabFilament::update() @@ -1654,22 +1787,23 @@ void TabFilament::update() return; // ys_FIXME m_update_cnt++; -// Freeze(); - wxString text = from_u8(PresetHints::cooling_description(m_presets->get_edited_preset())); - m_cooling_description_line->SetText(text); - text = from_u8(PresetHints::maximum_volumetric_flow_description(*m_preset_bundle)); - m_volumetric_speed_description_line->SetText(text); + + wxString text = from_u8(PresetHints::cooling_description(m_presets->get_edited_preset())); + m_cooling_description_line->SetText(text); + this->update_volumetric_flow_preset_hints(); Layout(); - bool cooling = m_config->opt_bool("cooling", 0); - bool fan_always_on = cooling || m_config->opt_bool("fan_always_on", 0); + bool cooling = m_config->opt_bool("cooling", 0); + bool fan_always_on = cooling || m_config->opt_bool("fan_always_on", 0); - for (auto el : { "max_fan_speed", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed" }) - get_field(el)->toggle(cooling); + for (auto el : { "max_fan_speed", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed" }) + get_field(el)->toggle(cooling); + + for (auto el : { "min_fan_speed", "disable_fan_first_layers" }) + get_field(el)->toggle(fan_always_on); + + update_filament_overrides_page(); - for (auto el : { "min_fan_speed", "disable_fan_first_layers" }) - get_field(el)->toggle(fan_always_on); -// Thaw(); m_update_cnt--; if (m_update_cnt == 0) @@ -1678,147 +1812,147 @@ void TabFilament::update() void TabFilament::OnActivate() { - m_volumetric_speed_description_line->SetText(from_u8(PresetHints::maximum_volumetric_flow_description(*m_preset_bundle))); - Tab::OnActivate(); + this->update_volumetric_flow_preset_hints(); + Tab::OnActivate(); } wxSizer* Tab::description_line_widget(wxWindow* parent, ogStaticText* *StaticText) { - *StaticText = new ogStaticText(parent, ""); + *StaticText = new ogStaticText(parent, ""); // auto font = (new wxSystemSettings)->GetFont(wxSYS_DEFAULT_GUI_FONT); - (*StaticText)->SetFont(wxGetApp().normal_font()); + (*StaticText)->SetFont(wxGetApp().normal_font()); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(*StaticText, 1, wxEXPAND|wxALL, 0); - return sizer; + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(*StaticText, 1, wxEXPAND|wxALL, 0); + return sizer; } bool Tab::current_preset_is_dirty() { - return m_presets->current_is_dirty(); + return m_presets->current_is_dirty(); } void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) { - const PrinterTechnology tech = m_presets->get_selected_preset().printer_technology(); + const PrinterTechnology tech = m_presets->get_selected_preset().printer_technology(); - // Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment) - if (tech == ptFFF) { - optgroup->append_single_option_line("host_type"); - } + // Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment) + if (tech == ptFFF) { + optgroup->append_single_option_line("host_type"); + } - auto printhost_browse = [=](wxWindow* parent) { + auto printhost_browse = [=](wxWindow* parent) { add_scaled_button(parent, &m_printhost_browse_btn, "browse", _(L("Browse")) + " "+ dots, wxBU_LEFT | wxBU_EXACTFIT); ScalableButton* btn = m_printhost_browse_btn; - btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(btn); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn); - btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { - BonjourDialog dialog(parent, tech); - if (dialog.show_and_lookup()) { - optgroup->set_value("print_host", std::move(dialog.get_selected()), true); - optgroup->get_field("print_host")->field_changed(); - } - }); + btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { + BonjourDialog dialog(parent, tech); + if (dialog.show_and_lookup()) { + optgroup->set_value("print_host", std::move(dialog.get_selected()), true); + optgroup->get_field("print_host")->field_changed(); + } + }); - return sizer; - }; + return sizer; + }; - auto print_host_test = [this](wxWindow* parent) { + auto print_host_test = [this](wxWindow* parent) { add_scaled_button(parent, &m_print_host_test_btn, "test", _(L("Test")), wxBU_LEFT | wxBU_EXACTFIT); ScalableButton* btn = m_print_host_test_btn; btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(btn); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn); - btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { - std::unique_ptr host(PrintHost::get_print_host(m_config)); - if (! host) { - const auto text = wxString::Format("%s", - _(L("Could not get a valid Printer Host reference"))); - show_error(this, text); - return; - } - wxString msg; - if (host->test(msg)) { - show_info(this, host->get_test_ok_msg(), _(L("Success!"))); - } else { - show_error(this, host->get_test_failed_msg(msg)); - } - }); + btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { + std::unique_ptr host(PrintHost::get_print_host(m_config)); + if (! host) { + const auto text = wxString::Format("%s", + _(L("Could not get a valid Printer Host reference"))); + show_error(this, text); + return; + } + wxString msg; + if (host->test(msg)) { + show_info(this, host->get_test_ok_msg(), _(L("Success!"))); + } else { + show_error(this, host->get_test_failed_msg(msg)); + } + }); - return sizer; - }; + return sizer; + }; - Line host_line = optgroup->create_single_option_line("print_host"); - host_line.append_widget(printhost_browse); - host_line.append_widget(print_host_test); - optgroup->append_line(host_line); - optgroup->append_single_option_line("printhost_apikey"); + Line host_line = optgroup->create_single_option_line("print_host"); + host_line.append_widget(printhost_browse); + host_line.append_widget(print_host_test); + optgroup->append_line(host_line); + optgroup->append_single_option_line("printhost_apikey"); - const auto ca_file_hint = _(L("HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate.")); + const auto ca_file_hint = _(L("HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate.")); - if (Http::ca_file_supported()) { - Line cafile_line = optgroup->create_single_option_line("printhost_cafile"); + if (Http::ca_file_supported()) { + Line cafile_line = optgroup->create_single_option_line("printhost_cafile"); - auto printhost_cafile_browse = [this, optgroup] (wxWindow* parent) { - auto btn = new wxButton(parent, wxID_ANY, " " + _(L("Browse"))+" " +dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT); - btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - btn->SetBitmap(create_scaled_bitmap(this, "browse")); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(btn); + auto printhost_cafile_browse = [this, optgroup] (wxWindow* parent) { + auto btn = new wxButton(parent, wxID_ANY, " " + _(L("Browse"))+" " +dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT); + btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + btn->SetBitmap(create_scaled_bitmap(this, "browse")); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn); - btn->Bind(wxEVT_BUTTON, [this, optgroup] (wxCommandEvent e) { - static const auto filemasks = _(L("Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*")); - wxFileDialog openFileDialog(this, _(L("Open CA certificate file")), "", "", filemasks, wxFD_OPEN | wxFD_FILE_MUST_EXIST); - if (openFileDialog.ShowModal() != wxID_CANCEL) { - optgroup->set_value("printhost_cafile", std::move(openFileDialog.GetPath()), true); - optgroup->get_field("printhost_cafile")->field_changed(); - } - }); + btn->Bind(wxEVT_BUTTON, [this, optgroup] (wxCommandEvent e) { + static const auto filemasks = _(L("Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*")); + wxFileDialog openFileDialog(this, _(L("Open CA certificate file")), "", "", filemasks, wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (openFileDialog.ShowModal() != wxID_CANCEL) { + optgroup->set_value("printhost_cafile", std::move(openFileDialog.GetPath()), true); + optgroup->get_field("printhost_cafile")->field_changed(); + } + }); - return sizer; - }; + return sizer; + }; - cafile_line.append_widget(printhost_cafile_browse); - optgroup->append_line(cafile_line); + cafile_line.append_widget(printhost_cafile_browse); + optgroup->append_line(cafile_line); - Line cafile_hint { "", "" }; - cafile_hint.full_width = 1; - cafile_hint.widget = [this, ca_file_hint](wxWindow* parent) { - auto txt = new wxStaticText(parent, wxID_ANY, ca_file_hint); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(txt); - return sizer; - }; - optgroup->append_line(cafile_hint); - } else { - Line line { "", "" }; - line.full_width = 1; + Line cafile_hint { "", "" }; + cafile_hint.full_width = 1; + cafile_hint.widget = [this, ca_file_hint](wxWindow* parent) { + auto txt = new wxStaticText(parent, wxID_ANY, ca_file_hint); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(txt); + return sizer; + }; + optgroup->append_line(cafile_hint); + } else { + Line line { "", "" }; + line.full_width = 1; - line.widget = [this, ca_file_hint] (wxWindow* parent) { - auto txt = new wxStaticText(parent, wxID_ANY, wxString::Format("%s\n\n\t%s", - wxString::Format(_(L("HTTPS CA File:\n\ + line.widget = [this, ca_file_hint] (wxWindow* parent) { + auto txt = new wxStaticText(parent, wxID_ANY, wxString::Format("%s\n\n\t%s", + wxString::Format(_(L("HTTPS CA File:\n\ \tOn this system, %s uses HTTPS certificates from the system Certificate Store or Keychain.\n\ \tTo use a custom CA file, please import your CA file into Certificate Store / Keychain.")), SLIC3R_APP_NAME), - ca_file_hint)); - txt->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(txt); - return sizer; - }; + ca_file_hint)); + txt->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(txt); + return sizer; + }; - optgroup->append_line(line); - } + optgroup->append_line(line); + } } void TabPrinter::build() { - m_presets = &m_preset_bundle->printers; - load_initial_data(); + m_presets = &m_preset_bundle->printers; + load_initial_data(); m_printer_technology = m_presets->get_selected_preset().printer_technology(); @@ -1829,211 +1963,251 @@ void TabPrinter::build_fff() { if (!m_pages.empty()) m_pages.resize(0); - // to avoid redundant memory allocation / deallocation during extruders count changing - m_pages.reserve(30); + // to avoid redundant memory allocation / deallocation during extruders count changing + m_pages.reserve(30); - auto *nozzle_diameter = dynamic_cast(m_config->option("nozzle_diameter")); - m_initial_extruders_count = m_extruders_count = nozzle_diameter->values.size(); + auto *nozzle_diameter = dynamic_cast(m_config->option("nozzle_diameter")); + m_initial_extruders_count = m_extruders_count = nozzle_diameter->values.size(); wxGetApp().sidebar().update_objects_list_extruder_column(m_initial_extruders_count); - const Preset* parent_preset = m_presets->get_selected_preset_parent(); - m_sys_extruders_count = parent_preset == nullptr ? 0 : - static_cast(parent_preset->config.option("nozzle_diameter"))->values.size(); + const Preset* parent_preset = m_presets->get_selected_preset_parent(); + m_sys_extruders_count = parent_preset == nullptr ? 0 : + static_cast(parent_preset->config.option("nozzle_diameter"))->values.size(); - auto page = add_options_page(_(L("General")), "printer"); - auto optgroup = page->new_optgroup(_(L("Size and coordinates"))); + auto page = add_options_page(_(L("General")), "printer"); + auto optgroup = page->new_optgroup(_(L("Size and coordinates"))); Line line = optgroup->create_single_option_line("bed_shape");//{ _(L("Bed shape")), "" }; - line.widget = [this](wxWindow* parent) { + line.widget = [this](wxWindow* parent) { ScalableButton* btn; add_scaled_button(parent, &btn, "printer_white", " " + _(L("Set")) + " " + dots, wxBU_LEFT | wxBU_EXACTFIT); btn->SetFont(wxGetApp().normal_font()); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(btn); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn); - btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) - { - auto dlg = new BedShapeDialog(this); - dlg->build_dialog(m_config->option("bed_shape")); - if (dlg->ShowModal() == wxID_OK) { - load_key_value("bed_shape", dlg->GetValue()); - update_changed_ui(); - } - })); + btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) + { + BedShapeDialog dlg(this); + dlg.build_dialog(*m_config->option("bed_shape"), + *m_config->option("bed_custom_texture"), + *m_config->option("bed_custom_model")); + if (dlg.ShowModal() == wxID_OK) { + const std::vector& shape = dlg.get_shape(); + const std::string& custom_texture = dlg.get_custom_texture(); + const std::string& custom_model = dlg.get_custom_model(); + if (!shape.empty()) + { + load_key_value("bed_shape", shape); + load_key_value("bed_custom_texture", custom_texture); + load_key_value("bed_custom_model", custom_model); + update_changed_ui(); + } + } + })); - return sizer; - }; - optgroup->append_line(line, &m_colored_Label); + return sizer; + }; + optgroup->append_line(line, &m_colored_Label); optgroup->append_single_option_line("max_print_height"); optgroup->append_single_option_line("z_offset"); - optgroup = page->new_optgroup(_(L("Capabilities"))); - ConfigOptionDef def; - def.type = coInt, - def.set_default_value(new ConfigOptionInt(1)); - def.label = L("Extruders"); - def.tooltip = L("Number of extruders of the printer."); - def.min = 1; + optgroup = page->new_optgroup(_(L("Capabilities"))); + ConfigOptionDef def; + def.type = coInt, + def.set_default_value(new ConfigOptionInt(1)); + def.label = L("Extruders"); + def.tooltip = L("Number of extruders of the printer."); + def.min = 1; def.mode = comExpert; - Option option(def, "extruders_count"); - optgroup->append_single_option_line(option); - optgroup->append_single_option_line("single_extruder_multi_material"); + Option option(def, "extruders_count"); + optgroup->append_single_option_line(option); + optgroup->append_single_option_line("single_extruder_multi_material"); - optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value) { - size_t extruders_count = boost::any_cast(optgroup->get_value("extruders_count")); - wxTheApp->CallAfter([this, opt_key, value, extruders_count]() { - if (opt_key == "extruders_count" || opt_key == "single_extruder_multi_material") { - extruders_count_changed(extruders_count); + optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value) { + size_t extruders_count = boost::any_cast(optgroup->get_value("extruders_count")); + wxTheApp->CallAfter([this, opt_key, value, extruders_count]() { + if (opt_key == "extruders_count" || opt_key == "single_extruder_multi_material") { + extruders_count_changed(extruders_count); init_options_list(); // m_options_list should be updated before UI updating - update_dirty(); - if (opt_key == "single_extruder_multi_material") // the single_extruder_multimaterial was added to force pages + update_dirty(); + if (opt_key == "single_extruder_multi_material") { // the single_extruder_multimaterial was added to force pages on_value_change(opt_key, value); // rebuild - let's make sure the on_value_change is not skipped - } - else { - update_dirty(); - on_value_change(opt_key, value); - } - }); - }; + + if (boost::any_cast(value) && m_extruders_count > 1) { + SuppressBackgroundProcessingUpdate sbpu; + std::vector nozzle_diameters = static_cast(m_config->option("nozzle_diameter"))->values; + const double frst_diam = nozzle_diameters[0]; + + for (auto cur_diam : nozzle_diameters) { + // if value is differs from first nozzle diameter value + if (fabs(cur_diam - frst_diam) > EPSILON) { + const wxString msg_text = _(L("Single Extruder Multi Material is selected, \n" + "and all extruders must have the same diameter.\n" + "Do you want to change the diameter for all extruders to first extruder nozzle diameter value?")); + wxMessageDialog dialog(parent(), msg_text, _(L("Nozzle diameter")), wxICON_WARNING | wxYES_NO); + + DynamicPrintConfig new_conf = *m_config; + if (dialog.ShowModal() == wxID_YES) { + for (size_t i = 1; i < nozzle_diameters.size(); i++) + nozzle_diameters[i] = frst_diam; + + new_conf.set_key_value("nozzle_diameter", new ConfigOptionFloats(nozzle_diameters)); + } + else + new_conf.set_key_value("single_extruder_multi_material", new ConfigOptionBool(false)); + + load_config(new_conf); + break; + } + } + } + } + } + else { + update_dirty(); + on_value_change(opt_key, value); + } + }); + }; #if 0 - if (!m_no_controller) - { - optgroup = page->new_optgroup(_(L("USB/Serial connection"))); - line = {_(L("Serial port")), ""}; - Option serial_port = optgroup->get_option("serial_port"); - serial_port.side_widget = ([this](wxWindow* parent) { - auto btn = new wxBitmapButton(parent, wxID_ANY, wxBitmap(from_u8(Slic3r::var("arrow_rotate_clockwise.png")), wxBITMAP_TYPE_PNG), - wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); - btn->SetToolTip(_(L("Rescan serial ports"))); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(btn); + if (!m_no_controller) + { + optgroup = page->new_optgroup(_(L("USB/Serial connection"))); + line = {_(L("Serial port")), ""}; + Option serial_port = optgroup->get_option("serial_port"); + serial_port.side_widget = ([this](wxWindow* parent) { + auto btn = new wxBitmapButton(parent, wxID_ANY, wxBitmap(from_u8(Slic3r::var("arrow_rotate_clockwise.png")), wxBITMAP_TYPE_PNG), + wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); + btn->SetToolTip(_(L("Rescan serial ports"))); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn); - btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent e) {update_serial_ports(); }); - return sizer; - }); - auto serial_test = [this](wxWindow* parent) { - auto btn = m_serial_test_btn = new wxButton(parent, wxID_ANY, - _(L("Test")), wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); - btn->SetFont(Slic3r::GUI::small_font()); - btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("wrench.png")), wxBITMAP_TYPE_PNG)); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(btn); + btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent e) {update_serial_ports(); }); + return sizer; + }); + auto serial_test = [this](wxWindow* parent) { + auto btn = m_serial_test_btn = new wxButton(parent, wxID_ANY, + _(L("Test")), wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); + btn->SetFont(Slic3r::GUI::small_font()); + btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("wrench.png")), wxBITMAP_TYPE_PNG)); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn); - btn->Bind(wxEVT_BUTTON, [this, parent](wxCommandEvent e) { - auto sender = Slic3r::make_unique(); - auto res = sender->connect( - m_config->opt_string("serial_port"), - m_config->opt_int("serial_speed") - ); - if (res && sender->wait_connected()) { - show_info(parent, _(L("Connection to printer works correctly.")), _(L("Success!"))); - } - else { - show_error(parent, _(L("Connection failed."))); - } - }); - return sizer; - }; + btn->Bind(wxEVT_BUTTON, [this, parent](wxCommandEvent e) { + auto sender = Slic3r::make_unique(); + auto res = sender->connect( + m_config->opt_string("serial_port"), + m_config->opt_int("serial_speed") + ); + if (res && sender->wait_connected()) { + show_info(parent, _(L("Connection to printer works correctly.")), _(L("Success!"))); + } + else { + show_error(parent, _(L("Connection failed."))); + } + }); + return sizer; + }; - line.append_option(serial_port); - line.append_option(optgroup->get_option("serial_speed")); - line.append_widget(serial_test); - optgroup->append_line(line); - } + line.append_option(serial_port); + line.append_option(optgroup->get_option("serial_speed")); + line.append_widget(serial_test); + optgroup->append_line(line); + } #endif - optgroup = page->new_optgroup(_(L("Print Host upload"))); - build_printhost(optgroup.get()); + optgroup = page->new_optgroup(_(L("Print Host upload"))); + build_printhost(optgroup.get()); - optgroup = page->new_optgroup(_(L("Firmware"))); - optgroup->append_single_option_line("gcode_flavor"); - optgroup->append_single_option_line("silent_mode"); - optgroup->append_single_option_line("remaining_times"); + optgroup = page->new_optgroup(_(L("Firmware"))); + optgroup->append_single_option_line("gcode_flavor"); + optgroup->append_single_option_line("silent_mode"); + optgroup->append_single_option_line("remaining_times"); - optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value) { - wxTheApp->CallAfter([this, opt_key, value]() { - if (opt_key == "silent_mode") { - bool val = boost::any_cast(value); - if (m_use_silent_mode != val) { - m_rebuild_kinematics_page = true; - m_use_silent_mode = val; - } - } - build_unregular_pages(); - update_dirty(); - on_value_change(opt_key, value); - }); - }; + optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value) { + wxTheApp->CallAfter([this, opt_key, value]() { + if (opt_key == "silent_mode") { + bool val = boost::any_cast(value); + if (m_use_silent_mode != val) { + m_rebuild_kinematics_page = true; + m_use_silent_mode = val; + } + } + build_unregular_pages(); + update_dirty(); + on_value_change(opt_key, value); + }); + }; - optgroup = page->new_optgroup(_(L("Advanced"))); - optgroup->append_single_option_line("use_relative_e_distances"); - optgroup->append_single_option_line("use_firmware_retraction"); - optgroup->append_single_option_line("use_volumetric_e"); - optgroup->append_single_option_line("variable_layer_height"); + optgroup = page->new_optgroup(_(L("Advanced"))); + optgroup->append_single_option_line("use_relative_e_distances"); + optgroup->append_single_option_line("use_firmware_retraction"); + optgroup->append_single_option_line("use_volumetric_e"); + optgroup->append_single_option_line("variable_layer_height"); const int gcode_field_height = 15; // 150 const int notes_field_height = 25; // 250 - page = add_options_page(_(L("Custom G-code")), "cog"); - optgroup = page->new_optgroup(_(L("Start G-code")), 0); - option = optgroup->get_option("start_gcode"); - option.opt.full_width = true; + page = add_options_page(_(L("Custom G-code")), "cog"); + optgroup = page->new_optgroup(_(L("Start G-code")), 0); + option = optgroup->get_option("start_gcode"); + option.opt.full_width = true; option.opt.height = gcode_field_height;//150; - optgroup->append_single_option_line(option); + optgroup->append_single_option_line(option); - optgroup = page->new_optgroup(_(L("End G-code")), 0); - option = optgroup->get_option("end_gcode"); - option.opt.full_width = true; + optgroup = page->new_optgroup(_(L("End G-code")), 0); + option = optgroup->get_option("end_gcode"); + option.opt.full_width = true; option.opt.height = gcode_field_height;//150; - optgroup->append_single_option_line(option); + optgroup->append_single_option_line(option); - optgroup = page->new_optgroup(_(L("Before layer change G-code")), 0); - option = optgroup->get_option("before_layer_gcode"); - option.opt.full_width = true; + optgroup = page->new_optgroup(_(L("Before layer change G-code")), 0); + option = optgroup->get_option("before_layer_gcode"); + option.opt.full_width = true; option.opt.height = gcode_field_height;//150; - optgroup->append_single_option_line(option); + optgroup->append_single_option_line(option); - optgroup = page->new_optgroup(_(L("After layer change G-code")), 0); - option = optgroup->get_option("layer_gcode"); - option.opt.full_width = true; + optgroup = page->new_optgroup(_(L("After layer change G-code")), 0); + option = optgroup->get_option("layer_gcode"); + option.opt.full_width = true; option.opt.height = gcode_field_height;//150; - optgroup->append_single_option_line(option); + optgroup->append_single_option_line(option); - optgroup = page->new_optgroup(_(L("Tool change G-code")), 0); - option = optgroup->get_option("toolchange_gcode"); - option.opt.full_width = true; + optgroup = page->new_optgroup(_(L("Tool change G-code")), 0); + option = optgroup->get_option("toolchange_gcode"); + option.opt.full_width = true; option.opt.height = gcode_field_height;//150; - optgroup->append_single_option_line(option); + optgroup->append_single_option_line(option); - optgroup = page->new_optgroup(_(L("Between objects G-code (for sequential printing)")), 0); - option = optgroup->get_option("between_objects_gcode"); - option.opt.full_width = true; + optgroup = page->new_optgroup(_(L("Between objects G-code (for sequential printing)")), 0); + option = optgroup->get_option("between_objects_gcode"); + option.opt.full_width = true; option.opt.height = gcode_field_height;//150; - optgroup->append_single_option_line(option); - - page = add_options_page(_(L("Notes")), "note.png"); - optgroup = page->new_optgroup(_(L("Notes")), 0); - option = optgroup->get_option("printer_notes"); - option.opt.full_width = true; + optgroup->append_single_option_line(option); + + page = add_options_page(_(L("Notes")), "note.png"); + optgroup = page->new_optgroup(_(L("Notes")), 0); + option = optgroup->get_option("printer_notes"); + option.opt.full_width = true; option.opt.height = notes_field_height;//250; - optgroup->append_single_option_line(option); + optgroup->append_single_option_line(option); - page = add_options_page(_(L("Dependencies")), "wrench.png"); - optgroup = page->new_optgroup(_(L("Profile dependencies"))); - line = Line{ "", "" }; - line.full_width = 1; - line.widget = [this](wxWindow* parent) { - return description_line_widget(parent, &m_parent_preset_description_line); - }; - optgroup->append_line(line); + page = add_options_page(_(L("Dependencies")), "wrench.png"); + optgroup = page->new_optgroup(_(L("Profile dependencies"))); + line = Line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_parent_preset_description_line); + }; + optgroup->append_line(line); - build_unregular_pages(); + build_unregular_pages(); #if 0 - if (!m_no_controller) - update_serial_ports(); + if (!m_no_controller) + update_serial_ports(); #endif } @@ -2056,11 +2230,21 @@ void TabPrinter::build_sla() btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { - auto dlg = new BedShapeDialog(this); - dlg->build_dialog(m_config->option("bed_shape")); - if (dlg->ShowModal() == wxID_OK) { - load_key_value("bed_shape", dlg->GetValue()); - update_changed_ui(); + BedShapeDialog dlg(this); + dlg.build_dialog(*m_config->option("bed_shape"), + *m_config->option("bed_custom_texture"), + *m_config->option("bed_custom_model")); + if (dlg.ShowModal() == wxID_OK) { + const std::vector& shape = dlg.get_shape(); + const std::string& custom_texture = dlg.get_custom_texture(); + const std::string& custom_model = dlg.get_custom_model(); + if (!shape.empty()) + { + load_key_value("bed_shape", shape); + load_key_value("bed_custom_texture", custom_texture); + load_key_value("bed_custom_model", custom_model); + update_changed_ui(); + } } })); @@ -2080,6 +2264,10 @@ void TabPrinter::build_sla() optgroup->append_line(line); optgroup->append_single_option_line("display_orientation"); + // FIXME: This should be on one line in the UI + optgroup->append_single_option_line("display_mirror_x"); + optgroup->append_single_option_line("display_mirror_y"); + optgroup = page->new_optgroup(_(L("Tilt"))); line = { _(L("Tilt time")), "" }; line.append_option(optgroup->get_option("fast_tilt_time")); @@ -2102,6 +2290,12 @@ void TabPrinter::build_sla() optgroup->append_single_option_line("absolute_correction"); optgroup->append_single_option_line("gamma_correction"); + optgroup = page->new_optgroup(_(L("Exposure"))); + optgroup->append_single_option_line("min_exposure_time"); + optgroup->append_single_option_line("max_exposure_time"); + optgroup->append_single_option_line("min_initial_exposure_time"); + optgroup->append_single_option_line("max_initial_exposure_time"); + optgroup = page->new_optgroup(_(L("Print Host upload"))); build_printhost(optgroup.get()); @@ -2126,25 +2320,28 @@ void TabPrinter::build_sla() void TabPrinter::update_serial_ports() { - Field *field = get_field("serial_port"); - Choice *choice = static_cast(field); - choice->set_values(Utils::scan_serial_ports()); + Field *field = get_field("serial_port"); + Choice *choice = static_cast(field); + choice->set_values(Utils::scan_serial_ports()); } void TabPrinter::extruders_count_changed(size_t extruders_count) { bool is_count_changed = false; if (m_extruders_count != extruders_count) { - m_extruders_count = extruders_count; - m_preset_bundle->printers.get_edited_preset().set_num_extruders(extruders_count); - m_preset_bundle->update_multi_material_filament_presets(); + m_extruders_count = extruders_count; + m_preset_bundle->printers.get_edited_preset().set_num_extruders(extruders_count); + m_preset_bundle->update_multi_material_filament_presets(); is_count_changed = true; } + else if (m_extruders_count == 1 && + m_preset_bundle->project_config.option("wiping_volumes_matrix")->values.size()>1) + m_preset_bundle->update_multi_material_filament_presets(); - /* This function should be call in any case because of correct updating/rebuilding + /* This function should be call in any case because of correct updating/rebuilding * of unregular pages of a Printer Settings */ - build_unregular_pages(); + build_unregular_pages(); if (is_count_changed) { on_value_change("extruders_count", extruders_count); @@ -2154,81 +2351,81 @@ void TabPrinter::extruders_count_changed(size_t extruders_count) void TabPrinter::append_option_line(ConfigOptionsGroupShp optgroup, const std::string opt_key) { - auto option = optgroup->get_option(opt_key, 0); - auto line = Line{ _(option.opt.full_label), "" }; - line.append_option(option); - if (m_use_silent_mode) - line.append_option(optgroup->get_option(opt_key, 1)); - optgroup->append_line(line); + auto option = optgroup->get_option(opt_key, 0); + auto line = Line{ _(option.opt.full_label), "" }; + line.append_option(option); + if (m_use_silent_mode) + line.append_option(optgroup->get_option(opt_key, 1)); + optgroup->append_line(line); } PageShp TabPrinter::build_kinematics_page() { - auto page = add_options_page(_(L("Machine limits")), "cog", true); + auto page = add_options_page(_(L("Machine limits")), "cog", true); - if (m_use_silent_mode) { - // Legend for OptionsGroups - auto optgroup = page->new_optgroup(""); - optgroup->set_show_modified_btns_val(false); + if (m_use_silent_mode) { + // Legend for OptionsGroups + auto optgroup = page->new_optgroup(""); + optgroup->set_show_modified_btns_val(false); optgroup->label_width = 23;// 230; - auto line = Line{ "", "" }; + auto line = Line{ "", "" }; - ConfigOptionDef def; - def.type = coString; - def.width = 15; - def.gui_type = "legend"; + ConfigOptionDef def; + def.type = coString; + def.width = 15; + def.gui_type = "legend"; def.mode = comAdvanced; - def.tooltip = L("Values in this column are for Normal mode"); - def.set_default_value(new ConfigOptionString{ _(L("Normal")).ToUTF8().data() }); + def.tooltip = L("Values in this column are for Normal mode"); + def.set_default_value(new ConfigOptionString{ _(L("Normal")).ToUTF8().data() }); - auto option = Option(def, "full_power_legend"); - line.append_option(option); + auto option = Option(def, "full_power_legend"); + line.append_option(option); - def.tooltip = L("Values in this column are for Stealth mode"); - def.set_default_value(new ConfigOptionString{ _(L("Stealth")).ToUTF8().data() }); - option = Option(def, "silent_legend"); - line.append_option(option); + def.tooltip = L("Values in this column are for Stealth mode"); + def.set_default_value(new ConfigOptionString{ _(L("Stealth")).ToUTF8().data() }); + option = Option(def, "silent_legend"); + line.append_option(option); - optgroup->append_line(line); - } + optgroup->append_line(line); + } - std::vector axes{ "x", "y", "z", "e" }; - auto optgroup = page->new_optgroup(_(L("Maximum feedrates"))); - for (const std::string &axis : axes) { - append_option_line(optgroup, "machine_max_feedrate_" + axis); - } + std::vector axes{ "x", "y", "z", "e" }; + auto optgroup = page->new_optgroup(_(L("Maximum feedrates"))); + for (const std::string &axis : axes) { + append_option_line(optgroup, "machine_max_feedrate_" + axis); + } - optgroup = page->new_optgroup(_(L("Maximum accelerations"))); - for (const std::string &axis : axes) { - append_option_line(optgroup, "machine_max_acceleration_" + axis); - } - append_option_line(optgroup, "machine_max_acceleration_extruding"); - append_option_line(optgroup, "machine_max_acceleration_retracting"); + optgroup = page->new_optgroup(_(L("Maximum accelerations"))); + for (const std::string &axis : axes) { + append_option_line(optgroup, "machine_max_acceleration_" + axis); + } + append_option_line(optgroup, "machine_max_acceleration_extruding"); + append_option_line(optgroup, "machine_max_acceleration_retracting"); - optgroup = page->new_optgroup(_(L("Jerk limits"))); - for (const std::string &axis : axes) { - append_option_line(optgroup, "machine_max_jerk_" + axis); - } + optgroup = page->new_optgroup(_(L("Jerk limits"))); + for (const std::string &axis : axes) { + append_option_line(optgroup, "machine_max_jerk_" + axis); + } - optgroup = page->new_optgroup(_(L("Minimum feedrates"))); - append_option_line(optgroup, "machine_min_extruding_rate"); - append_option_line(optgroup, "machine_min_travel_rate"); + optgroup = page->new_optgroup(_(L("Minimum feedrates"))); + append_option_line(optgroup, "machine_min_extruding_rate"); + append_option_line(optgroup, "machine_min_travel_rate"); - return page; + return page; } /* Previous name build_extruder_pages(). - * - * This function was renamed because of now it implements not just an extruder pages building, - * but "Machine limits" and "Single extruder MM setup" too + * + * This function was renamed because of now it implements not just an extruder pages building, + * but "Machine limits" and "Single extruder MM setup" too * (These pages can changes according to the another values of a current preset) * */ void TabPrinter::build_unregular_pages() { - size_t n_before_extruders = 2; // Count of pages before Extruder pages - bool is_marlin_flavor = m_config->option>("gcode_flavor")->value == gcfMarlin; + size_t n_before_extruders = 2; // Count of pages before Extruder pages + bool is_marlin_flavor = m_config->option>("gcode_flavor")->value == gcfMarlin; - /* ! Freeze/Thaw in this function is needed to avoid call OnPaint() for erased pages + /* ! Freeze/Thaw in this function is needed to avoid call OnPaint() for erased pages * and be cause of application crash, when try to change Preset in moment, * when one of unregular pages is selected. * */ @@ -2246,108 +2443,169 @@ void TabPrinter::build_unregular_pages() }; #endif //__WXMSW__ - // Add/delete Kinematics page according to is_marlin_flavor - size_t existed_page = 0; - for (int i = n_before_extruders; i < m_pages.size(); ++i) // first make sure it's not there already - if (m_pages[i]->title().find(_(L("Machine limits"))) != std::string::npos) { - if (!is_marlin_flavor || m_rebuild_kinematics_page) - m_pages.erase(m_pages.begin() + i); - else - existed_page = i; - break; - } + // Add/delete Kinematics page according to is_marlin_flavor + size_t existed_page = 0; + for (int i = n_before_extruders; i < m_pages.size(); ++i) // first make sure it's not there already + if (m_pages[i]->title().find(_(L("Machine limits"))) != std::string::npos) { + if (!is_marlin_flavor || m_rebuild_kinematics_page) + m_pages.erase(m_pages.begin() + i); + else + existed_page = i; + break; + } - if (existed_page < n_before_extruders && is_marlin_flavor) { - auto page = build_kinematics_page(); + if (existed_page < n_before_extruders && is_marlin_flavor) { + auto page = build_kinematics_page(); #ifdef __WXMSW__ - layout_page(page); + layout_page(page); #endif - m_pages.insert(m_pages.begin() + n_before_extruders, page); - } + m_pages.insert(m_pages.begin() + n_before_extruders, page); + } - if (is_marlin_flavor) - n_before_extruders++; - size_t n_after_single_extruder_MM = 2; // Count of pages after single_extruder_multi_material page + if (is_marlin_flavor) + n_before_extruders++; + size_t n_after_single_extruder_MM = 2; // Count of pages after single_extruder_multi_material page - if (m_extruders_count_old == m_extruders_count || - (m_has_single_extruder_MM_page && m_extruders_count == 1)) - { - // if we have a single extruder MM setup, add a page with configuration options: - for (int i = 0; i < m_pages.size(); ++i) // first make sure it's not there already - if (m_pages[i]->title().find(_(L("Single extruder MM setup"))) != std::string::npos) { - m_pages.erase(m_pages.begin() + i); - break; - } - m_has_single_extruder_MM_page = false; - } - if (m_extruders_count > 1 && m_config->opt_bool("single_extruder_multi_material") && !m_has_single_extruder_MM_page) { - // create a page, but pretend it's an extruder page, so we can add it to m_pages ourselves - auto page = add_options_page(_(L("Single extruder MM setup")), "printer", true); - auto optgroup = page->new_optgroup(_(L("Single extruder multimaterial parameters"))); - optgroup->append_single_option_line("cooling_tube_retraction"); - optgroup->append_single_option_line("cooling_tube_length"); - optgroup->append_single_option_line("parking_pos_retraction"); + if (m_extruders_count_old == m_extruders_count || + (m_has_single_extruder_MM_page && m_extruders_count == 1)) + { + // if we have a single extruder MM setup, add a page with configuration options: + for (int i = 0; i < m_pages.size(); ++i) // first make sure it's not there already + if (m_pages[i]->title().find(_(L("Single extruder MM setup"))) != std::string::npos) { + m_pages.erase(m_pages.begin() + i); + break; + } + m_has_single_extruder_MM_page = false; + } + if (m_extruders_count > 1 && m_config->opt_bool("single_extruder_multi_material") && !m_has_single_extruder_MM_page) { + // create a page, but pretend it's an extruder page, so we can add it to m_pages ourselves + auto page = add_options_page(_(L("Single extruder MM setup")), "printer", true); + auto optgroup = page->new_optgroup(_(L("Single extruder multimaterial parameters"))); + optgroup->append_single_option_line("cooling_tube_retraction"); + optgroup->append_single_option_line("cooling_tube_length"); + optgroup->append_single_option_line("parking_pos_retraction"); optgroup->append_single_option_line("extra_loading_move"); optgroup->append_single_option_line("high_current_on_filament_swap"); - m_pages.insert(m_pages.end() - n_after_single_extruder_MM, page); - m_has_single_extruder_MM_page = true; - } - + m_pages.insert(m_pages.end() - n_after_single_extruder_MM, page); + m_has_single_extruder_MM_page = true; + } + // Build missed extruder pages - for (auto extruder_idx = m_extruders_count_old; extruder_idx < m_extruders_count; ++extruder_idx) { - //# build page + for (auto extruder_idx = m_extruders_count_old; extruder_idx < m_extruders_count; ++extruder_idx) { + //# build page const wxString& page_name = wxString::Format(_(L("Extruder %d")), int(extruder_idx + 1)); auto page = add_options_page(page_name, "funnel", true); - m_pages.insert(m_pages.begin() + n_before_extruders + extruder_idx, page); - - auto optgroup = page->new_optgroup(_(L("Size"))); - optgroup->append_single_option_line("nozzle_diameter", extruder_idx); - - optgroup = page->new_optgroup(_(L("Layer height limits"))); - optgroup->append_single_option_line("min_layer_height", extruder_idx); - optgroup->append_single_option_line("max_layer_height", extruder_idx); - - - optgroup = page->new_optgroup(_(L("Position (for multi-extruder printers)"))); - optgroup->append_single_option_line("extruder_offset", extruder_idx); - - optgroup = page->new_optgroup(_(L("Retraction"))); - optgroup->append_single_option_line("retract_length", extruder_idx); - optgroup->append_single_option_line("retract_lift", extruder_idx); - Line line = { _(L("Only lift Z")), "" }; - line.append_option(optgroup->get_option("retract_lift_above", extruder_idx)); - line.append_option(optgroup->get_option("retract_lift_below", extruder_idx)); - optgroup->append_line(line); - - optgroup->append_single_option_line("retract_speed", extruder_idx); - optgroup->append_single_option_line("deretract_speed", extruder_idx); - optgroup->append_single_option_line("retract_restart_extra", extruder_idx); - optgroup->append_single_option_line("retract_before_travel", extruder_idx); - optgroup->append_single_option_line("retract_layer_change", extruder_idx); - optgroup->append_single_option_line("wipe", extruder_idx); - optgroup->append_single_option_line("retract_before_wipe", extruder_idx); - - optgroup = page->new_optgroup(_(L("Retraction when tool is disabled (advanced settings for multi-extruder setups)"))); - optgroup->append_single_option_line("retract_length_toolchange", extruder_idx); - optgroup->append_single_option_line("retract_restart_extra_toolchange", extruder_idx); + m_pages.insert(m_pages.begin() + n_before_extruders + extruder_idx, page); - optgroup = page->new_optgroup(_(L("Preview"))); - optgroup->append_single_option_line("extruder_colour", extruder_idx); + auto optgroup = page->new_optgroup(_(L("Size"))); + optgroup->append_single_option_line("nozzle_diameter", extruder_idx); + + optgroup->m_on_change = [this, extruder_idx](const t_config_option_key& opt_key, boost::any value) + { + if (m_config->opt_bool("single_extruder_multi_material") && m_extruders_count > 1 && opt_key.find_first_of("nozzle_diameter") != std::string::npos) + { + SuppressBackgroundProcessingUpdate sbpu; + const double new_nd = boost::any_cast(value); + std::vector nozzle_diameters = static_cast(m_config->option("nozzle_diameter"))->values; + + // if value was changed + if (fabs(nozzle_diameters[extruder_idx == 0 ? 1 : 0] - new_nd) > EPSILON) + { + const wxString msg_text = _(L("This is a single extruder multimaterial printer, diameters of all extruders " + "will be set to the new value. Do you want to proceed?")); + wxMessageDialog dialog(parent(), msg_text, _(L("Nozzle diameter")), wxICON_WARNING | wxYES_NO); + + DynamicPrintConfig new_conf = *m_config; + if (dialog.ShowModal() == wxID_YES) { + for (size_t i = 0; i < nozzle_diameters.size(); i++) { + if (i==extruder_idx) + continue; + nozzle_diameters[i] = new_nd; + } + } + else + nozzle_diameters[extruder_idx] = nozzle_diameters[extruder_idx == 0 ? 1 : 0]; + + new_conf.set_key_value("nozzle_diameter", new ConfigOptionFloats(nozzle_diameters)); + load_config(new_conf); + } + } + + update_dirty(); + update(); + }; + + optgroup = page->new_optgroup(_(L("Layer height limits"))); + optgroup->append_single_option_line("min_layer_height", extruder_idx); + optgroup->append_single_option_line("max_layer_height", extruder_idx); + + + optgroup = page->new_optgroup(_(L("Position (for multi-extruder printers)"))); + optgroup->append_single_option_line("extruder_offset", extruder_idx); + + optgroup = page->new_optgroup(_(L("Retraction"))); + optgroup->append_single_option_line("retract_length", extruder_idx); + optgroup->append_single_option_line("retract_lift", extruder_idx); + Line line = { _(L("Only lift Z")), "" }; + line.append_option(optgroup->get_option("retract_lift_above", extruder_idx)); + line.append_option(optgroup->get_option("retract_lift_below", extruder_idx)); + optgroup->append_line(line); + + optgroup->append_single_option_line("retract_speed", extruder_idx); + optgroup->append_single_option_line("deretract_speed", extruder_idx); + optgroup->append_single_option_line("retract_restart_extra", extruder_idx); + optgroup->append_single_option_line("retract_before_travel", extruder_idx); + optgroup->append_single_option_line("retract_layer_change", extruder_idx); + optgroup->append_single_option_line("wipe", extruder_idx); + optgroup->append_single_option_line("retract_before_wipe", extruder_idx); + + optgroup = page->new_optgroup(_(L("Retraction when tool is disabled (advanced settings for multi-extruder setups)"))); + optgroup->append_single_option_line("retract_length_toolchange", extruder_idx); + optgroup->append_single_option_line("retract_restart_extra_toolchange", extruder_idx); + + optgroup = page->new_optgroup(_(L("Preview"))); + + auto reset_to_filament_color = [this, extruder_idx](wxWindow* parent) { + add_scaled_button(parent, &m_reset_to_filament_color, "undo", + _(L("Reset to Filament Color")), wxBU_LEFT | wxBU_EXACTFIT); + ScalableButton* btn = m_reset_to_filament_color; + btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn); + + btn->Bind(wxEVT_BUTTON, [this, extruder_idx](wxCommandEvent& e) + { + std::vector colors = static_cast(m_config->option("extruder_colour"))->values; + colors[extruder_idx] = ""; + + DynamicPrintConfig new_conf = *m_config; + new_conf.set_key_value("extruder_colour", new ConfigOptionStrings(colors)); + load_config(new_conf); + + update_dirty(); + update(); + }); + + return sizer; + }; + line = optgroup->create_single_option_line("extruder_colour", extruder_idx); + line.append_widget(reset_to_filament_color); + optgroup->append_line(line); #ifdef __WXMSW__ - layout_page(page); + layout_page(page); #endif - } - - // # remove extra pages - if (m_extruders_count < m_extruders_count_old) - m_pages.erase( m_pages.begin() + n_before_extruders + m_extruders_count, - m_pages.begin() + n_before_extruders + m_extruders_count_old); + } + + // # remove extra pages + if (m_extruders_count < m_extruders_count_old) + m_pages.erase( m_pages.begin() + n_before_extruders + m_extruders_count, + m_pages.begin() + n_before_extruders + m_extruders_count_old); Thaw(); - m_extruders_count_old = m_extruders_count; - rebuild_page_tree(); + m_extruders_count_old = m_extruders_count; + rebuild_page_tree(); // Reload preset pages with current configuration values reload_config(); @@ -2356,12 +2614,12 @@ void TabPrinter::build_unregular_pages() // this gets executed after preset is loaded and before GUI fields are updated void TabPrinter::on_preset_loaded() { - // update the extruders count field - auto *nozzle_diameter = dynamic_cast(m_config->option("nozzle_diameter")); - int extruders_count = nozzle_diameter->values.size(); - set_value("extruders_count", extruders_count); - // update the GUI field according to the number of nozzle diameters supplied - extruders_count_changed(extruders_count); + // update the extruders count field + auto *nozzle_diameter = dynamic_cast(m_config->option("nozzle_diameter")); + int extruders_count = nozzle_diameter->values.size(); + set_value("extruders_count", extruders_count); + // update the GUI field according to the number of nozzle diameters supplied + extruders_count_changed(extruders_count); } void TabPrinter::update_pages() @@ -2397,7 +2655,7 @@ void TabPrinter::update_pages() wxGetApp().sidebar().update_objects_list_extruder_column(m_extruders_count); } - else + else m_pages_sla.empty() ? build_sla() : m_pages.swap(m_pages_sla); rebuild_page_tree(); @@ -2417,102 +2675,102 @@ void TabPrinter::update_fff() { // Freeze(); - bool en; - auto serial_speed = get_field("serial_speed"); - if (serial_speed != nullptr) { - en = !m_config->opt_string("serial_port").empty(); - get_field("serial_speed")->toggle(en); - if (m_config->opt_int("serial_speed") != 0 && en) - m_serial_test_btn->Enable(); - else - m_serial_test_btn->Disable(); - } + bool en; + auto serial_speed = get_field("serial_speed"); + if (serial_speed != nullptr) { + en = !m_config->opt_string("serial_port").empty(); + get_field("serial_speed")->toggle(en); + if (m_config->opt_int("serial_speed") != 0 && en) + m_serial_test_btn->Enable(); + else + m_serial_test_btn->Disable(); + } - { - std::unique_ptr host(PrintHost::get_print_host(m_config)); - m_print_host_test_btn->Enable(!m_config->opt_string("print_host").empty() && host->can_test()); - m_printhost_browse_btn->Enable(host->has_auto_discovery()); - } + { + std::unique_ptr host(PrintHost::get_print_host(m_config)); + m_print_host_test_btn->Enable(!m_config->opt_string("print_host").empty() && host->can_test()); + m_printhost_browse_btn->Enable(host->has_auto_discovery()); + } - bool have_multiple_extruders = m_extruders_count > 1; - get_field("toolchange_gcode")->toggle(have_multiple_extruders); - get_field("single_extruder_multi_material")->toggle(have_multiple_extruders); + bool have_multiple_extruders = m_extruders_count > 1; + get_field("toolchange_gcode")->toggle(have_multiple_extruders); + get_field("single_extruder_multi_material")->toggle(have_multiple_extruders); - bool is_marlin_flavor = m_config->option>("gcode_flavor")->value == gcfMarlin; + bool is_marlin_flavor = m_config->option>("gcode_flavor")->value == gcfMarlin; - { - Field *sm = get_field("silent_mode"); - if (! is_marlin_flavor) - // Disable silent mode for non-marlin firmwares. - get_field("silent_mode")->toggle(false); - if (is_marlin_flavor) - sm->enable(); - else - sm->disable(); - } + { + Field *sm = get_field("silent_mode"); + if (! is_marlin_flavor) + // Disable silent mode for non-marlin firmwares. + get_field("silent_mode")->toggle(false); + if (is_marlin_flavor) + sm->enable(); + else + sm->disable(); + } - if (m_use_silent_mode != m_config->opt_bool("silent_mode")) { - m_rebuild_kinematics_page = true; - m_use_silent_mode = m_config->opt_bool("silent_mode"); - } + if (m_use_silent_mode != m_config->opt_bool("silent_mode")) { + m_rebuild_kinematics_page = true; + m_use_silent_mode = m_config->opt_bool("silent_mode"); + } - for (size_t i = 0; i < m_extruders_count; ++i) { - bool have_retract_length = m_config->opt_float("retract_length", i) > 0; + for (size_t i = 0; i < m_extruders_count; ++i) { + bool have_retract_length = m_config->opt_float("retract_length", i) > 0; - // when using firmware retraction, firmware decides retraction length - bool use_firmware_retraction = m_config->opt_bool("use_firmware_retraction"); - get_field("retract_length", i)->toggle(!use_firmware_retraction); + // when using firmware retraction, firmware decides retraction length + bool use_firmware_retraction = m_config->opt_bool("use_firmware_retraction"); + get_field("retract_length", i)->toggle(!use_firmware_retraction); - // user can customize travel length if we have retraction length or we"re using - // firmware retraction - get_field("retract_before_travel", i)->toggle(have_retract_length || use_firmware_retraction); + // user can customize travel length if we have retraction length or we"re using + // firmware retraction + get_field("retract_before_travel", i)->toggle(have_retract_length || use_firmware_retraction); - // user can customize other retraction options if retraction is enabled - bool retraction = (have_retract_length || use_firmware_retraction); - std::vector vec = { "retract_lift", "retract_layer_change" }; - for (auto el : vec) - get_field(el, i)->toggle(retraction); + // user can customize other retraction options if retraction is enabled + bool retraction = (have_retract_length || use_firmware_retraction); + std::vector vec = { "retract_lift", "retract_layer_change" }; + for (auto el : vec) + get_field(el, i)->toggle(retraction); - // retract lift above / below only applies if using retract lift - vec.resize(0); - vec = { "retract_lift_above", "retract_lift_below" }; - for (auto el : vec) - get_field(el, i)->toggle(retraction && m_config->opt_float("retract_lift", i) > 0); + // retract lift above / below only applies if using retract lift + vec.resize(0); + vec = { "retract_lift_above", "retract_lift_below" }; + for (auto el : vec) + get_field(el, i)->toggle(retraction && m_config->opt_float("retract_lift", i) > 0); - // some options only apply when not using firmware retraction - vec.resize(0); - vec = { "retract_speed", "deretract_speed", "retract_before_wipe", "retract_restart_extra", "wipe" }; - for (auto el : vec) - get_field(el, i)->toggle(retraction && !use_firmware_retraction); + // some options only apply when not using firmware retraction + vec.resize(0); + vec = { "retract_speed", "deretract_speed", "retract_before_wipe", "retract_restart_extra", "wipe" }; + for (auto el : vec) + get_field(el, i)->toggle(retraction && !use_firmware_retraction); - bool wipe = m_config->opt_bool("wipe", i); - get_field("retract_before_wipe", i)->toggle(wipe); + bool wipe = m_config->opt_bool("wipe", i); + get_field("retract_before_wipe", i)->toggle(wipe); - if (use_firmware_retraction && wipe) { - auto dialog = new wxMessageDialog(parent(), - _(L("The Wipe option is not available when using the Firmware Retraction mode.\n" - "\nShall I disable it in order to enable Firmware Retraction?")), - _(L("Firmware Retraction")), wxICON_WARNING | wxYES | wxNO); + if (use_firmware_retraction && wipe) { + wxMessageDialog dialog(parent(), + _(L("The Wipe option is not available when using the Firmware Retraction mode.\n" + "\nShall I disable it in order to enable Firmware Retraction?")), + _(L("Firmware Retraction")), wxICON_WARNING | wxYES | wxNO); - DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_YES) { - auto wipe = static_cast(m_config->option("wipe")->clone()); - for (int w = 0; w < wipe->values.size(); w++) - wipe->values[w] = false; - new_conf.set_key_value("wipe", wipe); - } - else { - new_conf.set_key_value("use_firmware_retraction", new ConfigOptionBool(false)); - } - load_config(new_conf); - } + DynamicPrintConfig new_conf = *m_config; + if (dialog.ShowModal() == wxID_YES) { + auto wipe = static_cast(m_config->option("wipe")->clone()); + for (int w = 0; w < wipe->values.size(); w++) + wipe->values[w] = false; + new_conf.set_key_value("wipe", wipe); + } + else { + new_conf.set_key_value("use_firmware_retraction", new ConfigOptionBool(false)); + } + load_config(new_conf); + } - get_field("retract_length_toolchange", i)->toggle(have_multiple_extruders); + get_field("retract_length_toolchange", i)->toggle(have_multiple_extruders); - bool toolchange_retraction = m_config->opt_float("retract_length_toolchange", i) > 0; - get_field("retract_restart_extra_toolchange", i)->toggle - (have_multiple_extruders && toolchange_retraction); - } + bool toolchange_retraction = m_config->opt_float("retract_length_toolchange", i) > 0; + get_field("retract_restart_extra_toolchange", i)->toggle + (have_multiple_extruders && toolchange_retraction); + } // Thaw(); } @@ -2523,42 +2781,45 @@ void TabPrinter::update_sla() // Initialize the UI from the current preset void Tab::load_current_preset() { - const Preset& preset = m_presets->get_edited_preset(); + const Preset& preset = m_presets->get_edited_preset(); - (preset.is_default || preset.is_system) ? m_btn_delete_preset->Disable() : m_btn_delete_preset->Enable(true); + (preset.is_default || preset.is_system) ? m_btn_delete_preset->Disable() : m_btn_delete_preset->Enable(true); update(); - if (m_type == Slic3r::Preset::TYPE_PRINTER) { - // For the printer profile, generate the extruder pages. - if (preset.printer_technology() == ptFFF) - on_preset_loaded(); - else - wxGetApp().sidebar().update_objects_list_extruder_column(1); - } + if (m_type == Slic3r::Preset::TYPE_PRINTER) { + // For the printer profile, generate the extruder pages. + if (preset.printer_technology() == ptFFF) + on_preset_loaded(); + else + wxGetApp().sidebar().update_objects_list_extruder_column(1); + } // Reload preset pages with the new configuration values. reload_config(); - m_bmp_non_system = m_presets->get_selected_preset_parent() ? &m_bmp_value_unlock : &m_bmp_white_bullet; - m_ttg_non_system = m_presets->get_selected_preset_parent() ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; - m_tt_non_system = m_presets->get_selected_preset_parent() ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; + const Preset* selected_preset_parent = m_presets->get_selected_preset_parent(); + m_is_default_preset = selected_preset_parent != nullptr && selected_preset_parent->is_default; - m_undo_to_sys_btn->Enable(!preset.is_default); + m_bmp_non_system = selected_preset_parent ? &m_bmp_value_unlock : &m_bmp_white_bullet; + m_ttg_non_system = selected_preset_parent ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; + m_tt_non_system = selected_preset_parent ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; + +// m_undo_to_sys_btn->Enable(!preset.is_default); #if 0 - // use CallAfter because some field triggers schedule on_change calls using CallAfter, - // and we don't want them to be called after this update_dirty() as they would mark the - // preset dirty again - // (not sure this is true anymore now that update_dirty is idempotent) - wxTheApp->CallAfter([this] + // use CallAfter because some field triggers schedule on_change calls using CallAfter, + // and we don't want them to be called after this update_dirty() as they would mark the + // preset dirty again + // (not sure this is true anymore now that update_dirty is idempotent) + wxTheApp->CallAfter([this] #endif - { - // checking out if this Tab exists till this moment - if (!wxGetApp().checked_tab(this)) - return; - update_tab_ui(); + { + // checking out if this Tab exists till this moment + if (!wxGetApp().checked_tab(this)) + return; + update_tab_ui(); // update show/hide tabs - if (m_type == Slic3r::Preset::TYPE_PRINTER) { + if (m_type == Slic3r::Preset::TYPE_PRINTER) { const PrinterTechnology printer_technology = m_presets->get_edited_preset().printer_technology(); if (printer_technology != static_cast(this)->m_printer_technology) { @@ -2577,61 +2838,61 @@ void Tab::load_current_preset() int page_id = wxGetApp().tab_panel()->FindPage(tab); wxGetApp().tab_panel()->GetPage(page_id)->Show(false); wxGetApp().tab_panel()->RemovePage(page_id); - } + } } static_cast(this)->m_printer_technology = printer_technology; } - on_presets_changed(); - if (printer_technology == ptFFF) { - static_cast(this)->m_initial_extruders_count = static_cast(this)->m_extruders_count; - const Preset* parent_preset = m_presets->get_selected_preset_parent(); - static_cast(this)->m_sys_extruders_count = parent_preset == nullptr ? 0 : - static_cast(parent_preset->config.option("nozzle_diameter"))->values.size(); - } - } - else { - on_presets_changed(); + on_presets_changed(); + if (printer_technology == ptFFF) { + static_cast(this)->m_initial_extruders_count = static_cast(this)->m_extruders_count; + const Preset* parent_preset = m_presets->get_selected_preset_parent(); + static_cast(this)->m_sys_extruders_count = parent_preset == nullptr ? 0 : + static_cast(parent_preset->config.option("nozzle_diameter"))->values.size(); + } + } + else { + on_presets_changed(); if (m_type == Preset::TYPE_SLA_PRINT || m_type == Preset::TYPE_PRINT) - update_frequently_changed_parameters(); - } + update_frequently_changed_parameters(); + } - m_opt_status_value = (m_presets->get_selected_preset_parent() ? osSystemValue : 0) | osInitValue; - init_options_list(); + m_opt_status_value = (m_presets->get_selected_preset_parent() ? osSystemValue : 0) | osInitValue; + init_options_list(); update_visibility(); - update_changed_ui(); - } + update_changed_ui(); + } #if 0 - ); + ); #endif } //Regerenerate content of the page tree. void Tab::rebuild_page_tree() { - // get label of the currently selected item + // get label of the currently selected item const auto sel_item = m_treectrl->GetSelection(); - const auto selected = sel_item ? m_treectrl->GetItemText(sel_item) : ""; - const auto rootItem = m_treectrl->GetRootItem(); + const auto selected = sel_item ? m_treectrl->GetItemText(sel_item) : ""; + const auto rootItem = m_treectrl->GetRootItem(); - auto have_selection = 0; - m_treectrl->DeleteChildren(rootItem); - for (auto p : m_pages) - { - auto itemId = m_treectrl->AppendItem(rootItem, p->title(), p->iconID()); - m_treectrl->SetItemTextColour(itemId, p->get_item_colour()); - if (p->title() == selected) { - m_treectrl->SelectItem(itemId); - have_selection = 1; - } - } + auto have_selection = 0; + m_treectrl->DeleteChildren(rootItem); + for (auto p : m_pages) + { + auto itemId = m_treectrl->AppendItem(rootItem, p->title(), p->iconID()); + m_treectrl->SetItemTextColour(itemId, p->get_item_colour()); + if (p->title() == selected) { + m_treectrl->SelectItem(itemId); + have_selection = 1; + } + } - if (!have_selection) { - // this is triggered on first load, so we don't disable the sel change event - auto item = m_treectrl->GetFirstVisibleItem(); - if (item) { - m_treectrl->SelectItem(item); - } - } + if (!have_selection) { + // this is triggered on first load, so we don't disable the sel change event + auto item = m_treectrl->GetFirstVisibleItem(); + if (item) { + m_treectrl->SelectItem(item); + } + } } void Tab::update_page_tree_visibility() @@ -2669,38 +2930,38 @@ void Tab::update_page_tree_visibility() // If the current profile is modified, user is asked to save the changes. void Tab::select_preset(std::string preset_name, bool delete_current) { - if (preset_name.empty()) { - if (delete_current) { - // Find an alternate preset to be selected after the current preset is deleted. - const std::deque &presets = this->m_presets->get_presets(); - size_t idx_current = this->m_presets->get_idx_selected(); - // Find the next visible preset. - size_t idx_new = idx_current + 1; - if (idx_new < presets.size()) - for (; idx_new < presets.size() && ! presets[idx_new].is_visible; ++ idx_new) ; - if (idx_new == presets.size()) - for (idx_new = idx_current - 1; idx_new > 0 && ! presets[idx_new].is_visible; -- idx_new); - preset_name = presets[idx_new].name; - } else { - // If no name is provided, select the "-- default --" preset. - preset_name = m_presets->default_preset().name; - } - } - assert(! delete_current || (m_presets->get_edited_preset().name != preset_name && m_presets->get_edited_preset().is_user())); - bool current_dirty = ! delete_current && m_presets->current_is_dirty(); - bool print_tab = m_presets->type() == Preset::TYPE_PRINT || m_presets->type() == Preset::TYPE_SLA_PRINT; - bool printer_tab = m_presets->type() == Preset::TYPE_PRINTER; - bool canceled = false; - m_dependent_tabs = {}; - if (current_dirty && ! may_discard_current_dirty_preset()) { - canceled = true; - } else if (print_tab) { - // Before switching the print profile to a new one, verify, whether the currently active filament or SLA material - // are compatible with the new print. - // If it is not compatible and the current filament or SLA material are dirty, let user decide - // whether to discard the changes or keep the current print selection. - PrinterTechnology printer_technology = m_preset_bundle->printers.get_edited_preset().printer_technology(); - PresetCollection &dependent = (printer_technology == ptFFF) ? m_preset_bundle->filaments : m_preset_bundle->sla_materials; + if (preset_name.empty()) { + if (delete_current) { + // Find an alternate preset to be selected after the current preset is deleted. + const std::deque &presets = this->m_presets->get_presets(); + size_t idx_current = this->m_presets->get_idx_selected(); + // Find the next visible preset. + size_t idx_new = idx_current + 1; + if (idx_new < presets.size()) + for (; idx_new < presets.size() && ! presets[idx_new].is_visible; ++ idx_new) ; + if (idx_new == presets.size()) + for (idx_new = idx_current - 1; idx_new > 0 && ! presets[idx_new].is_visible; -- idx_new); + preset_name = presets[idx_new].name; + } else { + // If no name is provided, select the "-- default --" preset. + preset_name = m_presets->default_preset().name; + } + } + assert(! delete_current || (m_presets->get_edited_preset().name != preset_name && m_presets->get_edited_preset().is_user())); + bool current_dirty = ! delete_current && m_presets->current_is_dirty(); + bool print_tab = m_presets->type() == Preset::TYPE_PRINT || m_presets->type() == Preset::TYPE_SLA_PRINT; + bool printer_tab = m_presets->type() == Preset::TYPE_PRINTER; + bool canceled = false; + m_dependent_tabs = {}; + if (current_dirty && ! may_discard_current_dirty_preset()) { + canceled = true; + } else if (print_tab) { + // Before switching the print profile to a new one, verify, whether the currently active filament or SLA material + // are compatible with the new print. + // If it is not compatible and the current filament or SLA material are dirty, let user decide + // whether to discard the changes or keep the current print selection. + PrinterTechnology printer_technology = m_preset_bundle->printers.get_edited_preset().printer_technology(); + PresetCollection &dependent = (printer_technology == ptFFF) ? m_preset_bundle->filaments : m_preset_bundle->sla_materials; bool old_preset_dirty = dependent.current_is_dirty(); bool new_preset_compatible = dependent.get_edited_preset().is_compatible_with_print(*m_presets->find_preset(preset_name, true)); if (! canceled) @@ -2711,17 +2972,17 @@ void Tab::select_preset(std::string preset_name, bool delete_current) if (old_preset_dirty) dependent.discard_current_changes(); } - } else if (printer_tab) { - // Before switching the printer to a new one, verify, whether the currently active print and filament - // are compatible with the new printer. - // If they are not compatible and the current print or filament are dirty, let user decide - // whether to discard the changes or keep the current printer selection. - // - // With the introduction of the SLA printer types, we need to support switching between - // the FFF and SLA printers. - const Preset &new_printer_preset = *m_presets->find_preset(preset_name, true); - PrinterTechnology old_printer_technology = m_presets->get_edited_preset().printer_technology(); - PrinterTechnology new_printer_technology = new_printer_preset.printer_technology(); + } else if (printer_tab) { + // Before switching the printer to a new one, verify, whether the currently active print and filament + // are compatible with the new printer. + // If they are not compatible and the current print or filament are dirty, let user decide + // whether to discard the changes or keep the current printer selection. + // + // With the introduction of the SLA printer types, we need to support switching between + // the FFF and SLA printers. + const Preset &new_printer_preset = *m_presets->find_preset(preset_name, true); + PrinterTechnology old_printer_technology = m_presets->get_edited_preset().printer_technology(); + PrinterTechnology new_printer_technology = new_printer_preset.printer_technology(); if (new_printer_technology == ptSLA && old_printer_technology == ptFFF && !may_switch_to_SLA_preset()) canceled = true; else { @@ -2754,109 +3015,109 @@ void Tab::select_preset(std::string preset_name, bool delete_current) } } } - } + } - if (! canceled && delete_current) { - // Delete the file and select some other reasonable preset. - // It does not matter which preset will be made active as the preset will be re-selected from the preset_name variable. - // The 'external' presets will only be removed from the preset list, their files will not be deleted. - try { - m_presets->delete_current_preset(); - } catch (const std::exception & /* e */) { - //FIXME add some error reporting! - canceled = true; - } - } + if (! canceled && delete_current) { + // Delete the file and select some other reasonable preset. + // It does not matter which preset will be made active as the preset will be re-selected from the preset_name variable. + // The 'external' presets will only be removed from the preset list, their files will not be deleted. + try { + m_presets->delete_current_preset(); + } catch (const std::exception & /* e */) { + //FIXME add some error reporting! + canceled = true; + } + } - if (canceled) { - update_tab_ui(); - // Trigger the on_presets_changed event so that we also restore the previous value in the plater selector, - // if this action was initiated from the platter. - on_presets_changed(); - } else { - if (current_dirty) - m_presets->discard_current_changes(); + if (canceled) { + update_tab_ui(); + // Trigger the on_presets_changed event so that we also restore the previous value in the plater selector, + // if this action was initiated from the platter. + on_presets_changed(); + } else { + if (current_dirty) + m_presets->discard_current_changes(); - const bool is_selected = m_presets->select_preset_by_name(preset_name, false) || delete_current; - assert(m_presets->get_edited_preset().name == preset_name || ! is_selected); - // Mark the print & filament enabled if they are compatible with the currently selected preset. - // The following method should not discard changes of current print or filament presets on change of a printer profile, - // if they are compatible with the current printer. - if (current_dirty || delete_current || print_tab || printer_tab) - m_preset_bundle->update_compatible(true); - // Initialize the UI from the current preset. + const bool is_selected = m_presets->select_preset_by_name(preset_name, false) || delete_current; + assert(m_presets->get_edited_preset().name == preset_name || ! is_selected); + // Mark the print & filament enabled if they are compatible with the currently selected preset. + // The following method should not discard changes of current print or filament presets on change of a printer profile, + // if they are compatible with the current printer. + if (current_dirty || delete_current || print_tab || printer_tab) + m_preset_bundle->update_compatible(true); + // Initialize the UI from the current preset. if (printer_tab) static_cast(this)->update_pages(); if (! is_selected && printer_tab) { /* There is a case, when : - * after Config Wizard applying we try to select previously selected preset, but + * after Config Wizard applying we try to select previously selected preset, but * in a current configuration this one: * 1. doesn't exist now, * 2. have another printer_technology - * So, it is necessary to update list of dependent tabs + * So, it is necessary to update list of dependent tabs * to the corresponding printer_technology */ const PrinterTechnology printer_technology = m_presets->get_edited_preset().printer_technology(); if (printer_technology == ptFFF && m_dependent_tabs.front() != Preset::Type::TYPE_PRINT) - m_dependent_tabs = { Preset::Type::TYPE_PRINT, Preset::Type::TYPE_FILAMENT }; + m_dependent_tabs = { Preset::Type::TYPE_PRINT, Preset::Type::TYPE_FILAMENT }; else if (printer_technology == ptSLA && m_dependent_tabs.front() != Preset::Type::TYPE_SLA_PRINT) m_dependent_tabs = { Preset::Type::TYPE_SLA_PRINT, Preset::Type::TYPE_SLA_MATERIAL }; } - load_current_preset(); - } + load_current_preset(); + } } // If the current preset is dirty, the user is asked whether the changes may be discarded. // if the current preset was not dirty, or the user agreed to discard the changes, 1 is returned. bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr*/, const std::string& new_printer_name /*= ""*/) { - if (presets == nullptr) presets = m_presets; - // Display a dialog showing the dirty options in a human readable form. - const Preset& old_preset = presets->get_edited_preset(); - std::string type_name = presets->name(); - wxString tab = " "; - wxString name = old_preset.is_default ? - wxString::Format(_(L("Default preset (%s)")), _(type_name)) : - wxString::Format(_(L("Preset (%s)")), _(type_name)) + "\n" + tab + old_preset.name; + if (presets == nullptr) presets = m_presets; + // Display a dialog showing the dirty options in a human readable form. + const Preset& old_preset = presets->get_edited_preset(); + std::string type_name = presets->name(); + wxString tab = " "; + wxString name = old_preset.is_default ? + wxString::Format(_(L("Default preset (%s)")), _(type_name)) : + wxString::Format(_(L("Preset (%s)")), _(type_name)) + "\n" + tab + old_preset.name; - // Collect descriptions of the dirty options. - wxString changes; - for (const std::string &opt_key : presets->current_dirty_options()) { - const ConfigOptionDef &opt = m_config->def()->options.at(opt_key); - /*std::string*/wxString name = ""; - if (! opt.category.empty()) - name += _(opt.category) + " > "; - name += !opt.full_label.empty() ? - _(opt.full_label) : - _(opt.label); - changes += tab + /*from_u8*/(name) + "\n"; - } - // Show a confirmation dialog with the list of dirty options. - wxString message = name + "\n\n"; - if (new_printer_name.empty()) - message += _(L("has the following unsaved changes:")); - else { - message += (m_type == Slic3r::Preset::TYPE_PRINTER) ? - _(L("is not compatible with printer")) : - _(L("is not compatible with print profile")); - message += wxString("\n") + tab + from_u8(new_printer_name) + "\n\n"; - message += _(L("and it has the following unsaved changes:")); - } - auto confirm = new wxMessageDialog(parent(), - message + "\n" + changes + "\n\n" + _(L("Discard changes and continue anyway?")), - _(L("Unsaved Changes")), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); - return confirm->ShowModal() == wxID_YES; + // Collect descriptions of the dirty options. + wxString changes; + for (const std::string &opt_key : presets->current_dirty_options()) { + const ConfigOptionDef &opt = m_config->def()->options.at(opt_key); + /*std::string*/wxString name = ""; + if (! opt.category.empty()) + name += _(opt.category) + " > "; + name += !opt.full_label.empty() ? + _(opt.full_label) : + _(opt.label); + changes += tab + /*from_u8*/(name) + "\n"; + } + // Show a confirmation dialog with the list of dirty options. + wxString message = name + "\n\n"; + if (new_printer_name.empty()) + message += _(L("has the following unsaved changes:")); + else { + message += (m_type == Slic3r::Preset::TYPE_PRINTER) ? + _(L("is not compatible with printer")) : + _(L("is not compatible with print profile")); + message += wxString("\n") + tab + from_u8(new_printer_name) + "\n\n"; + message += _(L("and it has the following unsaved changes:")); + } + wxMessageDialog confirm(parent(), + message + "\n" + changes + "\n\n" + _(L("Discard changes and continue anyway?")), + _(L("Unsaved Changes")), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); + return confirm.ShowModal() == wxID_YES; } // If we are switching from the FFF-preset to the SLA, we should to control the printed objects if they have a part(s). // Because of we can't to print the multi-part objects with SLA technology. bool Tab::may_switch_to_SLA_preset() { - if (wxGetApp().obj_list()->has_multi_part_objects()) + if (model_has_multi_part_objects(wxGetApp().model())) { - show_info( parent(), + show_info( parent(), _(L("It's impossible to print multi-part object(s) with SLA technology.")) + "\n\n" + _(L("Please check your object list before preset changing.")), _(L("Attention!")) ); @@ -2867,14 +3128,14 @@ bool Tab::may_switch_to_SLA_preset() void Tab::OnTreeSelChange(wxTreeEvent& event) { - if (m_disable_tree_sel_changed_event) + if (m_disable_tree_sel_changed_event) return; // There is a bug related to Ubuntu overlay scrollbars, see https://github.com/prusa3d/PrusaSlicer/issues/898 and https://github.com/prusa3d/PrusaSlicer/issues/952. // The issue apparently manifests when Show()ing a window with overlay scrollbars while the UI is frozen. For this reason, // we will Thaw the UI prematurely on Linux. This means destroing the no_updates object prematurely. -#ifdef __linux__ - std::unique_ptr no_updates(new wxWindowUpdateLocker(this)); +#ifdef __linux__ + std::unique_ptr no_updates(new wxWindowUpdateLocker(this)); #else // wxWindowUpdateLocker noUpdates(this); #endif @@ -2882,44 +3143,44 @@ void Tab::OnTreeSelChange(wxTreeEvent& event) if (m_pages.empty()) return; - Page* page = nullptr; + Page* page = nullptr; const auto sel_item = m_treectrl->GetSelection(); const auto selection = sel_item ? m_treectrl->GetItemText(sel_item) : ""; for (auto p : m_pages) - if (p->title() == selection) - { - page = p.get(); - m_is_nonsys_values = page->m_is_nonsys_values; - m_is_modified_values = page->m_is_modified_values; - break; - } - if (page == nullptr) return; + if (p->title() == selection) + { + page = p.get(); + m_is_nonsys_values = page->m_is_nonsys_values; + m_is_modified_values = page->m_is_modified_values; + break; + } + if (page == nullptr) return; - for (auto& el : m_pages) + for (auto& el : m_pages) // if (el.get()->IsShown()) { - el.get()->Hide(); + el.get()->Hide(); // break; // } - #ifdef __linux__ - no_updates.reset(nullptr); - #endif + #ifdef __linux__ + no_updates.reset(nullptr); + #endif - update_undo_buttons(); - page->Show(); + update_undo_buttons(); + page->Show(); // if (! page->layout_valid) { - page->layout_valid = true; - m_hsizer->Layout(); - Refresh(); + page->layout_valid = true; + m_hsizer->Layout(); + Refresh(); // } } void Tab::OnKeyDown(wxKeyEvent& event) { - if (event.GetKeyCode() == WXK_TAB) - m_treectrl->Navigate(event.ShiftDown() ? wxNavigationKeyEvent::IsBackward : wxNavigationKeyEvent::IsForward); - else - event.Skip(); + if (event.GetKeyCode() == WXK_TAB) + m_treectrl->Navigate(event.ShiftDown() ? wxNavigationKeyEvent::IsBackward : wxNavigationKeyEvent::IsForward); + else + event.Skip(); } // Save the current preset into file. @@ -2929,278 +3190,263 @@ void Tab::OnKeyDown(wxKeyEvent& event) // opens a Slic3r::GUI::SavePresetWindow dialog. void Tab::save_preset(std::string name /*= ""*/) { - // since buttons(and choices too) don't get focus on Mac, we set focus manually - // to the treectrl so that the EVT_* events are fired for the input field having - // focus currently.is there anything better than this ? + // since buttons(and choices too) don't get focus on Mac, we set focus manually + // to the treectrl so that the EVT_* events are fired for the input field having + // focus currently.is there anything better than this ? //! m_treectrl->OnSetFocus(); - if (name.empty()) { - const Preset &preset = m_presets->get_selected_preset(); + if (name.empty()) { + const Preset &preset = m_presets->get_selected_preset(); auto default_name = preset.is_default ? "Untitled" : - preset.is_system ? (boost::format(_utf8(L("%1% - Copy"))) % preset.name).str() : - preset.name; + preset.is_system ? (boost::format(_utf8(L("%1% - Copy"))) % preset.name).str() : + preset.name; - bool have_extention = boost::iends_with(default_name, ".ini"); - if (have_extention) { - size_t len = default_name.length()-4; - default_name.resize(len); - } - //[map $_->name, grep !$_->default && !$_->external, @{$self->{presets}}], - std::vector values; - for (size_t i = 0; i < m_presets->size(); ++i) { - const Preset &preset = m_presets->preset(i); - if (preset.is_default || preset.is_system || preset.is_external) - continue; - values.push_back(preset.name); - } + bool have_extention = boost::iends_with(default_name, ".ini"); + if (have_extention) { + size_t len = default_name.length()-4; + default_name.resize(len); + } + //[map $_->name, grep !$_->default && !$_->external, @{$self->{presets}}], + std::vector values; + for (size_t i = 0; i < m_presets->size(); ++i) { + const Preset &preset = m_presets->preset(i); + if (preset.is_default || preset.is_system || preset.is_external) + continue; + values.push_back(preset.name); + } - auto dlg = new SavePresetWindow(parent()); - dlg->build(title(), default_name, values); - if (dlg->ShowModal() != wxID_OK) - return; - name = dlg->get_name(); - if (name == "") { - show_error(this, _(L("The supplied name is empty. It can't be saved."))); - return; - } - const Preset *existing = m_presets->find_preset(name, false); - if (existing && (existing->is_default || existing->is_system)) { - show_error(this, _(L("Cannot overwrite a system profile."))); - return; - } - if (existing && (existing->is_external)) { - show_error(this, _(L("Cannot overwrite an external profile."))); - return; - } - } + SavePresetWindow dlg(parent()); + dlg.build(title(), default_name, values); + if (dlg.ShowModal() != wxID_OK) + return; + name = dlg.get_name(); + if (name == "") { + show_error(this, _(L("The supplied name is empty. It can't be saved."))); + return; + } + const Preset *existing = m_presets->find_preset(name, false); + if (existing && (existing->is_default || existing->is_system)) { + show_error(this, _(L("Cannot overwrite a system profile."))); + return; + } + if (existing && (existing->is_external)) { + show_error(this, _(L("Cannot overwrite an external profile."))); + return; + } + } - // Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini - m_presets->save_current_preset(name); - // Mark the print & filament enabled if they are compatible with the currently selected preset. - m_preset_bundle->update_compatible(false); - // Add the new item into the UI component, remove dirty flags and activate the saved item. - update_tab_ui(); - // Update the selection boxes at the platter. - on_presets_changed(); - // If current profile is saved, "delete preset" button have to be enabled - m_btn_delete_preset->Enable(true); + // Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini + m_presets->save_current_preset(name); + // Mark the print & filament enabled if they are compatible with the currently selected preset. + m_preset_bundle->update_compatible(false); + // Add the new item into the UI component, remove dirty flags and activate the saved item. + update_tab_ui(); + // Update the selection boxes at the platter. + on_presets_changed(); + // If current profile is saved, "delete preset" button have to be enabled + m_btn_delete_preset->Enable(true); - if (m_type == Preset::TYPE_PRINTER) - static_cast(this)->m_initial_extruders_count = static_cast(this)->m_extruders_count; - update_changed_ui(); + if (m_type == Preset::TYPE_PRINTER) + static_cast(this)->m_initial_extruders_count = static_cast(this)->m_extruders_count; + update_changed_ui(); } // Called for a currently selected preset. void Tab::delete_preset() { - auto current_preset = m_presets->get_selected_preset(); - // Don't let the user delete the ' - default - ' configuration. + auto current_preset = m_presets->get_selected_preset(); + // Don't let the user delete the ' - default - ' configuration. std::string action = current_preset.is_external ? _utf8(L("remove")) : _utf8(L("delete")); // TRN remove/delete const wxString msg = from_u8((boost::format(_utf8(L("Are you sure you want to %1% the selected preset?"))) % action).str()); - action = current_preset.is_external ? _utf8(L("Remove")) : _utf8(L("Delete")); - // TRN Remove/Delete + action = current_preset.is_external ? _utf8(L("Remove")) : _utf8(L("Delete")); + // TRN Remove/Delete wxString title = from_u8((boost::format(_utf8(L("%1% Preset"))) % action).str()); //action + _(L(" Preset")); - if (current_preset.is_default || - wxID_YES != wxMessageDialog(parent(), msg, title, wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION).ShowModal()) - return; - // Select will handle of the preset dependencies, of saving & closing the depending profiles, and - // finally of deleting the preset. - this->select_preset("", true); + if (current_preset.is_default || + wxID_YES != wxMessageDialog(parent(), msg, title, wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION).ShowModal()) + return; + // Select will handle of the preset dependencies, of saving & closing the depending profiles, and + // finally of deleting the preset. + this->select_preset("", true); } void Tab::toggle_show_hide_incompatible() { - m_show_incompatible_presets = !m_show_incompatible_presets; - update_show_hide_incompatible_button(); - update_tab_ui(); + m_show_incompatible_presets = !m_show_incompatible_presets; + update_show_hide_incompatible_button(); + update_tab_ui(); } void Tab::update_show_hide_incompatible_button() { - m_btn_hide_incompatible_presets->SetBitmap_(m_show_incompatible_presets ? - m_bmp_show_incompatible_presets : m_bmp_hide_incompatible_presets); - m_btn_hide_incompatible_presets->SetToolTip(m_show_incompatible_presets ? - "Both compatible an incompatible presets are shown. Click to hide presets not compatible with the current printer." : - "Only compatible presets are shown. Click to show both the presets compatible and not compatible with the current printer."); + m_btn_hide_incompatible_presets->SetBitmap_(m_show_incompatible_presets ? + m_bmp_show_incompatible_presets : m_bmp_hide_incompatible_presets); + m_btn_hide_incompatible_presets->SetToolTip(m_show_incompatible_presets ? + "Both compatible an incompatible presets are shown. Click to hide presets not compatible with the current printer." : + "Only compatible presets are shown. Click to show both the presets compatible and not compatible with the current printer."); } void Tab::update_ui_from_settings() { - // Show the 'show / hide presets' button only for the print and filament tabs, and only if enabled - // in application preferences. - m_show_btn_incompatible_presets = wxGetApp().app_config->get("show_incompatible_presets")[0] == '1' ? true : false; - bool show = m_show_btn_incompatible_presets && m_type != Slic3r::Preset::TYPE_PRINTER; - Layout(); - show ? m_btn_hide_incompatible_presets->Show() : m_btn_hide_incompatible_presets->Hide(); - // If the 'show / hide presets' button is hidden, hide the incompatible presets. - if (show) { - update_show_hide_incompatible_button(); - } - else { - if (m_show_incompatible_presets) { - m_show_incompatible_presets = false; - update_tab_ui(); - } - } + // Show the 'show / hide presets' button only for the print and filament tabs, and only if enabled + // in application preferences. + m_show_btn_incompatible_presets = wxGetApp().app_config->get("show_incompatible_presets")[0] == '1' ? true : false; + bool show = m_show_btn_incompatible_presets && m_type != Slic3r::Preset::TYPE_PRINTER; + Layout(); + show ? m_btn_hide_incompatible_presets->Show() : m_btn_hide_incompatible_presets->Hide(); + // If the 'show / hide presets' button is hidden, hide the incompatible presets. + if (show) { + update_show_hide_incompatible_button(); + } + else { + if (m_show_incompatible_presets) { + m_show_incompatible_presets = false; + update_tab_ui(); + } + } } // Return a callback to create a Tab widget to mark the preferences as compatible / incompatible to the current printer. wxSizer* Tab::compatible_widget_create(wxWindow* parent, PresetDependencies &deps) { - deps.checkbox = new wxCheckBox(parent, wxID_ANY, _(L("All"))); - deps.checkbox->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + deps.checkbox = new wxCheckBox(parent, wxID_ANY, _(L("All"))); + deps.checkbox->SetFont(Slic3r::GUI::wxGetApp().normal_font()); add_scaled_button(parent, &deps.btn, "printer_white", wxString::Format(" %s %s", _(L("Set")), dots), wxBU_LEFT | wxBU_EXACTFIT); deps.btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add((deps.checkbox), 0, wxALIGN_CENTER_VERTICAL); - sizer->Add((deps.btn), 0, wxALIGN_CENTER_VERTICAL); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add((deps.checkbox), 0, wxALIGN_CENTER_VERTICAL); + sizer->Add((deps.btn), 0, wxALIGN_CENTER_VERTICAL); - deps.checkbox->Bind(wxEVT_CHECKBOX, ([this, &deps](wxCommandEvent e) - { - deps.btn->Enable(! deps.checkbox->GetValue()); - // All printers have been made compatible with this preset. - if (deps.checkbox->GetValue()) - this->load_key_value(deps.key_list, std::vector {}); - this->get_field(deps.key_condition)->toggle(deps.checkbox->GetValue()); - this->update_changed_ui(); - }) ); + deps.checkbox->Bind(wxEVT_CHECKBOX, ([this, &deps](wxCommandEvent e) + { + deps.btn->Enable(! deps.checkbox->GetValue()); + // All printers have been made compatible with this preset. + if (deps.checkbox->GetValue()) + this->load_key_value(deps.key_list, std::vector {}); + this->get_field(deps.key_condition)->toggle(deps.checkbox->GetValue()); + this->update_changed_ui(); + }) ); - deps.btn->Bind(wxEVT_BUTTON, ([this, parent, &deps](wxCommandEvent e) - { - // Collect names of non-default non-external profiles. - PrinterTechnology printer_technology = m_preset_bundle->printers.get_edited_preset().printer_technology(); - PresetCollection &depending_presets = (deps.type == Preset::TYPE_PRINTER) ? m_preset_bundle->printers : - (printer_technology == ptFFF) ? m_preset_bundle->prints : m_preset_bundle->sla_prints; - wxArrayString presets; - for (size_t idx = 0; idx < depending_presets.size(); ++ idx) - { - Preset& preset = depending_presets.preset(idx); - bool add = ! preset.is_default && ! preset.is_external; - if (add && deps.type == Preset::TYPE_PRINTER) - // Only add printers with the same technology as the active printer. - add &= preset.printer_technology() == printer_technology; - if (add) - presets.Add(from_u8(preset.name)); - } + deps.btn->Bind(wxEVT_BUTTON, ([this, parent, &deps](wxCommandEvent e) + { + // Collect names of non-default non-external profiles. + PrinterTechnology printer_technology = m_preset_bundle->printers.get_edited_preset().printer_technology(); + PresetCollection &depending_presets = (deps.type == Preset::TYPE_PRINTER) ? m_preset_bundle->printers : + (printer_technology == ptFFF) ? m_preset_bundle->prints : m_preset_bundle->sla_prints; + wxArrayString presets; + for (size_t idx = 0; idx < depending_presets.size(); ++ idx) + { + Preset& preset = depending_presets.preset(idx); + bool add = ! preset.is_default && ! preset.is_external; + if (add && deps.type == Preset::TYPE_PRINTER) + // Only add printers with the same technology as the active printer. + add &= preset.printer_technology() == printer_technology; + if (add) + presets.Add(from_u8(preset.name)); + } - wxMultiChoiceDialog dlg(parent, deps.dialog_title, deps.dialog_label, presets); - // Collect and set indices of depending_presets marked as compatible. - wxArrayInt selections; - auto *compatible_printers = dynamic_cast(m_config->option(deps.key_list)); - if (compatible_printers != nullptr || !compatible_printers->values.empty()) - for (auto preset_name : compatible_printers->values) - for (size_t idx = 0; idx < presets.GetCount(); ++idx) - if (presets[idx] == preset_name) { - selections.Add(idx); - break; - } - dlg.SetSelections(selections); - std::vector value; - // Show the dialog. - if (dlg.ShowModal() == wxID_OK) { - selections.Clear(); - selections = dlg.GetSelections(); - for (auto idx : selections) - value.push_back(presets[idx].ToUTF8().data()); - if (value.empty()) { - deps.checkbox->SetValue(1); - deps.btn->Disable(); - } - // All depending_presets have been made compatible with this preset. - this->load_key_value(deps.key_list, value); - this->update_changed_ui(); - } - })); - return sizer; + wxMultiChoiceDialog dlg(parent, deps.dialog_title, deps.dialog_label, presets); + // Collect and set indices of depending_presets marked as compatible. + wxArrayInt selections; + auto *compatible_printers = dynamic_cast(m_config->option(deps.key_list)); + if (compatible_printers != nullptr || !compatible_printers->values.empty()) + for (auto preset_name : compatible_printers->values) + for (size_t idx = 0; idx < presets.GetCount(); ++idx) + if (presets[idx] == preset_name) { + selections.Add(idx); + break; + } + dlg.SetSelections(selections); + std::vector value; + // Show the dialog. + if (dlg.ShowModal() == wxID_OK) { + selections.Clear(); + selections = dlg.GetSelections(); + for (auto idx : selections) + value.push_back(presets[idx].ToUTF8().data()); + if (value.empty()) { + deps.checkbox->SetValue(1); + deps.btn->Disable(); + } + // All depending_presets have been made compatible with this preset. + this->load_key_value(deps.key_list, value); + this->update_changed_ui(); + } + })); + return sizer; } void Tab::compatible_widget_reload(PresetDependencies &deps) { - bool has_any = ! m_config->option(deps.key_list)->values.empty(); - has_any ? deps.btn->Enable() : deps.btn->Disable(); - deps.checkbox->SetValue(! has_any); - this->get_field(deps.key_condition)->toggle(! has_any); + bool has_any = ! m_config->option(deps.key_list)->values.empty(); + has_any ? deps.btn->Enable() : deps.btn->Disable(); + deps.checkbox->SetValue(! has_any); + this->get_field(deps.key_condition)->toggle(! has_any); } void Tab::fill_icon_descriptions() { - m_icon_descriptions.emplace_back(&m_bmp_value_lock, L("LOCKED LOCK"), + m_icon_descriptions.emplace_back(&m_bmp_value_lock, L("LOCKED LOCK"), // TRN Description for "LOCKED LOCK" - L("indicates that the settings are the same as the system values for the current option group")); + L("indicates that the settings are the same as the system (or default) values for the current option group")); m_icon_descriptions.emplace_back(&m_bmp_value_unlock, L("UNLOCKED LOCK"), // TRN Description for "UNLOCKED LOCK" - L("indicates that some settings were changed and are not equal to the system values for " - "the current option group.\n" - "Click the UNLOCKED LOCK icon to reset all settings for current option group to " - "the system values.")); + L("indicates that some settings were changed and are not equal to the system (or default) values for " + "the current option group.\n" + "Click the UNLOCKED LOCK icon to reset all settings for current option group to " + "the system (or default) values.")); m_icon_descriptions.emplace_back(&m_bmp_white_bullet, L("WHITE BULLET"), // TRN Description for "WHITE BULLET" - L("for the left button: \tindicates a non-system preset,\n" - "for the right button: \tindicates that the settings hasn't been modified.")); + L("for the left button: \tindicates a non-system (or non-default) preset,\n" + "for the right button: \tindicates that the settings hasn't been modified.")); m_icon_descriptions.emplace_back(&m_bmp_value_revert, L("BACK ARROW"), // TRN Description for "BACK ARROW" L("indicates that the settings were changed and are not equal to the last saved preset for " - "the current option group.\n" - "Click the BACK ARROW icon to reset all settings for the current option group to " - "the last saved preset.")); + "the current option group.\n" + "Click the BACK ARROW icon to reset all settings for the current option group to " + "the last saved preset.")); } void Tab::set_tooltips_text() { -// m_undo_to_sys_btn->SetToolTip(_(L( "LOCKED LOCK icon indicates that the settings are the same as the system values " -// "for the current option group.\n" -// "UNLOCKED LOCK icon indicates that some settings were changed and are not equal " -// "to the system values for the current option group.\n" -// "WHITE BULLET icon indicates a non system preset.\n\n" -// "Click the UNLOCKED LOCK icon to reset all settings for current option group to " -// "the system values."))); -// -// m_undo_btn->SetToolTip(_(L( "WHITE BULLET icon indicates that the settings are the same as in the last saved" -// "preset for the current option group.\n" -// "BACK ARROW icon indicates that the settings were changed and are not equal to " -// "the last saved preset for the current option group.\n\n" -// "Click the BACK ARROW icon to reset all settings for the current option group to " -// "the last saved preset."))); + // --- Tooltip text for reset buttons (for whole options group) + // Text to be shown on the "Revert to system" aka "Lock to system" button next to each input field. + m_ttg_value_lock = _(L("LOCKED LOCK icon indicates that the settings are the same as the system (or default) values " + "for the current option group")); + m_ttg_value_unlock = _(L("UNLOCKED LOCK icon indicates that some settings were changed and are not equal " + "to the system (or default) values for the current option group.\n" + "Click to reset all settings for current option group to the system (or default) values.")); + m_ttg_white_bullet_ns = _(L("WHITE BULLET icon indicates a non system (or non default) preset.")); + m_ttg_non_system = &m_ttg_white_bullet_ns; + // Text to be shown on the "Undo user changes" button next to each input field. + m_ttg_white_bullet = _(L("WHITE BULLET icon indicates that the settings are the same as in the last saved " + "preset for the current option group.")); + m_ttg_value_revert = _(L("BACK ARROW icon indicates that the settings were changed and are not equal to " + "the last saved preset for the current option group.\n" + "Click to reset all settings for the current option group to the last saved preset.")); - // --- Tooltip text for reset buttons (for whole options group) - // Text to be shown on the "Revert to system" aka "Lock to system" button next to each input field. - m_ttg_value_lock = _(L("LOCKED LOCK icon indicates that the settings are the same as the system values " - "for the current option group")); - m_ttg_value_unlock = _(L("UNLOCKED LOCK icon indicates that some settings were changed and are not equal " - "to the system values for the current option group.\n" - "Click to reset all settings for current option group to the system values.")); - m_ttg_white_bullet_ns = _(L("WHITE BULLET icon indicates a non system preset.")); - m_ttg_non_system = &m_ttg_white_bullet_ns; - // Text to be shown on the "Undo user changes" button next to each input field. - m_ttg_white_bullet = _(L("WHITE BULLET icon indicates that the settings are the same as in the last saved " - "preset for the current option group.")); - m_ttg_value_revert = _(L("BACK ARROW icon indicates that the settings were changed and are not equal to " - "the last saved preset for the current option group.\n" - "Click to reset all settings for the current option group to the last saved preset.")); - - // --- Tooltip text for reset buttons (for each option in group) - // Text to be shown on the "Revert to system" aka "Lock to system" button next to each input field. - m_tt_value_lock = _(L("LOCKED LOCK icon indicates that the value is the same as the system value.")); - m_tt_value_unlock = _(L("UNLOCKED LOCK icon indicates that the value was changed and is not equal " - "to the system value.\n" - "Click to reset current value to the system value.")); - // m_tt_white_bullet_ns= _(L("WHITE BULLET icon indicates a non system preset.")); - m_tt_non_system = &m_ttg_white_bullet_ns; - // Text to be shown on the "Undo user changes" button next to each input field. - m_tt_white_bullet = _(L("WHITE BULLET icon indicates that the value is the same as in the last saved preset.")); - m_tt_value_revert = _(L("BACK ARROW icon indicates that the value was changed and is not equal to the last saved preset.\n" - "Click to reset current value to the last saved preset.")); + // --- Tooltip text for reset buttons (for each option in group) + // Text to be shown on the "Revert to system" aka "Lock to system" button next to each input field. + m_tt_value_lock = _(L("LOCKED LOCK icon indicates that the value is the same as the system (or default) value.")); + m_tt_value_unlock = _(L("UNLOCKED LOCK icon indicates that the value was changed and is not equal " + "to the system (or default) value.\n" + "Click to reset current value to the system (or default) value.")); + // m_tt_white_bullet_ns= _(L("WHITE BULLET icon indicates a non system preset.")); + m_tt_non_system = &m_ttg_white_bullet_ns; + // Text to be shown on the "Undo user changes" button next to each input field. + m_tt_white_bullet = _(L("WHITE BULLET icon indicates that the value is the same as in the last saved preset.")); + m_tt_value_revert = _(L("BACK ARROW icon indicates that the value was changed and is not equal to the last saved preset.\n" + "Click to reset current value to the last saved preset.")); } void Page::reload_config() { - for (auto group : m_optgroups) - group->reload_config(); + for (auto group : m_optgroups) + group->reload_config(); } void Page::update_visibility(ConfigOptionMode mode) @@ -3220,22 +3466,22 @@ void Page::msw_rescale() Field* Page::get_field(const t_config_option_key& opt_key, int opt_index /*= -1*/) const { - Field* field = nullptr; - for (auto opt : m_optgroups) { - field = opt->get_fieldc(opt_key, opt_index); - if (field != nullptr) - return field; - } - return field; + Field* field = nullptr; + for (auto opt : m_optgroups) { + field = opt->get_fieldc(opt_key, opt_index); + if (field != nullptr) + return field; + } + return field; } bool Page::set_value(const t_config_option_key& opt_key, const boost::any& value) { - bool changed = false; - for(auto optgroup: m_optgroups) { - if (optgroup->set_value(opt_key, value)) - changed = 1 ; - } - return changed; + bool changed = false; + for(auto optgroup: m_optgroups) { + if (optgroup->set_value(opt_key, value)) + changed = 1 ; + } + return changed; } // package Slic3r::GUI::Tab::Page; @@ -3255,39 +3501,39 @@ ConfigOptionsGroupShp Page::new_optgroup(const wxString& title, int noncommon_la return bmp; }; - //! config_ have to be "right" - ConfigOptionsGroupShp optgroup = std::make_shared(this, title, m_config, true, extra_column); - if (noncommon_label_width >= 0) - optgroup->label_width = noncommon_label_width; + //! config_ have to be "right" + ConfigOptionsGroupShp optgroup = std::make_shared(this, title, m_config, true, extra_column); + if (noncommon_label_width >= 0) + optgroup->label_width = noncommon_label_width; #ifdef __WXOSX__ - auto tab = GetParent()->GetParent(); + auto tab = GetParent()->GetParent(); #else - auto tab = GetParent(); + auto tab = GetParent(); #endif - optgroup->m_on_change = [this, tab](t_config_option_key opt_key, boost::any value) { - //! This function will be called from OptionGroup. - //! Using of CallAfter is redundant. - //! And in some cases it causes update() function to be recalled again + optgroup->m_on_change = [this, tab](t_config_option_key opt_key, boost::any value) { + //! This function will be called from OptionGroup. + //! Using of CallAfter is redundant. + //! And in some cases it causes update() function to be recalled again //! wxTheApp->CallAfter([this, opt_key, value]() { - static_cast(tab)->update_dirty(); - static_cast(tab)->on_value_change(opt_key, value); + static_cast(tab)->update_dirty(); + static_cast(tab)->on_value_change(opt_key, value); //! }); - }; + }; - optgroup->m_get_initial_config = [this, tab]() { - DynamicPrintConfig config = static_cast(tab)->m_presets->get_selected_preset().config; - return config; - }; + optgroup->m_get_initial_config = [this, tab]() { + DynamicPrintConfig config = static_cast(tab)->m_presets->get_selected_preset().config; + return config; + }; - optgroup->m_get_sys_config = [this, tab]() { - DynamicPrintConfig config = static_cast(tab)->m_presets->get_selected_preset_parent()->config; - return config; - }; + optgroup->m_get_sys_config = [this, tab]() { + DynamicPrintConfig config = static_cast(tab)->m_presets->get_selected_preset_parent()->config; + return config; + }; - optgroup->have_sys_config = [this, tab]() { - return static_cast(tab)->m_presets->get_selected_preset_parent() != nullptr; - }; + optgroup->have_sys_config = [this, tab]() { + return static_cast(tab)->m_presets->get_selected_preset_parent() != nullptr; + }; optgroup->rescale_extra_column_item = [this](wxWindow* win) { auto *ctrl = dynamic_cast(win); @@ -3297,69 +3543,69 @@ ConfigOptionsGroupShp Page::new_optgroup(const wxString& title, int noncommon_la ctrl->SetBitmap(reinterpret_cast(ctrl->GetClientData())->bmp()); }; - vsizer()->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 10); - m_optgroups.push_back(optgroup); + vsizer()->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 10); + m_optgroups.push_back(optgroup); - return optgroup; + return optgroup; } void SavePresetWindow::build(const wxString& title, const std::string& default_name, std::vector &values) { // TRN Preset - auto text = new wxStaticText(this, wxID_ANY, wxString::Format(_(L("Save %s as:")), title), - wxDefaultPosition, wxDefaultSize); - m_combo = new wxComboBox(this, wxID_ANY, from_u8(default_name), - wxDefaultPosition, wxDefaultSize, 0, 0, wxTE_PROCESS_ENTER); - for (auto value : values) - m_combo->Append(from_u8(value)); - auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL); + auto text = new wxStaticText(this, wxID_ANY, wxString::Format(_(L("Save %s as:")), title), + wxDefaultPosition, wxDefaultSize); + m_combo = new wxComboBox(this, wxID_ANY, from_u8(default_name), + wxDefaultPosition, wxDefaultSize, 0, 0, wxTE_PROCESS_ENTER); + for (auto value : values) + m_combo->Append(from_u8(value)); + auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL); - auto sizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(text, 0, wxEXPAND | wxALL, 10); - sizer->Add(m_combo, 0, wxEXPAND | wxLEFT | wxRIGHT, 10); - sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 10); + auto sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(text, 0, wxEXPAND | wxALL, 10); + sizer->Add(m_combo, 0, wxEXPAND | wxLEFT | wxRIGHT, 10); + sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 10); - wxButton* btn = static_cast(FindWindowById(wxID_OK, this)); - btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { accept(); }); - m_combo->Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent&) { accept(); }); + wxButton* btn = static_cast(FindWindowById(wxID_OK, this)); + btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { accept(); }); + m_combo->Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent&) { accept(); }); - SetSizer(sizer); - sizer->SetSizeHints(this); + SetSizer(sizer); + sizer->SetSizeHints(this); } void SavePresetWindow::accept() { - m_chosen_name = normalize_utf8_nfc(m_combo->GetValue().ToUTF8()); - if (!m_chosen_name.empty()) { - const char* unusable_symbols = "<>[]:/\\|?*\""; - bool is_unusable_symbol = false; - bool is_unusable_suffix = false; - const std::string unusable_suffix = PresetCollection::get_suffix_modified();//"(modified)"; - for (size_t i = 0; i < std::strlen(unusable_symbols); i++) { - if (m_chosen_name.find_first_of(unusable_symbols[i]) != std::string::npos) { - is_unusable_symbol = true; - break; - } - } - if (m_chosen_name.find(unusable_suffix) != std::string::npos) - is_unusable_suffix = true; + m_chosen_name = normalize_utf8_nfc(m_combo->GetValue().ToUTF8()); + if (!m_chosen_name.empty()) { + const char* unusable_symbols = "<>[]:/\\|?*\""; + bool is_unusable_symbol = false; + bool is_unusable_suffix = false; + const std::string unusable_suffix = PresetCollection::get_suffix_modified();//"(modified)"; + for (size_t i = 0; i < std::strlen(unusable_symbols); i++) { + if (m_chosen_name.find_first_of(unusable_symbols[i]) != std::string::npos) { + is_unusable_symbol = true; + break; + } + } + if (m_chosen_name.find(unusable_suffix) != std::string::npos) + is_unusable_suffix = true; - if (is_unusable_symbol) { - show_error(this,_(L("The supplied name is not valid;")) + "\n" + - _(L("the following characters are not allowed:")) + " " + unusable_symbols); - } - else if (is_unusable_suffix) { - show_error(this,_(L("The supplied name is not valid;")) + "\n" + - _(L("the following suffix is not allowed:")) + "\n\t" + - wxString::FromUTF8(unusable_suffix.c_str())); - } - else if (m_chosen_name == "- default -") { - show_error(this, _(L("The supplied name is not available."))); - } - else { - EndModal(wxID_OK); - } - } + if (is_unusable_symbol) { + show_error(this,_(L("The supplied name is not valid;")) + "\n" + + _(L("the following characters are not allowed:")) + " " + unusable_symbols); + } + else if (is_unusable_suffix) { + show_error(this,_(L("The supplied name is not valid;")) + "\n" + + _(L("the following suffix is not allowed:")) + "\n\t" + + wxString::FromUTF8(unusable_suffix.c_str())); + } + else if (m_chosen_name == "- default -") { + show_error(this, _(L("The supplied name is not available."))); + } + else { + EndModal(wxID_OK); + } + } } void TabSLAMaterial::build() @@ -3416,12 +3662,12 @@ void TabSLAMaterial::build() line = optgroup->create_single_option_line("compatible_prints"); line.widget = [this](wxWindow* parent) { - return compatible_widget_create(parent, m_compatible_prints); - }; - optgroup->append_line(line, &m_colored_Label); - option = optgroup->get_option("compatible_prints_condition"); - option.opt.full_width = true; - optgroup->append_single_option_line(option); + return compatible_widget_create(parent, m_compatible_prints); + }; + optgroup->append_line(line, &m_colored_Label); + option = optgroup->get_option("compatible_prints_condition"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); line = Line{ "", "" }; line.full_width = 1; @@ -3434,21 +3680,21 @@ void TabSLAMaterial::build() // Reload current config (aka presets->edited_preset->config) into the UI fields. void TabSLAMaterial::reload_config() { - this->compatible_widget_reload(m_compatible_printers); - this->compatible_widget_reload(m_compatible_prints); - Tab::reload_config(); + this->compatible_widget_reload(m_compatible_printers); + this->compatible_widget_reload(m_compatible_prints); + Tab::reload_config(); } void TabSLAMaterial::update() { if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF) - return; // #ys_FIXME - -// #ys_FIXME + return; + +// #ys_FIXME. Just a template for this function // m_update_cnt++; // ! something to update // m_update_cnt--; -// +// // if (m_update_cnt == 0) wxGetApp().mainframe->on_config_changed(m_config); } @@ -3481,6 +3727,7 @@ void TabSLAPrint::build() // optgroup->append_single_option_line("support_pillar_widening_factor"); optgroup->append_single_option_line("support_base_diameter"); optgroup->append_single_option_line("support_base_height"); + optgroup->append_single_option_line("support_base_safety_distance"); optgroup->append_single_option_line("support_object_elevation"); optgroup = page->new_optgroup(_(L("Connection of the support sticks and junctions"))); @@ -3502,15 +3749,21 @@ void TabSLAPrint::build() // optgroup->append_single_option_line("pad_edge_radius"); optgroup->append_single_option_line("pad_wall_slope"); - page = add_options_page(_(L("Advanced")), "wrench"); - optgroup = page->new_optgroup(_(L("Slicing"))); - optgroup->append_single_option_line("slice_closing_radius"); + optgroup->append_single_option_line("pad_zero_elevation"); + optgroup->append_single_option_line("pad_object_gap"); + optgroup->append_single_option_line("pad_object_connector_stride"); + optgroup->append_single_option_line("pad_object_connector_width"); + optgroup->append_single_option_line("pad_object_connector_penetration"); - page = add_options_page(_(L("Output options")), "output+page_white"); - optgroup = page->new_optgroup(_(L("Output file"))); - Option option = optgroup->get_option("output_filename_format"); - option.opt.full_width = true; - optgroup->append_single_option_line(option); + page = add_options_page(_(L("Advanced")), "wrench"); + optgroup = page->new_optgroup(_(L("Slicing"))); + optgroup->append_single_option_line("slice_closing_radius"); + + page = add_options_page(_(L("Output options")), "output+page_white"); + optgroup = page->new_optgroup(_(L("Output file"))); + Option option = optgroup->get_option("output_filename_format"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); page = add_options_page(_(L("Dependencies")), "wrench"); optgroup = page->new_optgroup(_(L("Profile dependencies"))); @@ -3535,48 +3788,95 @@ void TabSLAPrint::build() // Reload current config (aka presets->edited_preset->config) into the UI fields. void TabSLAPrint::reload_config() { - this->compatible_widget_reload(m_compatible_printers); - Tab::reload_config(); + this->compatible_widget_reload(m_compatible_printers); + Tab::reload_config(); } void TabSLAPrint::update() { if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF) - return; // #ys_FIXME + return; -// #ys_FIXME - m_update_cnt++; + m_update_cnt++; - double head_penetration = m_config->opt_float("support_head_penetration"); - double head_width = m_config->opt_float("support_head_width"); - if(head_penetration > head_width) { - wxString msg_text = _(L("Head penetration should not be greater than the head width.")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Invalid Head penetration")), wxICON_WARNING | wxOK); - DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_OK) { - new_conf.set_key_value("support_head_penetration", new ConfigOptionFloat(head_width)); - } + bool supports_en = m_config->opt_bool("supports_enable"); - load_config(new_conf); - } + get_field("support_head_front_diameter")->toggle(supports_en); + get_field("support_head_penetration")->toggle(supports_en); + get_field("support_head_width")->toggle(supports_en); + get_field("support_pillar_diameter")->toggle(supports_en); + get_field("support_pillar_connection_mode")->toggle(supports_en); + get_field("support_buildplate_only")->toggle(supports_en); + get_field("support_base_diameter")->toggle(supports_en); + get_field("support_base_height")->toggle(supports_en); + get_field("support_base_safety_distance")->toggle(supports_en); + get_field("support_critical_angle")->toggle(supports_en); + get_field("support_max_bridge_length")->toggle(supports_en); + get_field("support_max_pillar_link_distance")->toggle(supports_en); + get_field("support_points_density_relative")->toggle(supports_en); + get_field("support_points_minimal_distance")->toggle(supports_en); - double pinhead_d = m_config->opt_float("support_head_front_diameter"); - double pillar_d = m_config->opt_float("support_pillar_diameter"); - if(pinhead_d > pillar_d) { - wxString msg_text = _(L("Pinhead diameter should be smaller than the pillar diameter.")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Invalid pinhead diameter")), wxICON_WARNING | wxOK); - DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_OK) { - new_conf.set_key_value("support_head_front_diameter", new ConfigOptionFloat(pillar_d / 2.0)); - } + bool pad_en = m_config->opt_bool("pad_enable"); - load_config(new_conf); - } + get_field("pad_wall_thickness")->toggle(pad_en); + get_field("pad_wall_height")->toggle(pad_en); + get_field("pad_max_merge_distance")->toggle(pad_en); + // get_field("pad_edge_radius")->toggle(supports_en); + get_field("pad_wall_slope")->toggle(pad_en); + get_field("pad_zero_elevation")->toggle(pad_en); - m_update_cnt--; + double head_penetration = m_config->opt_float("support_head_penetration"); + double head_width = m_config->opt_float("support_head_width"); + if (head_penetration > head_width) { + wxString msg_text = _( + L("Head penetration should not be greater than the head width.")); - if (m_update_cnt == 0) - wxGetApp().mainframe->on_config_changed(m_config); + wxMessageDialog dialog(parent(), + msg_text, + _(L("Invalid Head penetration")), + wxICON_WARNING | wxOK); + + DynamicPrintConfig new_conf = *m_config; + if (dialog.ShowModal() == wxID_OK) { + new_conf.set_key_value("support_head_penetration", + new ConfigOptionFloat(head_width)); + } + + load_config(new_conf); + } + + double pinhead_d = m_config->opt_float("support_head_front_diameter"); + double pillar_d = m_config->opt_float("support_pillar_diameter"); + if (pinhead_d > pillar_d) { + wxString msg_text = _(L( + "Pinhead diameter should be smaller than the pillar diameter.")); + + wxMessageDialog dialog (parent(), + msg_text, + _(L("Invalid pinhead diameter")), + wxICON_WARNING | wxOK); + + DynamicPrintConfig new_conf = *m_config; + if (dialog.ShowModal() == wxID_OK) { + new_conf.set_key_value("support_head_front_diameter", + new ConfigOptionFloat(pillar_d / 2.0)); + } + + load_config(new_conf); + } + + bool has_suppad = pad_en && supports_en; + bool zero_elev = m_config->opt_bool("pad_zero_elevation") && has_suppad; + + get_field("support_object_elevation")->toggle(supports_en && !zero_elev); + get_field("pad_object_gap")->toggle(zero_elev); + get_field("pad_object_connector_stride")->toggle(zero_elev); + get_field("pad_object_connector_width")->toggle(zero_elev); + get_field("pad_object_connector_penetration")->toggle(zero_elev); + + m_update_cnt--; + + if (m_update_cnt == 0) wxGetApp().mainframe->on_config_changed(m_config); } } // GUI diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 6bbe15f7f4..0a90707005 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -142,6 +142,12 @@ protected: PresetDependencies m_compatible_printers; PresetDependencies m_compatible_prints; + /* Indicates, that default preset or preset inherited from default is selected + * This value is used for a options color updating + * (use green color only for options, which values are equal to system values) + */ + bool m_is_default_preset {false}; + ScalableButton* m_undo_btn; ScalableButton* m_undo_to_sys_btn; ScalableButton* m_question_btn; @@ -212,7 +218,7 @@ protected: int m_em_unit; // To avoid actions with no-completed Tab bool m_complited { false }; - ConfigOptionMode m_mode = comSimple; + ConfigOptionMode m_mode = comExpert; // to correct first Tab update_visibility() set mode to Expert public: PresetBundle* m_preset_bundle; @@ -331,6 +337,12 @@ class TabFilament : public Tab { ogStaticText* m_volumetric_speed_description_line; ogStaticText* m_cooling_description_line; + + void add_filament_overrides_page(); + void update_filament_overrides_page(); + void update_volumetric_flow_preset_hints(); + + std::map m_overrides_options; public: TabFilament(wxNotebook* parent) : // Tab(parent, _(L("Filament Settings")), L("filament")) {} @@ -359,6 +371,7 @@ public: wxButton* m_serial_test_btn = nullptr; ScalableButton* m_print_host_test_btn = nullptr; ScalableButton* m_printhost_browse_btn = nullptr; + ScalableButton* m_reset_to_filament_color = nullptr; size_t m_extruders_count; size_t m_extruders_count_old = 0; diff --git a/src/slic3r/GUI/UpdateDialogs.cpp b/src/slic3r/GUI/UpdateDialogs.cpp index 0f72b3b358..2ea9040075 100644 --- a/src/slic3r/GUI/UpdateDialogs.cpp +++ b/src/slic3r/GUI/UpdateDialogs.cpp @@ -55,7 +55,7 @@ MsgUpdateSlic3r::MsgUpdateSlic3r(const Semver &ver_current, const Semver &ver_on auto *link = new wxHyperlinkCtrl(this, wxID_ANY, _(L("Changelog && Download")), url_wx); content_sizer->Add(link); } else { - const auto lang_code = wxGetApp().current_language_code().ToStdString(); + const auto lang_code = wxGetApp().current_language_code_safe().ToStdString(); const std::string url_log = (boost::format(URL_CHANGELOG) % lang_code).str(); const wxString url_log_wx = from_u8(url_log); @@ -100,7 +100,7 @@ MsgUpdateConfig::MsgUpdateConfig(const std::vector &updates) : content_sizer->Add(text); content_sizer->AddSpacer(VERT_SPACING); - const auto lang_code = wxGetApp().current_language_code().ToStdString(); + const auto lang_code = wxGetApp().current_language_code_safe().ToStdString(); auto *versions = new wxBoxSizer(wxVERTICAL); for (const auto &update : updates) { diff --git a/src/slic3r/GUI/UpdateDialogs.hpp b/src/slic3r/GUI/UpdateDialogs.hpp index 2a580e2513..4b61b84c23 100644 --- a/src/slic3r/GUI/UpdateDialogs.hpp +++ b/src/slic3r/GUI/UpdateDialogs.hpp @@ -5,7 +5,7 @@ #include #include -#include "slic3r/Utils/Semver.hpp" +#include "libslic3r/Semver.hpp" #include "MsgDialog.hpp" class wxBoxSizer; diff --git a/src/slic3r/GUI/WipeTowerDialog.cpp b/src/slic3r/GUI/WipeTowerDialog.cpp index d93852da2c..3726498551 100644 --- a/src/slic3r/GUI/WipeTowerDialog.cpp +++ b/src/slic3r/GUI/WipeTowerDialog.cpp @@ -173,7 +173,7 @@ void WipingPanel::format_sizer(wxSizer* sizer, wxPanel* page, wxGridSizer* grid_ wxSize text_size = GetTextExtent(info); auto info_str = new wxStaticText(page, wxID_ANY, info ,wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER); info_str->Wrap(int(0.6*text_size.x)); - sizer->Add( info_str, 0, wxALIGN_CENTER_HORIZONTAL | wxEXPAND); + sizer->Add( info_str, 0, wxEXPAND); auto table_sizer = new wxBoxSizer(wxVERTICAL); sizer->Add(table_sizer, 0, wxALIGN_CENTER | wxCENTER, table_lshift); table_sizer->Add(new wxStaticText(page, wxID_ANY, table_title), 0, wxALIGN_CENTER | wxTOP, 50); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 76ba853dc3..a4cccfb86f 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -157,6 +157,24 @@ wxMenuItem* append_menu_radio_item(wxMenu* menu, int id, const wxString& string, return item; } +wxMenuItem* append_menu_check_item(wxMenu* menu, int id, const wxString& string, const wxString& description, + std::function cb, wxEvtHandler* event_handler) +{ + if (id == wxID_ANY) + id = wxNewId(); + + wxMenuItem* item = menu->AppendCheckItem(id, string, description); + +#ifdef __WXMSW__ + if (event_handler != nullptr && event_handler != menu) + event_handler->Bind(wxEVT_MENU, cb, id); + else +#endif // __WXMSW__ + menu->Bind(wxEVT_MENU, cb, id); + + return item; +} + const unsigned int wxCheckListBoxComboPopup::DefaultWidth = 200; const unsigned int wxCheckListBoxComboPopup::DefaultHeight = 200; const unsigned int wxCheckListBoxComboPopup::DefaultItemHeight = 18; @@ -432,35 +450,92 @@ wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name_in, // ObjectDataViewModelNode // ---------------------------------------------------------------------------- +void ObjectDataViewModelNode::init_container() +{ +#ifdef __WXGTK__ + // it's necessary on GTK because of control have to know if this item will be container + // in another case you couldn't to add subitem for this item + // it will be produce "segmentation fault" + m_container = true; +#endif //__WXGTK__ +} + ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const ItemType type) : m_parent(parent), m_type(type), m_extruder(wxEmptyString) { - if (type == itSettings) { + if (type == itSettings) m_name = "Settings to modified"; - } - else if (type == itInstanceRoot) { + else if (type == itInstanceRoot) m_name = _(L("Instances")); -#ifdef __WXGTK__ - m_container = true; -#endif //__WXGTK__ - } - else if (type == itInstance) { + else if (type == itInstance) + { m_idx = parent->GetChildCount(); m_name = wxString::Format(_(L("Instance %d")), m_idx + 1); set_action_icon(); } + else if (type == itLayerRoot) + { + m_bmp = create_scaled_bitmap(nullptr, "edit_layers_all"); // FIXME: pass window ptr + m_name = _(L("Layers")); + } + + if (type & (itInstanceRoot | itLayerRoot)) + init_container(); } +ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, + const t_layer_height_range& layer_range, + const int idx /*= -1 */, + const wxString& extruder) : + m_parent(parent), + m_type(itLayer), + m_idx(idx), + m_layer_range(layer_range), + m_extruder(extruder) +{ + const int children_cnt = parent->GetChildCount(); + if (idx < 0) + m_idx = children_cnt; + else + { + // update indexes for another Laeyr Nodes + for (int i = m_idx; i < children_cnt; i++) + parent->GetNthChild(i)->SetIdx(i + 1); + } + const std::string label_range = (boost::format(" %.2f-%.2f ") % layer_range.first % layer_range.second).str(); + m_name = _(L("Range")) + label_range + "(" + _(L("mm")) + ")"; + m_bmp = create_scaled_bitmap(nullptr, "edit_layers_some"); // FIXME: pass window ptr + + set_action_icon(); + init_container(); +} + +#ifndef NDEBUG +bool ObjectDataViewModelNode::valid() +{ + // Verify that the object was not deleted yet. + assert(m_idx >= -1); + return m_idx >= -1; +} +#endif /* NDEBUG */ + void ObjectDataViewModelNode::set_action_icon() { - m_action_icon_name = m_type == itObject ? "advanced_plus" : - m_type == itVolume ? "cog" : "set_separate_obj"; + m_action_icon_name = m_type & itObject ? "advanced_plus" : + m_type & (itVolume | itLayer) ? "cog" : /*m_type & itInstance*/ "set_separate_obj"; m_action_icon = create_scaled_bitmap(nullptr, m_action_icon_name); // FIXME: pass window ptr } +void ObjectDataViewModelNode::set_printable_icon(PrintIndicator printable) +{ + m_printable = printable; + m_printable_icon = m_printable == piUndef ? m_empty_bmp : + create_scaled_bitmap(nullptr, m_printable == piPrintable ? "eye_open.png" : "eye_closed.png"); +} + Slic3r::GUI::BitmapCache *m_bitmap_cache = nullptr; void ObjectDataViewModelNode::update_settings_digest_bitmaps() { @@ -510,6 +585,32 @@ void ObjectDataViewModelNode::msw_rescale() update_settings_digest_bitmaps(); } +bool ObjectDataViewModelNode::SetValue(const wxVariant& variant, unsigned col) +{ + switch (col) + { + case colPrint: + m_printable_icon << variant; + return true; + case colName: { + DataViewBitmapText data; + data << variant; + m_bmp = data.GetBitmap(); + m_name = data.GetText(); + return true; } + case colExtruder: { + const wxString & val = variant.GetString(); + m_extruder = val == "0" ? _(L("default")) : val; + return true; } + case colEditing: + m_action_icon << variant; + return true; + default: + printf("MyObjectTreeModel::SetValue: wrong column"); + } + return false; +} + void ObjectDataViewModelNode::SetIdx(const int& idx) { m_idx = idx; @@ -523,6 +624,22 @@ void ObjectDataViewModelNode::SetIdx(const int& idx) // ObjectDataViewModel // ---------------------------------------------------------------------------- +static int get_root_idx(ObjectDataViewModelNode *parent_node, const ItemType root_type) +{ + // because of istance_root and layers_root are at the end of the list, so + // start locking from the end + for (int root_idx = parent_node->GetChildCount() - 1; root_idx >= 0; root_idx--) + { + // if there is SettingsItem or VolumeItem, then RootItems don't exist in current ObjectItem + if (parent_node->GetNthChild(root_idx)->GetType() & (itSettings | itVolume)) + break; + if (parent_node->GetNthChild(root_idx)->GetType() & root_type) + return root_idx; + } + + return -1; +} + ObjectDataViewModel::ObjectDataViewModel() { m_bitmap_cache = new Slic3r::GUI::BitmapCache; @@ -567,10 +684,10 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent wxString extruder_str = extruder == 0 ? _(L("default")) : wxString::Format("%d", extruder); - // because of istance_root is a last item of the object - int insert_position = root->GetChildCount() - 1; - if (insert_position < 0 || root->GetNthChild(insert_position)->m_type != itInstanceRoot) - insert_position = -1; + // get insertion position according to the existed Layers and/or Instances Items + int insert_position = get_root_idx(root, itLayerRoot); + if (insert_position < 0) + insert_position = get_root_idx(root, itInstanceRoot); const bool obj_errors = root->m_bmp.IsOk(); @@ -586,7 +703,7 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent ItemAdded(parent_item, child); root->m_volumes_cnt++; - if (insert_position > 0) insert_position++; + if (insert_position >= 0) insert_position++; } const auto node = new ObjectDataViewModelNode(root, name, GetVolumeIcon(volume_type, has_errors), extruder_str, root->m_volumes_cnt); @@ -619,42 +736,86 @@ wxDataViewItem ObjectDataViewModel::AddSettingsChild(const wxDataViewItem &paren return child; } -int get_istances_root_idx(ObjectDataViewModelNode *parent_node) +/* return values: + * true => root_node is created and added to the parent_root + * false => root node alredy exists +*/ +static bool append_root_node(ObjectDataViewModelNode *parent_node, + ObjectDataViewModelNode **root_node, + const ItemType root_type) { - // because of istance_root is a last item of the object - const int inst_root_idx = parent_node->GetChildCount()-1; + const int inst_root_id = get_root_idx(parent_node, root_type); - if (inst_root_idx < 0 || parent_node->GetNthChild(inst_root_idx)->GetType() == itInstanceRoot) - return inst_root_idx; + *root_node = inst_root_id < 0 ? + new ObjectDataViewModelNode(parent_node, root_type) : + parent_node->GetNthChild(inst_root_id); - return -1; + if (inst_root_id < 0) { + if ((root_type&itInstanceRoot) || + (root_type&itLayerRoot) && get_root_idx(parent_node, itInstanceRoot)<0) + parent_node->Append(*root_node); + else if (root_type&itLayerRoot) + parent_node->Insert(*root_node, static_cast(get_root_idx(parent_node, itInstanceRoot))); + return true; + } + + return false; } -wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem &parent_item, size_t num) +wxDataViewItem ObjectDataViewModel::AddRoot(const wxDataViewItem &parent_item, ItemType root_type) { ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); if (!parent_node) return wxDataViewItem(0); - // Check and create/get instances root node - const int inst_root_id = get_istances_root_idx(parent_node); + // get InstanceRoot node + ObjectDataViewModelNode *root_node { nullptr }; + const bool appended = append_root_node(parent_node, &root_node, root_type); + if (!root_node) return wxDataViewItem(0); - ObjectDataViewModelNode *inst_root_node = inst_root_id < 0 ? - new ObjectDataViewModelNode(parent_node, itInstanceRoot) : - parent_node->GetNthChild(inst_root_id); - const wxDataViewItem inst_root_item((void*)inst_root_node); + const wxDataViewItem root_item((void*)root_node); - if (inst_root_id < 0) { - parent_node->Append(inst_root_node); - // notify control - ItemAdded(parent_item, inst_root_item); -// if (num == 1) num++; - } + if (appended) + ItemAdded(parent_item, root_item);// notify control + return root_item; +} + +wxDataViewItem ObjectDataViewModel::AddInstanceRoot(const wxDataViewItem &parent_item) +{ + return AddRoot(parent_item, itInstanceRoot); +} + +wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem &parent_item, size_t num) +{ + const std::vector print_indicator(num, true); + + return wxDataViewItem((void*)AddInstanceChild(parent_item, print_indicator)); +} + +wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem& parent_item, + const std::vector& print_indicator) +{ + const wxDataViewItem inst_root_item = AddInstanceRoot(parent_item); + if (!inst_root_item) return wxDataViewItem(0); + + ObjectDataViewModelNode* inst_root_node = (ObjectDataViewModelNode*)inst_root_item.GetID(); + + const bool just_created = inst_root_node->GetChildren().Count() == 0; // Add instance nodes ObjectDataViewModelNode *instance_node = nullptr; size_t counter = 0; - while (counter < num) { + while (counter < print_indicator.size()) { instance_node = new ObjectDataViewModelNode(inst_root_node, itInstance); + + // if InstanceRoot item is just created and start to adding Instances + if (just_created && counter == 0) { + ObjectDataViewModelNode* obj_node = (ObjectDataViewModelNode*)parent_item.GetID(); + // use object's printable state to first instance + instance_node->set_printable_icon(obj_node->IsPrintable()); + } + else + instance_node->set_printable_icon(print_indicator[counter] ? piPrintable : piUnprintable); + inst_root_node->Append(instance_node); // notify control const wxDataViewItem instance_item((void*)instance_node); @@ -662,9 +823,108 @@ wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem &paren ++counter; } + // update object_node printable property + UpdateObjectPrintable(parent_item); + return wxDataViewItem((void*)instance_node); } +void ObjectDataViewModel::UpdateObjectPrintable(wxDataViewItem parent_item) +{ + const wxDataViewItem inst_root_item = GetInstanceRootItem(parent_item); + if (!inst_root_item) + return; + + ObjectDataViewModelNode* inst_root_node = (ObjectDataViewModelNode*)inst_root_item.GetID(); + + const size_t child_cnt = inst_root_node->GetChildren().Count(); + PrintIndicator obj_pi = piUnprintable; + for (size_t i=0; i < child_cnt; i++) + if (inst_root_node->GetNthChild(i)->IsPrintable() & piPrintable) { + obj_pi = piPrintable; + break; + } + // and set printable state for object_node to piUndef + ObjectDataViewModelNode* obj_node = (ObjectDataViewModelNode*)parent_item.GetID(); + obj_node->set_printable_icon(obj_pi); + ItemChanged(parent_item); +} + +// update printable property for all instances from object +void ObjectDataViewModel::UpdateInstancesPrintable(wxDataViewItem parent_item) +{ + const wxDataViewItem inst_root_item = GetInstanceRootItem(parent_item); + if (!inst_root_item) + return; + + ObjectDataViewModelNode* obj_node = (ObjectDataViewModelNode*)parent_item.GetID(); + const PrintIndicator obj_pi = obj_node->IsPrintable(); + + ObjectDataViewModelNode* inst_root_node = (ObjectDataViewModelNode*)inst_root_item.GetID(); + const size_t child_cnt = inst_root_node->GetChildren().Count(); + + for (size_t i=0; i < child_cnt; i++) + { + ObjectDataViewModelNode* inst_node = inst_root_node->GetNthChild(i); + // and set printable state for object_node to piUndef + inst_node->set_printable_icon(obj_pi); + ItemChanged(wxDataViewItem((void*)inst_node)); + } +} + +bool ObjectDataViewModel::IsPrintable(const wxDataViewItem& item) const +{ + ObjectDataViewModelNode* node = (ObjectDataViewModelNode*)item.GetID(); + if (!node) + return false; + + return node->IsPrintable() == piPrintable; +} + +wxDataViewItem ObjectDataViewModel::AddLayersRoot(const wxDataViewItem &parent_item) +{ + return AddRoot(parent_item, itLayerRoot); +} + +wxDataViewItem ObjectDataViewModel::AddLayersChild(const wxDataViewItem &parent_item, + const t_layer_height_range& layer_range, + const int extruder/* = 0*/, + const int index /* = -1*/) +{ + ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); + if (!parent_node) return wxDataViewItem(0); + + wxString extruder_str = extruder == 0 ? _(L("default")) : wxString::Format("%d", extruder); + + // get LayerRoot node + ObjectDataViewModelNode *layer_root_node; + wxDataViewItem layer_root_item; + + if (parent_node->GetType() & itLayerRoot) { + layer_root_node = parent_node; + layer_root_item = parent_item; + } + else { + const int root_idx = get_root_idx(parent_node, itLayerRoot); + if (root_idx < 0) return wxDataViewItem(0); + layer_root_node = parent_node->GetNthChild(root_idx); + layer_root_item = wxDataViewItem((void*)layer_root_node); + } + + // Add layer node + ObjectDataViewModelNode *layer_node = new ObjectDataViewModelNode(layer_root_node, layer_range, index, extruder_str); + if (index < 0) + layer_root_node->Append(layer_node); + else + layer_root_node->Insert(layer_node, index); + + // notify control + const wxDataViewItem layer_item((void*)layer_node); + ItemAdded(layer_root_item, layer_item); + + return layer_item; +} + wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) { auto ret_item = wxDataViewItem(0); @@ -679,10 +939,14 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) // NOTE: MyObjectTreeModelNodePtrArray is only an array of _pointers_ // thus removing the node from it doesn't result in freeing it if (node_parent) { - if (node->m_type == itInstanceRoot) + if (node->m_type & (itInstanceRoot|itLayerRoot)) { - for (int i = node->GetChildCount() - 1; i > 0; i--) + // node can be deleted by the Delete, let's check its type while we safely can + bool is_instance_root = (node->m_type & itInstanceRoot); + + for (int i = node->GetChildCount() - 1; i >= (is_instance_root ? 1 : 0); i--) Delete(wxDataViewItem(node->GetNthChild(i))); + return parent; } @@ -690,7 +954,7 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) auto idx = node->GetIdx(); - if (node->m_type == itVolume) { + if (node->m_type & (itVolume|itLayer)) { node_parent->m_volumes_cnt--; DeleteSettings(item); } @@ -717,10 +981,31 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) ItemDeleted(parent, item); ObjectDataViewModelNode *last_instance_node = node_parent->GetNthChild(0); + PrintIndicator last_instance_printable = last_instance_node->IsPrintable(); node_parent->GetChildren().Remove(last_instance_node); delete last_instance_node; ItemDeleted(parent, wxDataViewItem(last_instance_node)); + ObjectDataViewModelNode *obj_node = node_parent->GetParent(); + obj_node->set_printable_icon(last_instance_printable); + obj_node->GetChildren().Remove(node_parent); + delete node_parent; + ret_item = wxDataViewItem(obj_node); + +#ifndef __WXGTK__ + if (obj_node->GetChildCount() == 0) + obj_node->m_container = false; +#endif //__WXGTK__ + ItemDeleted(ret_item, wxDataViewItem(node_parent)); + return ret_item; + } + + if (node->m_type & itInstance) + UpdateObjectPrintable(wxDataViewItem(node_parent->GetParent())); + + // if there was last layer item, delete this one and layers root item + if (node_parent->GetChildCount() == 0 && node_parent->m_type == itLayerRoot) + { ObjectDataViewModelNode *obj_node = node_parent->GetParent(); obj_node->GetChildren().Remove(node_parent); delete node_parent; @@ -735,7 +1020,7 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) } // if there is last volume item after deleting, delete this last volume too - if (node_parent->GetChildCount() <= 3) + if (node_parent->GetChildCount() <= 3) // 3??? #ys_FIXME { int vol_cnt = 0; int vol_idx = 0; @@ -817,7 +1102,7 @@ wxDataViewItem ObjectDataViewModel::DeleteLastInstance(const wxDataViewItem &par ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); if (!parent_node) return ret_item; - const int inst_root_id = get_istances_root_idx(parent_node); + const int inst_root_id = get_root_idx(parent_node, itInstanceRoot); if (inst_root_id < 0) return ret_item; wxDataViewItemArray items; @@ -827,9 +1112,12 @@ wxDataViewItem ObjectDataViewModel::DeleteLastInstance(const wxDataViewItem &par const int inst_cnt = inst_root_node->GetChildCount(); const bool delete_inst_root_item = inst_cnt - num < 2 ? true : false; + PrintIndicator last_inst_printable = piUndef; + int stop = delete_inst_root_item ? 0 : inst_cnt - num; for (int i = inst_cnt - 1; i >= stop;--i) { ObjectDataViewModelNode *last_instance_node = inst_root_node->GetNthChild(i); + if (i==0) last_inst_printable = last_instance_node->IsPrintable(); inst_root_node->GetChildren().Remove(last_instance_node); delete last_instance_node; ItemDeleted(inst_root_item, wxDataViewItem(last_instance_node)); @@ -838,13 +1126,18 @@ wxDataViewItem ObjectDataViewModel::DeleteLastInstance(const wxDataViewItem &par if (delete_inst_root_item) { ret_item = parent_item; parent_node->GetChildren().Remove(inst_root_node); + parent_node->set_printable_icon(last_inst_printable); ItemDeleted(parent_item, inst_root_item); + ItemChanged(parent_item); #ifndef __WXGTK__ if (parent_node->GetChildCount() == 0) parent_node->m_container = false; #endif //__WXGTK__ } + // update object_node printable property + UpdateObjectPrintable(parent_item); + return ret_item; } @@ -974,28 +1267,67 @@ wxDataViewItem ObjectDataViewModel::GetItemByVolumeId(int obj_idx, int volume_id return wxDataViewItem(0); } -wxDataViewItem ObjectDataViewModel::GetItemByInstanceId(int obj_idx, int inst_idx) +wxDataViewItem ObjectDataViewModel::GetItemById(const int obj_idx, const int sub_obj_idx, const ItemType parent_type) { if (obj_idx >= m_objects.size() || obj_idx < 0) { printf("Error! Out of objects range.\n"); return wxDataViewItem(0); } - auto instances_item = GetInstanceRootItem(wxDataViewItem(m_objects[obj_idx])); - if (!instances_item) + auto item = GetItemByType(wxDataViewItem(m_objects[obj_idx]), parent_type); + if (!item) return wxDataViewItem(0); - auto parent = (ObjectDataViewModelNode*)instances_item.GetID();; + auto parent = (ObjectDataViewModelNode*)item.GetID(); for (size_t i = 0; i < parent->GetChildCount(); i++) - if (parent->GetNthChild(i)->m_idx == inst_idx) + if (parent->GetNthChild(i)->m_idx == sub_obj_idx) return wxDataViewItem(parent->GetNthChild(i)); return wxDataViewItem(0); } +wxDataViewItem ObjectDataViewModel::GetItemByInstanceId(int obj_idx, int inst_idx) +{ + return GetItemById(obj_idx, inst_idx, itInstanceRoot); +} + +wxDataViewItem ObjectDataViewModel::GetItemByLayerId(int obj_idx, int layer_idx) +{ + return GetItemById(obj_idx, layer_idx, itLayerRoot); +} + +wxDataViewItem ObjectDataViewModel::GetItemByLayerRange(const int obj_idx, const t_layer_height_range& layer_range) +{ + if (obj_idx >= m_objects.size() || obj_idx < 0) { + printf("Error! Out of objects range.\n"); + return wxDataViewItem(0); + } + + auto item = GetItemByType(wxDataViewItem(m_objects[obj_idx]), itLayerRoot); + if (!item) + return wxDataViewItem(0); + + auto parent = (ObjectDataViewModelNode*)item.GetID(); + for (size_t i = 0; i < parent->GetChildCount(); i++) + if (parent->GetNthChild(i)->m_layer_range == layer_range) + return wxDataViewItem(parent->GetNthChild(i)); + + return wxDataViewItem(0); +} + +int ObjectDataViewModel::GetItemIdByLayerRange(const int obj_idx, const t_layer_height_range& layer_range) +{ + wxDataViewItem item = GetItemByLayerRange(obj_idx, layer_range); + if (!item) + return -1; + + return GetLayerIdByItem(item); +} + int ObjectDataViewModel::GetIdByItem(const wxDataViewItem& item) const { - wxASSERT(item.IsOk()); + if(!item.IsOk()) + return -1; ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); auto it = find(m_objects.begin(), m_objects.end(), node); @@ -1030,13 +1362,28 @@ int ObjectDataViewModel::GetInstanceIdByItem(const wxDataViewItem& item) const return GetIdByItemAndType(item, itInstance); } +int ObjectDataViewModel::GetLayerIdByItem(const wxDataViewItem& item) const +{ + return GetIdByItemAndType(item, itLayer); +} + +t_layer_height_range ObjectDataViewModel::GetLayerRangeByItem(const wxDataViewItem& item) const +{ + wxASSERT(item.IsOk()); + + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + if (!node || node->m_type != itLayer) + return { 0.0f, 0.0f }; + return node->GetLayerRange(); +} + void ObjectDataViewModel::GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx) { wxASSERT(item.IsOk()); type = itUndef; ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); - if (!node || node->GetIdx() <-1 || node->GetIdx() ==-1 && !(node->GetType() & (itObject | itSettings | itInstanceRoot))) + if (!node || node->GetIdx() <-1 || node->GetIdx() == -1 && !(node->GetType() & (itObject | itSettings | itInstanceRoot | itLayerRoot/* | itLayer*/))) return; idx = node->GetIdx(); @@ -1044,9 +1391,10 @@ void ObjectDataViewModel::GetItemInfo(const wxDataViewItem& item, ItemType& type ObjectDataViewModelNode *parent_node = node->GetParent(); if (!parent_node) return; - if (type == itInstance) - parent_node = node->GetParent()->GetParent(); - if (!parent_node || parent_node->m_type != itObject) { type = itUndef; return; } + + // get top parent (Object) node + while (parent_node->m_type != itObject) + parent_node = parent_node->GetParent(); auto it = find(m_objects.begin(), m_objects.end(), parent_node); if (it != m_objects.end()) @@ -1093,6 +1441,18 @@ int ObjectDataViewModel::GetRowByItem(const wxDataViewItem& item) const return -1; } +bool ObjectDataViewModel::InvalidItem(const wxDataViewItem& item) +{ + if (!item) + return true; + + ObjectDataViewModelNode* node = (ObjectDataViewModelNode*)item.GetID(); + if (!node || node->invalid()) + return true; + + return false; +} + wxString ObjectDataViewModel::GetName(const wxDataViewItem &item) const { ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); @@ -1115,13 +1475,16 @@ void ObjectDataViewModel::GetValue(wxVariant &variant, const wxDataViewItem &ite ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); switch (col) { - case 0: + case colPrint: + variant << node->m_printable_icon; + break; + case colName: variant << DataViewBitmapText(node->m_name, node->m_bmp); break; - case 1: + case colExtruder: variant = node->m_extruder; break; - case 2: + case colEditing: variant << node->m_action_icon; break; default: @@ -1184,7 +1547,7 @@ bool ObjectDataViewModel::IsEnabled(const wxDataViewItem &item, unsigned int col ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); // disable extruder selection for the non "itObject|itVolume" item - return !(col == 1 && node->m_extruder.IsEmpty()); + return !(col == colExtruder && node->m_extruder.IsEmpty()); } wxDataViewItem ObjectDataViewModel::GetParent(const wxDataViewItem &item) const @@ -1194,6 +1557,7 @@ wxDataViewItem ObjectDataViewModel::GetParent(const wxDataViewItem &item) const return wxDataViewItem(0); ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + assert(node != nullptr && node->valid()); // objects nodes has no parent too if (node->m_type == itObject) @@ -1214,10 +1578,7 @@ wxDataViewItem ObjectDataViewModel::GetTopParent(const wxDataViewItem &item) con ObjectDataViewModelNode *parent_node = node->GetParent(); while (parent_node->m_type != itObject) - { - node = parent_node; - parent_node = node->GetParent(); - } + parent_node = parent_node->GetParent(); return wxDataViewItem((void*)parent_node); } @@ -1288,7 +1649,7 @@ ItemType ObjectDataViewModel::GetItemType(const wxDataViewItem &item) const if (!item.IsOk()) return itUndef; ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); - return node->m_type; + return node->m_type < 0 ? itUndef : node->m_type; } wxDataViewItem ObjectDataViewModel::GetItemByType(const wxDataViewItem &parent_item, ItemType type) const @@ -1318,6 +1679,11 @@ wxDataViewItem ObjectDataViewModel::GetInstanceRootItem(const wxDataViewItem &it return GetItemByType(item, itInstanceRoot); } +wxDataViewItem ObjectDataViewModel::GetLayerRootItem(const wxDataViewItem &item) const +{ + return GetItemByType(item, itLayerRoot); +} + bool ObjectDataViewModel::IsSettingsItem(const wxDataViewItem &item) const { if (!item.IsOk()) @@ -1346,6 +1712,46 @@ void ObjectDataViewModel::SetVolumeType(const wxDataViewItem &item, const Slic3r ItemChanged(item); } +wxDataViewItem ObjectDataViewModel::SetPrintableState( + PrintIndicator printable, + int obj_idx, + int subobj_idx /* = -1*/, + ItemType subobj_type/* = itInstance*/) +{ + wxDataViewItem item = wxDataViewItem(0); + if (subobj_idx < 0) + item = GetItemById(obj_idx); + else + item = subobj_type&itInstance ? GetItemByInstanceId(obj_idx, subobj_idx) : + GetItemByVolumeId(obj_idx, subobj_idx); + + ObjectDataViewModelNode* node = (ObjectDataViewModelNode*)item.GetID(); + if (!node) + return wxDataViewItem(0); + node->set_printable_icon(printable); + ItemChanged(item); + + if (subobj_idx >= 0) + UpdateObjectPrintable(GetItemById(obj_idx)); + + return item; +} + +wxDataViewItem ObjectDataViewModel::SetObjectPrintableState( + PrintIndicator printable, + wxDataViewItem obj_item) +{ + ObjectDataViewModelNode* node = (ObjectDataViewModelNode*)obj_item.GetID(); + if (!node) + return wxDataViewItem(0); + node->set_printable_icon(printable); + ItemChanged(obj_item); + + UpdateInstancesPrintable(obj_item); + + return obj_item; +} + void ObjectDataViewModel::Rescale() { wxDataViewItemArray all_items; @@ -1530,7 +1936,7 @@ bool BitmapTextRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value // The icon can't be edited so get its old value and reuse it. wxVariant valueOld; - GetView()->GetModel()->GetValue(valueOld, m_item, 0); + GetView()->GetModel()->GetValue(valueOld, m_item, colName); DataViewBitmapText bmpText; bmpText << valueOld; @@ -1583,10 +1989,14 @@ DoubleSlider::DoubleSlider( wxWindow *parent, m_bmp_one_layer_unlock_off = ScalableBitmap(this, "one_layer_unlock_off.png"); m_lock_icon_dim = m_bmp_one_layer_lock_on.bmp().GetSize().x; + m_bmp_revert = ScalableBitmap(this, "undo"); + m_revert_icon_dim = m_bmp_revert.bmp().GetSize().x; + m_selection = ssUndef; // slider events Bind(wxEVT_PAINT, &DoubleSlider::OnPaint, this); + Bind(wxEVT_CHAR, &DoubleSlider::OnChar, this); Bind(wxEVT_LEFT_DOWN, &DoubleSlider::OnLeftDown, this); Bind(wxEVT_MOTION, &DoubleSlider::OnMotion, this); Bind(wxEVT_LEFT_UP, &DoubleSlider::OnLeftUp, this); @@ -1637,6 +2047,9 @@ void DoubleSlider::msw_rescale() m_bmp_one_layer_unlock_off.msw_rescale(); m_lock_icon_dim = m_bmp_one_layer_lock_on.bmp().GetSize().x; + m_bmp_revert.msw_rescale(); + m_revert_icon_dim = m_bmp_revert.bmp().GetSize().x; + SLIDER_MARGIN = 4 + Slic3r::GUI::wxGetApp().em_unit(); SetMinSize(get_min_size()); @@ -1873,8 +2286,11 @@ void DoubleSlider::render() //draw color print ticks draw_ticks(dc); - //draw color print ticks + //draw lock/unlock draw_one_layer_icon(dc); + + //draw revert bitmap (if it's shown) + draw_revert_icon(dc); } void DoubleSlider::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end) @@ -2017,6 +2433,9 @@ void DoubleSlider::draw_thumbs(wxDC& dc, const wxCoord& lower_pos, const wxCoord void DoubleSlider::draw_ticks(wxDC& dc) { + if (!m_is_enabled_tick_manipulation) + return; + dc.SetPen(m_is_enabled_tick_manipulation ? DARK_GREY_PEN : LIGHT_GREY_PEN ); int height, width; get_size(&width, &height); @@ -2034,6 +2453,9 @@ void DoubleSlider::draw_ticks(wxDC& dc) void DoubleSlider::draw_colored_band(wxDC& dc) { + if (!m_is_enabled_tick_manipulation) + return; + int height, width; get_size(&width, &height); @@ -2101,6 +2523,24 @@ void DoubleSlider::draw_one_layer_icon(wxDC& dc) m_rect_one_layer_icon = wxRect(x_draw, y_draw, m_lock_icon_dim, m_lock_icon_dim); } +void DoubleSlider::draw_revert_icon(wxDC& dc) +{ + if (m_ticks.empty() || !m_is_enabled_tick_manipulation) + return; + + int width, height; + get_size(&width, &height); + + wxCoord x_draw, y_draw; + is_horizontal() ? x_draw = width-2 : x_draw = 0.25*SLIDER_MARGIN; + is_horizontal() ? y_draw = 0.25*SLIDER_MARGIN: y_draw = height-2; + + dc.DrawBitmap(m_bmp_revert.bmp(), x_draw, y_draw); + + //update rect of the lock/unlock icon + m_rect_revert_icon = wxRect(x_draw, y_draw, m_revert_icon_dim, m_revert_icon_dim); +} + void DoubleSlider::update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection) { const wxRect& rect = wxRect(begin_x, begin_y, m_thumb_size.x, m_thumb_size.y); @@ -2117,8 +2557,8 @@ int DoubleSlider::get_value_from_position(const wxCoord x, const wxCoord y) if (is_horizontal()) return int(double(x - SLIDER_MARGIN) / step + 0.5); - else - return int(m_min_value + double(height - SLIDER_MARGIN - y) / step + 0.5); + + return int(m_min_value + double(height - SLIDER_MARGIN - y) / step + 0.5); } void DoubleSlider::detect_selected_slider(const wxPoint& pt) @@ -2168,7 +2608,10 @@ void DoubleSlider::ChangeOneLayerLock() void DoubleSlider::OnLeftDown(wxMouseEvent& event) { + if (HasCapture()) + return; this->CaptureMouse(); + wxClientDC dc(this); wxPoint pos = event.GetLogicalPosition(dc); if (is_point_in_rect(pos, m_rect_tick_action) && m_is_enabled_tick_manipulation) { @@ -2178,6 +2621,7 @@ void DoubleSlider::OnLeftDown(wxMouseEvent& event) m_is_left_down = true; if (is_point_in_rect(pos, m_rect_one_layer_icon)) { + // switch on/off one layer mode m_is_one_layer = !m_is_one_layer; if (!m_is_one_layer) { SetLowerValue(m_min_value); @@ -2186,20 +2630,36 @@ void DoubleSlider::OnLeftDown(wxMouseEvent& event) m_selection == ssLower ? correct_lower_value() : correct_higher_value(); if (!m_selection) m_selection = ssHigher; } + else if (is_point_in_rect(pos, m_rect_revert_icon) && m_is_enabled_tick_manipulation) { + // discard all color changes + SetLowerValue(m_min_value); + SetHigherValue(m_max_value); + + m_selection == ssLower ? correct_lower_value() : correct_higher_value(); + if (!m_selection) m_selection = ssHigher; + + m_ticks.clear(); + wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED)); + } else detect_selected_slider(pos); - if (!m_selection && m_is_enabled_tick_manipulation) { - const auto tick = is_point_near_tick(pos); - if (tick >= 0) + if (!m_selection) { + const int tick_val = is_point_near_tick(pos); + /* Set current thumb position to the nearest tick (if it is) + * OR to a value corresponding to the mouse click + * */ + const int mouse_val = tick_val >= 0 && m_is_enabled_tick_manipulation ? tick_val : + get_value_from_position(pos.x, pos.y); + if (mouse_val >= 0) { - if (abs(tick - m_lower_value) < abs(tick - m_higher_value)) { - SetLowerValue(tick); + if (abs(mouse_val - m_lower_value) < abs(mouse_val - m_higher_value)) { + SetLowerValue(mouse_val); correct_lower_value(); m_selection = ssLower; } else { - SetHigherValue(tick); + SetHigherValue(mouse_val); correct_higher_value(); m_selection = ssHigher; } @@ -2239,9 +2699,13 @@ void DoubleSlider::OnMotion(wxMouseEvent& event) const wxClientDC dc(this); const wxPoint pos = event.GetLogicalPosition(dc); + m_is_one_layer_icon_focesed = is_point_in_rect(pos, m_rect_one_layer_icon); + bool is_revert_icon_focused = false; + if (!m_is_left_down && !m_is_one_layer) { m_is_action_icon_focesed = is_point_in_rect(pos, m_rect_tick_action); + is_revert_icon_focused = !m_ticks.empty() && is_point_in_rect(pos, m_rect_revert_icon); } else if (m_is_left_down || m_is_right_down) { if (m_selection == ssLower) { @@ -2261,6 +2725,12 @@ void DoubleSlider::OnMotion(wxMouseEvent& event) Update(); event.Skip(); + // Set tooltips with information for each icon + const wxString tooltip = m_is_one_layer_icon_focesed ? _(L("One layer mode")) : + m_is_action_icon_focesed ? _(L("Add/Del color change")) : + is_revert_icon_focused ? _(L("Discard all color changes")) : ""; + this->SetToolTip(tooltip); + if (action) { wxCommandEvent e(wxEVT_SCROLL_CHANGED); @@ -2366,9 +2836,9 @@ void DoubleSlider::OnWheel(wxMouseEvent& event) void DoubleSlider::OnKeyDown(wxKeyEvent &event) { const int key = event.GetKeyCode(); - if (key == '+' || key == WXK_NUMPAD_ADD) + if (key == WXK_NUMPAD_ADD) action_tick(taAdd); - else if (key == '-' || key == 390 || key == WXK_DELETE || key == WXK_BACK) + else if (key == 390 || key == WXK_DELETE || key == WXK_BACK) action_tick(taDel); else if (is_horizontal()) { @@ -2387,6 +2857,8 @@ void DoubleSlider::OnKeyDown(wxKeyEvent &event) else if (key == WXK_UP || key == WXK_DOWN) move_current_thumb(key == WXK_UP); } + + event.Skip(); // !Needed to have EVT_CHAR generated as well } void DoubleSlider::OnKeyUp(wxKeyEvent &event) @@ -2398,9 +2870,20 @@ void DoubleSlider::OnKeyUp(wxKeyEvent &event) event.Skip(); } +void DoubleSlider::OnChar(wxKeyEvent& event) +{ + const int key = event.GetKeyCode(); + if (key == '+') + action_tick(taAdd); + else if (key == '-') + action_tick(taDel); +} + void DoubleSlider::OnRightDown(wxMouseEvent& event) { + if (HasCapture()) return; this->CaptureMouse(); + const wxClientDC dc(this); detect_selected_slider(event.GetLogicalPosition(dc)); if (!m_selection) @@ -2441,27 +2924,29 @@ LockButton::LockButton( wxWindow *parent, const wxSize& size /*= wxDefaultSize*/): wxButton(parent, id, wxEmptyString, pos, size, wxBU_EXACTFIT | wxNO_BORDER) { - m_bmp_lock_on = ScalableBitmap(this, "one_layer_lock_on.png"); - m_bmp_lock_off = ScalableBitmap(this, "one_layer_lock_off.png"); - m_bmp_unlock_on = ScalableBitmap(this, "one_layer_unlock_on.png"); - m_bmp_unlock_off = ScalableBitmap(this, "one_layer_unlock_off.png"); + m_bmp_lock_closed = ScalableBitmap(this, "lock_closed"); + m_bmp_lock_closed_f = ScalableBitmap(this, "lock_closed_f"); + m_bmp_lock_open = ScalableBitmap(this, "lock_open"); + m_bmp_lock_open_f = ScalableBitmap(this, "lock_open_f"); #ifdef __WXMSW__ SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); #endif // __WXMSW__ - SetBitmap(m_bmp_unlock_on.bmp()); - SetBitmapDisabled(m_bmp_lock_on.bmp()); + SetBitmap(m_bmp_lock_open.bmp()); + SetBitmapDisabled(m_bmp_lock_open.bmp()); + SetBitmapHover(m_bmp_lock_closed_f.bmp()); //button events - Bind(wxEVT_BUTTON, &LockButton::OnButton, this); - Bind(wxEVT_ENTER_WINDOW, &LockButton::OnEnterBtn, this); - Bind(wxEVT_LEAVE_WINDOW, &LockButton::OnLeaveBtn, this); + Bind(wxEVT_BUTTON, &LockButton::OnButton, this); } void LockButton::OnButton(wxCommandEvent& event) { + if (m_disabled) + return; + m_is_pushed = !m_is_pushed; - enter_button(true); + update_button_bitmaps(); event.Skip(); } @@ -2469,23 +2954,21 @@ void LockButton::OnButton(wxCommandEvent& event) void LockButton::SetLock(bool lock) { m_is_pushed = lock; - enter_button(true); + update_button_bitmaps(); } void LockButton::msw_rescale() { - m_bmp_lock_on .msw_rescale(); - m_bmp_lock_off .msw_rescale(); - m_bmp_unlock_on .msw_rescale(); - m_bmp_unlock_off.msw_rescale(); + m_bmp_lock_closed.msw_rescale(); + m_bmp_lock_closed_f.msw_rescale(); + m_bmp_lock_open.msw_rescale(); + m_bmp_lock_open_f.msw_rescale(); } -void LockButton::enter_button(const bool enter) +void LockButton::update_button_bitmaps() { - const wxBitmap& icon = m_is_pushed ? - enter ? m_bmp_lock_off.bmp() : m_bmp_lock_on.bmp() : - enter ? m_bmp_unlock_off.bmp() : m_bmp_unlock_on.bmp(); - SetBitmap(icon); + SetBitmap(m_is_pushed ? m_bmp_lock_closed.bmp() : m_bmp_lock_open.bmp()); + SetBitmapHover(m_is_pushed ? m_bmp_lock_closed_f.bmp() : m_bmp_lock_open_f.bmp()); Refresh(); Update(); @@ -2503,11 +2986,13 @@ ModeButton::ModeButton( wxWindow * parent, const wxString& mode /* = wxEmptyString*/, const wxSize& size /* = wxDefaultSize*/, const wxPoint& pos /* = wxDefaultPosition*/) : - ScalableButton(parent, id, icon_name, mode, size, pos) + ScalableButton(parent, id, icon_name, mode, size, pos, wxBU_EXACTFIT) { m_tt_focused = wxString::Format(_(L("Switch to the %s mode")), mode); m_tt_selected = wxString::Format(_(L("Current mode is %s")), mode); + SetBitmapMargins(3, 0); + //button events Bind(wxEVT_BUTTON, &ModeButton::OnButton, this); Bind(wxEVT_ENTER_WINDOW, &ModeButton::OnEnterBtn, this); @@ -2536,6 +3021,7 @@ void ModeButton::focus_button(const bool focus) Slic3r::GUI::wxGetApp().normal_font(); SetFont(new_font); + SetForegroundColour(wxSystemSettings::GetColour(focus ? wxSYS_COLOUR_BTNTEXT : wxSYS_COLOUR_BTNSHADOW)); Refresh(); Update(); @@ -2546,7 +3032,7 @@ void ModeButton::focus_button(const bool focus) // ModeSizer // ---------------------------------------------------------------------------- -ModeSizer::ModeSizer(wxWindow *parent, int hgap/* = 10*/) : +ModeSizer::ModeSizer(wxWindow *parent, int hgap/* = 0*/) : wxFlexGridSizer(3, 0, hgap) { SetFlexibleDirection(wxHORIZONTAL); @@ -2564,16 +3050,9 @@ ModeSizer::ModeSizer(wxWindow *parent, int hgap/* = 10*/) : m_mode_btns.reserve(3); for (const auto& button : buttons) { -#ifdef __WXOSX__ - wxSize sz = parent->GetTextExtent(button.first); - // set default width for ModeButtons to correct rendering on OnFocus under OSX - sz.x += 2 * em_unit(parent); - m_mode_btns.push_back(new ModeButton(parent, wxID_ANY, button.second, button.first, sz)); -#else - m_mode_btns.push_back(new ModeButton(parent, wxID_ANY, button.second, button.first));; -#endif // __WXOSX__ - - m_mode_btns.back()->Bind(wxEVT_BUTTON, std::bind(modebtnfn, std::placeholders::_1, m_mode_btns.size() - 1)); + m_mode_btns.push_back(new ModeButton(parent, wxID_ANY, button.second, button.first)); + + m_mode_btns.back()->Bind(wxEVT_BUTTON, std::bind(modebtnfn, std::placeholders::_1, int(m_mode_btns.size() - 1))); Add(m_mode_btns.back()); } } @@ -2658,6 +3137,13 @@ ScalableButton::ScalableButton( wxWindow * parent, #endif // __WXMSW__ SetBitmap(create_scaled_bitmap(parent, icon_name)); + + if (size != wxDefaultSize) + { + const int em = em_unit(parent); + m_width = size.x/em; + m_height= size.y/em; + } } @@ -2684,11 +3170,24 @@ void ScalableButton::SetBitmap_(const ScalableBitmap& bmp) m_current_icon_name = bmp.name(); } +void ScalableButton::SetBitmapDisabled_(const ScalableBitmap& bmp) +{ + SetBitmapDisabled(bmp.bmp()); + m_disabled_icon_name = bmp.name(); +} + void ScalableButton::msw_rescale() { - const wxBitmap bmp = create_scaled_bitmap(m_parent, m_current_icon_name); + SetBitmap(create_scaled_bitmap(m_parent, m_current_icon_name)); + if (!m_disabled_icon_name.empty()) + SetBitmapDisabled(create_scaled_bitmap(m_parent, m_disabled_icon_name)); - SetBitmap(bmp); + if (m_width > 0 || m_height>0) + { + const int em = em_unit(m_parent); + wxSize size(m_width * em, m_height * em); + SetMinSize(size); + } } diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 78fb7be55b..14ceab8ff3 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -11,15 +11,19 @@ #include #include #include +#include #include #include #include namespace Slic3r { - enum class ModelVolumeType : int; + enum class ModelVolumeType : int; }; +typedef double coordf_t; +typedef std::pair t_layer_height_range; + #ifdef __WXMSW__ void msw_rescale_menu(wxMenu* menu); #else /* __WXMSW__ */ @@ -33,11 +37,14 @@ wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const std::function cb, const std::string& icon = "", wxEvtHandler* event_handler = nullptr, std::function const cb_condition = []() { return true; }, wxWindow* parent = nullptr); -wxMenuItem* append_submenu(wxMenu* menu, wxMenu* sub_menu, int id, const wxString& string, const wxString& description, +wxMenuItem* append_submenu(wxMenu* menu, wxMenu* sub_menu, int id, const wxString& string, const wxString& description, const std::string& icon = "", std::function const cb_condition = []() { return true; }, wxWindow* parent = nullptr); -wxMenuItem* append_menu_radio_item(wxMenu* menu, int id, const wxString& string, const wxString& description, +wxMenuItem* append_menu_radio_item(wxMenu* menu, int id, const wxString& string, const wxString& description, + std::function cb, wxEvtHandler* event_handler); + +wxMenuItem* append_menu_check_item(wxMenu* menu, int id, const wxString& string, const wxString& description, std::function cb, wxEvtHandler* event_handler); class wxDialog; @@ -45,7 +52,7 @@ void edit_tooltip(wxString& tooltip); void msw_buttons_rescale(wxDialog* dlg, const int em_unit, const std::vector& btn_ids); int em_unit(wxWindow* win); -wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name, +wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name, const int px_cnt = 16, const bool is_horizontal = false, const bool grayscale = false); class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup @@ -89,23 +96,23 @@ public: class wxDataViewTreeCtrlComboPopup: public wxDataViewTreeCtrl, public wxComboPopup { - static const unsigned int DefaultWidth; - static const unsigned int DefaultHeight; - static const unsigned int DefaultItemHeight; + static const unsigned int DefaultWidth; + static const unsigned int DefaultHeight; + static const unsigned int DefaultItemHeight; - wxString m_text; - int m_cnt_open_items{0}; + wxString m_text; + int m_cnt_open_items{0}; public: - virtual bool Create(wxWindow* parent); - virtual wxWindow* GetControl() { return this; } - virtual void SetStringValue(const wxString& value) { m_text = value; } - virtual wxString GetStringValue() const { return m_text; } + virtual bool Create(wxWindow* parent); + virtual wxWindow* GetControl() { return this; } + virtual void SetStringValue(const wxString& value) { m_text = value; } + virtual wxString GetStringValue() const { return m_text; } // virtual wxSize GetAdjustedSize(int minWidth, int prefHeight, int maxHeight); - virtual void OnKeyEvent(wxKeyEvent& evt); - void OnDataViewTreeCtrlSelection(wxCommandEvent& evt); - void SetItemsCnt(int cnt) { m_cnt_open_items = cnt; } + virtual void OnKeyEvent(wxKeyEvent& evt); + void OnDataViewTreeCtrlSelection(wxCommandEvent& evt); + void SetItemsCnt(int cnt) { m_cnt_open_items = cnt; } }; @@ -118,7 +125,7 @@ class DataViewBitmapText : public wxObject public: DataViewBitmapText( const wxString &text = wxEmptyString, const wxBitmap& bmp = wxNullBitmap) : - m_text(text), + m_text(text), m_bmp(bmp) { } @@ -155,16 +162,33 @@ DECLARE_VARIANT_OBJECT(DataViewBitmapText) // ---------------------------------------------------------------------------- -// ObjectDataViewModelNode: a node inside PrusaObjectDataViewModel +// ObjectDataViewModelNode: a node inside ObjectDataViewModel // ---------------------------------------------------------------------------- enum ItemType { - itUndef = 0, - itObject = 1, - itVolume = 2, - itInstanceRoot = 4, - itInstance = 8, - itSettings = 16 + itUndef = 0, + itObject = 1, + itVolume = 2, + itInstanceRoot = 4, + itInstance = 8, + itSettings = 16, + itLayerRoot = 32, + itLayer = 64, +}; + +enum ColumnNumber +{ + colName = 0, // item name + colPrint , // printable property + colExtruder , // extruder selection + colEditing , // item editing +}; + +enum PrintIndicator +{ + piUndef = 0, // no print indicator + piPrintable , // printable + piUnprintable , // unprintable }; class ObjectDataViewModelNode; @@ -172,11 +196,12 @@ WX_DEFINE_ARRAY_PTR(ObjectDataViewModelNode*, MyObjectTreeModelNodePtrArray); class ObjectDataViewModelNode { - ObjectDataViewModelNode* m_parent; - MyObjectTreeModelNodePtrArray m_children; + ObjectDataViewModelNode* m_parent; + MyObjectTreeModelNodePtrArray m_children; wxBitmap m_empty_bmp; size_t m_volumes_cnt = 0; std::vector< std::string > m_opt_categories; + t_layer_height_range m_layer_range = { 0.0f, 0.0f }; wxString m_name; wxBitmap& m_bmp = m_empty_bmp; @@ -185,175 +210,166 @@ class ObjectDataViewModelNode bool m_container = false; wxString m_extruder = "default"; wxBitmap m_action_icon; + PrintIndicator m_printable {piUndef}; + wxBitmap m_printable_icon; std::string m_action_icon_name = ""; Slic3r::ModelVolumeType m_volume_type; public: - ObjectDataViewModelNode(const wxString &name, + ObjectDataViewModelNode(const wxString &name, const wxString& extruder): m_parent(NULL), m_name(name), m_type(itObject), m_extruder(extruder) { -#ifdef __WXGTK__ - // it's necessary on GTK because of control have to know if this item will be container - // in another case you couldn't to add subitem for this item - // it will be produce "segmentation fault" - m_container = true; -#endif //__WXGTK__ - set_action_icon(); + init_container(); } - ObjectDataViewModelNode(ObjectDataViewModelNode* parent, - const wxString& sub_obj_name, - const wxBitmap& bmp, - const wxString& extruder, + ObjectDataViewModelNode(ObjectDataViewModelNode* parent, + const wxString& sub_obj_name, + const wxBitmap& bmp, + const wxString& extruder, const int idx = -1 ) : m_parent (parent), - m_name (sub_obj_name), - m_type (itVolume), + m_name (sub_obj_name), + m_type (itVolume), m_idx (idx), m_extruder (extruder) { - m_bmp = bmp; -#ifdef __WXGTK__ - // it's necessary on GTK because of control have to know if this item will be container - // in another case you couldn't to add subitem for this item - // it will be produce "segmentation fault" - m_container = true; -#endif //__WXGTK__ - + m_bmp = bmp; set_action_icon(); + init_container(); } + ObjectDataViewModelNode(ObjectDataViewModelNode* parent, + const t_layer_height_range& layer_range, + const int idx = -1, + const wxString& extruder = wxEmptyString ); + ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const ItemType type); - ~ObjectDataViewModelNode() - { - // free all our children nodes - size_t count = m_children.GetCount(); - for (size_t i = 0; i < count; i++) - { - ObjectDataViewModelNode *child = m_children[i]; - delete child; - } - } + ~ObjectDataViewModelNode() + { + // free all our children nodes + size_t count = m_children.GetCount(); + for (size_t i = 0; i < count; i++) + { + ObjectDataViewModelNode *child = m_children[i]; + delete child; + } +#ifndef NDEBUG + // Indicate that the object was deleted. + m_idx = -2; +#endif /* NDEBUG */ + } + void init_container(); bool IsContainer() const { return m_container; } - ObjectDataViewModelNode* GetParent() - { - return m_parent; - } - MyObjectTreeModelNodePtrArray& GetChildren() - { - return m_children; - } - ObjectDataViewModelNode* GetNthChild(unsigned int n) - { - return m_children.Item(n); - } - void Insert(ObjectDataViewModelNode* child, unsigned int n) - { - if (!m_container) - m_container = true; - m_children.Insert(child, n); - } - void Append(ObjectDataViewModelNode* child) - { - if (!m_container) - m_container = true; - m_children.Add(child); - } - void RemoveAllChildren() - { - if (GetChildCount() == 0) - return; - for (int id = int(GetChildCount()) - 1; id >= 0; --id) - { - if (m_children.Item(id)->GetChildCount() > 0) - m_children[id]->RemoveAllChildren(); - auto node = m_children[id]; - m_children.RemoveAt(id); - delete node; - } - } + ObjectDataViewModelNode* GetParent() + { + assert(m_parent == nullptr || m_parent->valid()); + return m_parent; + } + MyObjectTreeModelNodePtrArray& GetChildren() + { + return m_children; + } + ObjectDataViewModelNode* GetNthChild(unsigned int n) + { + return m_children.Item(n); + } + void Insert(ObjectDataViewModelNode* child, unsigned int n) + { + if (!m_container) + m_container = true; + m_children.Insert(child, n); + } + void Append(ObjectDataViewModelNode* child) + { + if (!m_container) + m_container = true; + m_children.Add(child); + } + void RemoveAllChildren() + { + if (GetChildCount() == 0) + return; + for (int id = int(GetChildCount()) - 1; id >= 0; --id) + { + if (m_children.Item(id)->GetChildCount() > 0) + m_children[id]->RemoveAllChildren(); + auto node = m_children[id]; + m_children.RemoveAt(id); + delete node; + } + } - size_t GetChildCount() const - { - return m_children.GetCount(); - } + size_t GetChildCount() const + { + return m_children.GetCount(); + } - bool SetValue(const wxVariant &variant, unsigned int col) - { - switch (col) - { - case 0:{ - DataViewBitmapText data; - data << variant; - m_bmp = data.GetBitmap(); - m_name = data.GetText(); - return true;} - case 1: - m_extruder = variant.GetString(); - return true; - case 2: - m_action_icon << variant; - return true; - default: - printf("MyObjectTreeModel::SetValue: wrong column"); - } - return false; - } + bool SetValue(const wxVariant &variant, unsigned int col); - void SetBitmap(const wxBitmap &icon) { m_bmp = icon; } + void SetBitmap(const wxBitmap &icon) { m_bmp = icon; } const wxBitmap& GetBitmap() const { return m_bmp; } const wxString& GetName() const { return m_name; } ItemType GetType() const { return m_type; } void SetIdx(const int& idx); int GetIdx() const { return m_idx; } + t_layer_height_range GetLayerRange() const { return m_layer_range; } + PrintIndicator IsPrintable() const { return m_printable; } - // use this function only for childrens - void AssignAllVal(ObjectDataViewModelNode& from_node) - { - // ! Don't overwrite other values because of equality of this values for all children -- - m_name = from_node.m_name; + // use this function only for childrens + void AssignAllVal(ObjectDataViewModelNode& from_node) + { + // ! Don't overwrite other values because of equality of this values for all children -- + m_name = from_node.m_name; m_bmp = from_node.m_bmp; m_idx = from_node.m_idx; m_extruder = from_node.m_extruder; m_type = from_node.m_type; - } + } - bool SwapChildrens(int frst_id, int scnd_id) { - if (GetChildCount() < 2 || - frst_id < 0 || frst_id >= GetChildCount() || - scnd_id < 0 || scnd_id >= GetChildCount()) - return false; + bool SwapChildrens(int frst_id, int scnd_id) { + if (GetChildCount() < 2 || + frst_id < 0 || (size_t)frst_id >= GetChildCount() || + scnd_id < 0 || (size_t)scnd_id >= GetChildCount()) + return false; - ObjectDataViewModelNode new_scnd = *GetNthChild(frst_id); - ObjectDataViewModelNode new_frst = *GetNthChild(scnd_id); + ObjectDataViewModelNode new_scnd = *GetNthChild(frst_id); + ObjectDataViewModelNode new_frst = *GetNthChild(scnd_id); new_scnd.m_idx = m_children.Item(scnd_id)->m_idx; new_frst.m_idx = m_children.Item(frst_id)->m_idx; - m_children.Item(frst_id)->AssignAllVal(new_frst); - m_children.Item(scnd_id)->AssignAllVal(new_scnd); - return true; - } + m_children.Item(frst_id)->AssignAllVal(new_frst); + m_children.Item(scnd_id)->AssignAllVal(new_scnd); + return true; + } - // Set action icons for node - void set_action_icon(); + // Set action icons for node + void set_action_icon(); + // Set printable icon for node + void set_printable_icon(PrintIndicator printable); void update_settings_digest_bitmaps(); - bool update_settings_digest(const std::vector& categories); + bool update_settings_digest(const std::vector& categories); int volume_type() const { return int(m_volume_type); } void msw_rescale(); + +#ifndef NDEBUG + bool valid(); +#endif /* NDEBUG */ + bool invalid() const { return m_idx < -1; } + private: friend class ObjectDataViewModel; }; @@ -367,7 +383,7 @@ wxDECLARE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent); class ObjectDataViewModel :public wxDataViewModel { - std::vector m_objects; + std::vector m_objects; std::vector m_volume_bmps; wxBitmap* m_warning_bmp; @@ -377,7 +393,7 @@ public: ObjectDataViewModel(); ~ObjectDataViewModel(); - wxDataViewItem Add( const wxString &name, + wxDataViewItem Add( const wxString &name, const int extruder, const bool has_errors = false); wxDataViewItem AddVolumeChild( const wxDataViewItem &parent_item, @@ -388,83 +404,109 @@ public: const bool create_frst_child = true); wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num); - wxDataViewItem Delete(const wxDataViewItem &item); - wxDataViewItem DeleteLastInstance(const wxDataViewItem &parent_item, size_t num); - void DeleteAll(); + wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, const std::vector& print_indicator); + wxDataViewItem AddLayersRoot(const wxDataViewItem &parent_item); + wxDataViewItem AddLayersChild( const wxDataViewItem &parent_item, + const t_layer_height_range& layer_range, + const int extruder = 0, + const int index = -1); + wxDataViewItem Delete(const wxDataViewItem &item); + wxDataViewItem DeleteLastInstance(const wxDataViewItem &parent_item, size_t num); + void DeleteAll(); void DeleteChildren(wxDataViewItem& parent); void DeleteVolumeChildren(wxDataViewItem& parent); void DeleteSettings(const wxDataViewItem& parent); - wxDataViewItem GetItemById(int obj_idx); - wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx); - wxDataViewItem GetItemByInstanceId(int obj_idx, int inst_idx); - int GetIdByItem(const wxDataViewItem& item) const; + wxDataViewItem GetItemById(int obj_idx); + wxDataViewItem GetItemById(const int obj_idx, const int sub_obj_idx, const ItemType parent_type); + wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx); + wxDataViewItem GetItemByInstanceId(int obj_idx, int inst_idx); + wxDataViewItem GetItemByLayerId(int obj_idx, int layer_idx); + wxDataViewItem GetItemByLayerRange(const int obj_idx, const t_layer_height_range& layer_range); + int GetItemIdByLayerRange(const int obj_idx, const t_layer_height_range& layer_range); + int GetIdByItem(const wxDataViewItem& item) const; int GetIdByItemAndType(const wxDataViewItem& item, const ItemType type) const; int GetObjectIdByItem(const wxDataViewItem& item) const; int GetVolumeIdByItem(const wxDataViewItem& item) const; int GetInstanceIdByItem(const wxDataViewItem& item) const; + int GetLayerIdByItem(const wxDataViewItem& item) const; void GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx); int GetRowByItem(const wxDataViewItem& item) const; bool IsEmpty() { return m_objects.empty(); } + bool InvalidItem(const wxDataViewItem& item); - // helper method for wxLog + // helper method for wxLog - wxString GetName(const wxDataViewItem &item) const; + wxString GetName(const wxDataViewItem &item) const; wxBitmap& GetBitmap(const wxDataViewItem &item) const; - // helper methods to change the model + // helper methods to change the model - virtual unsigned int GetColumnCount() const override { return 3;} - virtual wxString GetColumnType(unsigned int col) const override{ return wxT("string"); } + virtual unsigned int GetColumnCount() const override { return 3;} + virtual wxString GetColumnType(unsigned int col) const override{ return wxT("string"); } - virtual void GetValue( wxVariant &variant, - const wxDataViewItem &item, + virtual void GetValue( wxVariant &variant, + const wxDataViewItem &item, unsigned int col) const override; - virtual bool SetValue( const wxVariant &variant, - const wxDataViewItem &item, + virtual bool SetValue( const wxVariant &variant, + const wxDataViewItem &item, unsigned int col) override; - bool SetValue( const wxVariant &variant, - const int item_idx, + bool SetValue( const wxVariant &variant, + const int item_idx, unsigned int col); - // For parent move child from cur_volume_id place to new_volume_id + // For parent move child from cur_volume_id place to new_volume_id // Remaining items will moved up/down accordingly - wxDataViewItem ReorganizeChildren( const int cur_volume_id, + wxDataViewItem ReorganizeChildren( const int cur_volume_id, const int new_volume_id, const wxDataViewItem &parent); - virtual bool IsEnabled(const wxDataViewItem &item, unsigned int col) const override; + virtual bool IsEnabled(const wxDataViewItem &item, unsigned int col) const override; - virtual wxDataViewItem GetParent(const wxDataViewItem &item) const override; + virtual wxDataViewItem GetParent(const wxDataViewItem &item) const override; // get object item wxDataViewItem GetTopParent(const wxDataViewItem &item) const; - virtual bool IsContainer(const wxDataViewItem &item) const override; - virtual unsigned int GetChildren(const wxDataViewItem &parent, - wxDataViewItemArray &array) const override; + virtual bool IsContainer(const wxDataViewItem &item) const override; + virtual unsigned int GetChildren(const wxDataViewItem &parent, + wxDataViewItemArray &array) const override; void GetAllChildren(const wxDataViewItem &parent,wxDataViewItemArray &array) const; - // Is the container just a header or an item with all columns - // In our case it is an item with all columns - virtual bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; } + // Is the container just a header or an item with all columns + // In our case it is an item with all columns + virtual bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; } ItemType GetItemType(const wxDataViewItem &item) const ; - wxDataViewItem GetItemByType( const wxDataViewItem &parent_item, + wxDataViewItem GetItemByType( const wxDataViewItem &parent_item, ItemType type) const; wxDataViewItem GetSettingsItem(const wxDataViewItem &item) const; wxDataViewItem GetInstanceRootItem(const wxDataViewItem &item) const; + wxDataViewItem GetLayerRootItem(const wxDataViewItem &item) const; bool IsSettingsItem(const wxDataViewItem &item) const; - void UpdateSettingsDigest( const wxDataViewItem &item, + void UpdateSettingsDigest( const wxDataViewItem &item, const std::vector& categories); + bool IsPrintable(const wxDataViewItem &item) const; + void UpdateObjectPrintable(wxDataViewItem parent_item); + void UpdateInstancesPrintable(wxDataViewItem parent_item); + void SetVolumeBitmaps(const std::vector& volume_bmps) { m_volume_bmps = volume_bmps; } void SetWarningBitmap(wxBitmap* bitmap) { m_warning_bmp = bitmap; } void SetVolumeType(const wxDataViewItem &item, const Slic3r::ModelVolumeType type); + wxDataViewItem SetPrintableState( PrintIndicator printable, int obj_idx, + int subobj_idx = -1, + ItemType subobj_type = itInstance); + wxDataViewItem SetObjectPrintableState(PrintIndicator printable, wxDataViewItem obj_item); void SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; } // Rescale bitmaps for existing Items void Rescale(); - wxBitmap GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, + wxBitmap GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, const bool is_marked = false); void DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false); + t_layer_height_range GetLayerRangeByItem(const wxDataViewItem& item) const; + +private: + wxDataViewItem AddRoot(const wxDataViewItem& parent_item, const ItemType root_type); + wxDataViewItem AddInstanceRoot(const wxDataViewItem& parent_item); }; // ---------------------------------------------------------------------------- @@ -506,12 +548,12 @@ public: return false; #else return true; -#endif +#endif } - wxWindow* CreateEditorCtrl(wxWindow* parent, - wxRect labelRect, + wxWindow* CreateEditorCtrl(wxWindow* parent, + wxRect labelRect, const wxVariant& value) override; - bool GetValueFromEditorCtrl( wxWindow* ctrl, + bool GetValueFromEditorCtrl( wxWindow* ctrl, wxVariant& value) override; bool WasCanceled() const { return m_was_unusable_symbol; } @@ -528,88 +570,88 @@ private: class MyCustomRenderer : public wxDataViewCustomRenderer { public: - // This renderer can be either activatable or editable, for demonstration - // purposes. In real programs, you should select whether the user should be - // able to activate or edit the cell and it doesn't make sense to switch - // between the two -- but this is just an example, so it doesn't stop us. - explicit MyCustomRenderer(wxDataViewCellMode mode) - : wxDataViewCustomRenderer("string", mode, wxALIGN_CENTER) - { } + // This renderer can be either activatable or editable, for demonstration + // purposes. In real programs, you should select whether the user should be + // able to activate or edit the cell and it doesn't make sense to switch + // between the two -- but this is just an example, so it doesn't stop us. + explicit MyCustomRenderer(wxDataViewCellMode mode) + : wxDataViewCustomRenderer("string", mode, wxALIGN_CENTER) + { } - virtual bool Render(wxRect rect, wxDC *dc, int state) override/*wxOVERRIDE*/ - { - dc->SetBrush(*wxLIGHT_GREY_BRUSH); - dc->SetPen(*wxTRANSPARENT_PEN); + virtual bool Render(wxRect rect, wxDC *dc, int state) override/*wxOVERRIDE*/ + { + dc->SetBrush(*wxLIGHT_GREY_BRUSH); + dc->SetPen(*wxTRANSPARENT_PEN); - rect.Deflate(2); - dc->DrawRoundedRectangle(rect, 5); + rect.Deflate(2); + dc->DrawRoundedRectangle(rect, 5); - RenderText(m_value, - 0, // no offset - wxRect(dc->GetTextExtent(m_value)).CentreIn(rect), - dc, - state); - return true; - } + RenderText(m_value, + 0, // no offset + wxRect(dc->GetTextExtent(m_value)).CentreIn(rect), + dc, + state); + return true; + } - virtual bool ActivateCell(const wxRect& WXUNUSED(cell), - wxDataViewModel *WXUNUSED(model), - const wxDataViewItem &WXUNUSED(item), - unsigned int WXUNUSED(col), - const wxMouseEvent *mouseEvent) override/*wxOVERRIDE*/ - { - wxString position; - if (mouseEvent) - position = wxString::Format("via mouse at %d, %d", mouseEvent->m_x, mouseEvent->m_y); - else - position = "from keyboard"; + virtual bool ActivateCell(const wxRect& WXUNUSED(cell), + wxDataViewModel *WXUNUSED(model), + const wxDataViewItem &WXUNUSED(item), + unsigned int WXUNUSED(col), + const wxMouseEvent *mouseEvent) override/*wxOVERRIDE*/ + { + wxString position; + if (mouseEvent) + position = wxString::Format("via mouse at %d, %d", mouseEvent->m_x, mouseEvent->m_y); + else + position = "from keyboard"; // wxLogMessage("MyCustomRenderer ActivateCell() %s", position); - return false; - } + return false; + } - virtual wxSize GetSize() const override/*wxOVERRIDE*/ - { - return wxSize(60, 20); - } + virtual wxSize GetSize() const override/*wxOVERRIDE*/ + { + return wxSize(60, 20); + } - virtual bool SetValue(const wxVariant &value) override/*wxOVERRIDE*/ - { - m_value = value.GetString(); - return true; - } + virtual bool SetValue(const wxVariant &value) override/*wxOVERRIDE*/ + { + m_value = value.GetString(); + return true; + } - virtual bool GetValue(wxVariant &WXUNUSED(value)) const override/*wxOVERRIDE*/{ return true; } + virtual bool GetValue(wxVariant &WXUNUSED(value)) const override/*wxOVERRIDE*/{ return true; } - virtual bool HasEditorCtrl() const override/*wxOVERRIDE*/{ return true; } + virtual bool HasEditorCtrl() const override/*wxOVERRIDE*/{ return true; } - virtual wxWindow* - CreateEditorCtrl(wxWindow* parent, - wxRect labelRect, - const wxVariant& value) override/*wxOVERRIDE*/ - { - wxTextCtrl* text = new wxTextCtrl(parent, wxID_ANY, value, - labelRect.GetPosition(), - labelRect.GetSize(), - wxTE_PROCESS_ENTER); - text->SetInsertionPointEnd(); + virtual wxWindow* + CreateEditorCtrl(wxWindow* parent, + wxRect labelRect, + const wxVariant& value) override/*wxOVERRIDE*/ + { + wxTextCtrl* text = new wxTextCtrl(parent, wxID_ANY, value, + labelRect.GetPosition(), + labelRect.GetSize(), + wxTE_PROCESS_ENTER); + text->SetInsertionPointEnd(); - return text; - } + return text; + } - virtual bool - GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) override/*wxOVERRIDE*/ - { - wxTextCtrl* text = wxDynamicCast(ctrl, wxTextCtrl); - if (!text) - return false; + virtual bool + GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) override/*wxOVERRIDE*/ + { + wxTextCtrl* text = wxDynamicCast(ctrl, wxTextCtrl); + if (!text) + return false; - value = text->GetValue(); + value = text->GetValue(); - return true; - } + return true; + } private: - wxString m_value; + wxString m_value; }; @@ -621,7 +663,7 @@ class ScalableBitmap { public: ScalableBitmap() {}; - ScalableBitmap( wxWindow *parent, + ScalableBitmap( wxWindow *parent, const std::string& icon_name = "", const int px_cnt = 16, const bool is_horizontal = false); @@ -667,9 +709,9 @@ public: DoubleSlider( wxWindow *parent, wxWindowID id, - int lowerValue, - int higherValue, - int minValue, + int lowerValue, + int higherValue, + int minValue, int maxValue, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, @@ -712,8 +754,8 @@ public: EnableTickManipulation(false); } - bool is_horizontal() const { return m_style == wxSL_HORIZONTAL; } - bool is_one_layer() const { return m_is_one_layer; } + bool is_horizontal() const { return m_style == wxSL_HORIZONTAL; } + bool is_one_layer() const { return m_is_one_layer; } bool is_lower_at_min() const { return m_lower_value == m_min_value; } bool is_higher_at_max() const { return m_higher_value == m_max_value; } bool is_full_span() const { return this->is_lower_at_min() && this->is_higher_at_max(); } @@ -727,11 +769,12 @@ public: void OnWheel(wxMouseEvent& event); void OnKeyDown(wxKeyEvent &event); void OnKeyUp(wxKeyEvent &event); + void OnChar(wxKeyEvent &event); void OnRightDown(wxMouseEvent& event); void OnRightUp(wxMouseEvent& event); protected: - + void render(); void draw_focus_rect(); void draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end); @@ -741,6 +784,7 @@ protected: void draw_ticks(wxDC& dc); void draw_colored_band(wxDC& dc); void draw_one_layer_icon(wxDC& dc); + void draw_revert_icon(wxDC& dc); void draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection); void draw_info_line_with_icon(wxDC& dc, const wxPoint& pos, SelectedSlider selection); void draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const; @@ -782,6 +826,7 @@ private: ScalableBitmap m_bmp_one_layer_lock_off; ScalableBitmap m_bmp_one_layer_unlock_on; ScalableBitmap m_bmp_one_layer_unlock_off; + ScalableBitmap m_bmp_revert; SelectedSlider m_selection; bool m_is_left_down = false; bool m_is_right_down = false; @@ -795,9 +840,11 @@ private: wxRect m_rect_higher_thumb; wxRect m_rect_tick_action; wxRect m_rect_one_layer_icon; + wxRect m_rect_revert_icon; wxSize m_thumb_size; int m_tick_icon_dim; int m_lock_icon_dim; + int m_revert_icon_dim; long m_style; float m_label_koef = 1.0; @@ -834,24 +881,27 @@ public: ~LockButton() {} void OnButton(wxCommandEvent& event); - void OnEnterBtn(wxMouseEvent& event) { enter_button(true); event.Skip(); } - void OnLeaveBtn(wxMouseEvent& event) { enter_button(false); event.Skip(); } - bool IsLocked() const { return m_is_pushed; } + bool IsLocked() const { return m_is_pushed; } void SetLock(bool lock); + // create its own Enable/Disable functions to not really disabled button because of tooltip enabling + void enable() { m_disabled = false; } + void disable() { m_disabled = true; } + void msw_rescale(); protected: - void enter_button(const bool enter); + void update_button_bitmaps(); private: bool m_is_pushed = false; + bool m_disabled = false; - ScalableBitmap m_bmp_lock_on; - ScalableBitmap m_bmp_lock_off; - ScalableBitmap m_bmp_unlock_on; - ScalableBitmap m_bmp_unlock_off; + ScalableBitmap m_bmp_lock_closed; + ScalableBitmap m_bmp_lock_closed_f; + ScalableBitmap m_bmp_lock_open; + ScalableBitmap m_bmp_lock_open_f; }; @@ -882,12 +932,16 @@ public: ~ScalableButton() {} void SetBitmap_(const ScalableBitmap& bmp); + void SetBitmapDisabled_(const ScalableBitmap &bmp); void msw_rescale(); private: wxWindow* m_parent; std::string m_current_icon_name = ""; + std::string m_disabled_icon_name = ""; + int m_width {-1}; // should be multiplied to em_unit + int m_height{-1}; // should be multiplied to em_unit }; @@ -932,7 +986,7 @@ private: class ModeSizer : public wxFlexGridSizer { public: - ModeSizer( wxWindow *parent, int hgap = 10); + ModeSizer( wxWindow *parent, int hgap = 0); ~ModeSizer() {} void SetMode(const /*ConfigOptionMode*/int mode); diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp index 1daeaff269..d955d6a7e4 100644 --- a/src/slic3r/Utils/FixModelByWin10.cpp +++ b/src/slic3r/Utils/FixModelByWin10.cpp @@ -377,7 +377,7 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx) // PresetBundle bundle; on_progress(L("Loading repaired model"), 80); DynamicPrintConfig config; - bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), &config, &model); + bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), &config, &model, false); boost::filesystem::remove(path_dst); if (! loaded) throw std::runtime_error(L("Import of the repaired 3mf file failed")); @@ -389,10 +389,10 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx) throw std::runtime_error(L("Repaired 3MF file does not contain any volume")); if (model.objects.front()->volumes.size() > 1) throw std::runtime_error(L("Repaired 3MF file contains more than one volume")); - meshes_repaired.emplace_back(std::move(model.objects.front()->volumes.front()->mesh)); + meshes_repaired.emplace_back(std::move(model.objects.front()->volumes.front()->mesh())); } for (size_t i = 0; i < volumes.size(); ++ i) { - volumes[i]->mesh = std::move(meshes_repaired[i]); + volumes[i]->set_mesh(std::move(meshes_repaired[i])); volumes[i]->set_new_unique_id(); } model_object.invalidate_bounding_box(); diff --git a/src/slic3r/Utils/Http.cpp b/src/slic3r/Utils/Http.cpp index 79c4ecfa91..69301547c3 100644 --- a/src/slic3r/Utils/Http.cpp +++ b/src/slic3r/Utils/Http.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -165,7 +166,7 @@ size_t Http::priv::form_file_read_cb(char *buffer, size_t size, size_t nitems, v try { stream->read(buffer, size * nitems); - } catch (...) { + } catch (const std::exception &) { return CURL_READFUNC_ABORT; } diff --git a/src/slic3r/Utils/OctoPrint.cpp b/src/slic3r/Utils/OctoPrint.cpp index cafa69c554..09ca02071a 100644 --- a/src/slic3r/Utils/OctoPrint.cpp +++ b/src/slic3r/Utils/OctoPrint.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -69,7 +70,7 @@ bool OctoPrint::test(wxString &msg) const msg = wxString::Format(_(L("Mismatched type of print host: %s")), text ? *text : "OctoPrint"); } } - catch (...) { + catch (const std::exception &) { res = false; msg = "Could not parse server response"; } diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index f34cd8db19..d55063c7b5 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,10 @@ using Slic3r::GUI::Config::Snapshot; using Slic3r::GUI::Config::SnapshotDB; + +// FIXME: Incompat bundle resolution doesn't deal with inherited user presets + + namespace Slic3r { @@ -45,10 +50,25 @@ static const char *INDEX_FILENAME = "index.idx"; static const char *TMP_EXTENSION = ".download"; +void copy_file_fix(const fs::path &source, const fs::path &target) +{ + static const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read; // aka 644 + + BOOST_LOG_TRIVIAL(debug) << boost::format("PresetUpdater: Copying %1% -> %2%") % source % target; + + // Make sure the file has correct permission both before and after we copy over it + if (fs::exists(target)) { + fs::permissions(target, perms); + } + fs::copy_file(source, target, fs::copy_option::overwrite_if_exists); + fs::permissions(target, perms); +} + struct Update { fs::path source; fs::path target; + Version version; std::string vendor; std::string changelog_url; @@ -61,7 +81,13 @@ struct Update , changelog_url(std::move(changelog_url)) {} - friend std::ostream& operator<<(std::ostream& os , const Update &self) { + void install() const + { + copy_file_fix(source, target); + } + + friend std::ostream& operator<<(std::ostream& os, const Update &self) + { os << "Update(" << self.source.string() << " -> " << self.target.string() << ')'; return os; } @@ -79,6 +105,17 @@ struct Incompat , vendor(std::move(vendor)) {} + void remove() { + // Remove the bundle file + fs::remove(bundle); + + // Look for an installed index and remove it too if any + const fs::path installed_idx = bundle.replace_extension("idx"); + if (fs::exists(installed_idx)) { + fs::remove(installed_idx); + } + } + friend std::ostream& operator<<(std::ostream& os , const Incompat &self) { os << "Incompat(" << self.bundle.string() << ')'; return os; @@ -91,31 +128,17 @@ struct Updates std::vector updates; }; -static Semver get_slic3r_version() -{ - auto res = Semver::parse(SLIC3R_VERSION); - - if (! res) { - const char *error = "Could not parse Slic3r version string: " SLIC3R_VERSION; - BOOST_LOG_TRIVIAL(error) << error; - throw std::runtime_error(error); - } - - return *res; -} wxDEFINE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent); struct PresetUpdater::priv { - const Semver ver_slic3r; std::vector index_db; bool enabled_version_check; bool enabled_config_update; std::string version_check_url; - bool had_config_update; fs::path cache_path; fs::path rsrc_path; @@ -135,14 +158,10 @@ struct PresetUpdater::priv void check_install_indices() const; Updates get_config_updates() const; void perform_updates(Updates &&updates, bool snapshot = true) const; - - static void copy_file(const fs::path &from, const fs::path &to); }; PresetUpdater::priv::priv() - : ver_slic3r(get_slic3r_version()) - , had_config_update(false) - , cache_path(fs::path(Slic3r::data_dir()) / "cache") + : cache_path(fs::path(Slic3r::data_dir()) / "cache") , rsrc_path(fs::path(resources_dir()) / "profiles") , vendor_path(fs::path(Slic3r::data_dir()) / "vendor") , cancel(false) @@ -273,7 +292,7 @@ void PresetUpdater::priv::sync_config(const std::set vendors) try { new_index.load(idx_path_temp); } catch (const std::exception & /* err */) { - BOOST_LOG_TRIVIAL(error) << boost::format("Failed loading a downloaded index %1% for vendor %2%: invalid index?") % idx_path_temp % vendor.name; + BOOST_LOG_TRIVIAL(error) << boost::format("Could not load downloaded index %1% for vendor %2%: invalid index?") % idx_path_temp % vendor.name; continue; } if (new_index.version() < index.version()) { @@ -323,7 +342,7 @@ void PresetUpdater::priv::check_install_indices() const if (! fs::exists(path_in_cache)) { BOOST_LOG_TRIVIAL(info) << "Install index from resources: " << path.filename(); - copy_file(path, path_in_cache); + copy_file_fix(path, path_in_cache); } else { Index idx_rsrc, idx_cache; idx_rsrc.load(path); @@ -331,7 +350,7 @@ void PresetUpdater::priv::check_install_indices() const if (idx_cache.version() < idx_rsrc.version()) { BOOST_LOG_TRIVIAL(info) << "Update index from resources: " << path.filename(); - copy_file(path, path_in_cache); + copy_file_fix(path, path_in_cache); } } } @@ -346,6 +365,7 @@ Updates PresetUpdater::priv::get_config_updates() const for (const auto idx : index_db) { auto bundle_path = vendor_path / (idx.vendor() + ".ini"); + auto bundle_path_idx = vendor_path / idx.path().filename(); if (! fs::exists(bundle_path)) { BOOST_LOG_TRIVIAL(info) << "Bundle not present for index, skipping: " << idx.vendor(); @@ -360,15 +380,12 @@ Updates PresetUpdater::priv::get_config_updates() const const auto recommended = idx.recommended(); if (recommended == idx.end()) { BOOST_LOG_TRIVIAL(error) << boost::format("No recommended version for vendor: %1%, invalid index?") % idx.vendor(); + // XXX: what should be done here? + continue; } const auto ver_current = idx.find(vp.config_version); const bool ver_current_found = ver_current != idx.end(); - if (! ver_current_found) { - auto message = (boost::format("Preset bundle `%1%` version not found in index: %2%") % idx.vendor() % vp.config_version.to_string()).str(); - BOOST_LOG_TRIVIAL(error) << message; - GUI::show_error(nullptr, GUI::from_u8(message)); - } BOOST_LOG_TRIVIAL(debug) << boost::format("Vendor: %1%, version installed: %2%%3%, version cached: %4%") % vp.name @@ -376,12 +393,38 @@ Updates PresetUpdater::priv::get_config_updates() const % (ver_current_found ? "" : " (not found in index!)") % recommended->config_version.to_string(); + if (! ver_current_found) { + auto message = (boost::format("Preset bundle `%1%` version not found in index: %2%") % idx.vendor() % vp.config_version.to_string()).str(); + BOOST_LOG_TRIVIAL(error) << message; + GUI::show_error(nullptr, GUI::from_u8(message)); + continue; + } + if (ver_current_found && !ver_current->is_current_slic3r_supported()) { BOOST_LOG_TRIVIAL(warning) << "Current Slic3r incompatible with installed bundle: " << bundle_path.string(); updates.incompats.emplace_back(std::move(bundle_path), *ver_current, vp.name); } else if (recommended->config_version > vp.config_version) { // Config bundle update situation + // Load 'installed' idx, if any. + // 'Installed' indices are kept alongside the bundle in the `vendor` subdir + // for bookkeeping to remember a cancelled update and not offer it again. + if (fs::exists(bundle_path_idx)) { + Index existing_idx; + try { + existing_idx.load(bundle_path_idx); + + const auto existing_recommended = existing_idx.recommended(); + if (existing_recommended != existing_idx.end() && recommended->config_version == existing_recommended->config_version) { + // The user has already seen (and presumably rejected) this update + BOOST_LOG_TRIVIAL(info) << boost::format("Downloaded index for `%1%` is the same as installed one, not offering an update.") % idx.vendor(); + continue; + } + } catch (const std::exception &err) { + BOOST_LOG_TRIVIAL(error) << boost::format("Could not load installed index at `%1%`: %2%") % bundle_path_idx % err.what(); + } + } + // Check if the update is already present in a snapshot const auto recommended_snap = SnapshotDB::singleton().snapshot_with_vendor_preset(vp.name, recommended->config_version); if (recommended_snap != SnapshotDB::singleton().end()) { @@ -417,10 +460,16 @@ Updates PresetUpdater::priv::get_config_updates() const found = true; } } - if (! found) + + if (found) { + // 'Install' the index in the vendor directory. This is used to memoize + // offered updates and to not offer the same update again if it was cancelled by the user. + copy_file_fix(idx.path(), bundle_path_idx); + } else { BOOST_LOG_TRIVIAL(warning) << boost::format("Index for vendor %1% indicates update (%2%) but the new bundle was found neither in cache nor resources") % idx.vendor() % recommended->config_version.to_string(); + } } } @@ -437,12 +486,11 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons BOOST_LOG_TRIVIAL(info) << boost::format("Deleting %1% incompatible bundles") % updates.incompats.size(); - for (const auto &incompat : updates.incompats) { + for (auto &incompat : updates.incompats) { BOOST_LOG_TRIVIAL(info) << '\t' << incompat; - fs::remove(incompat.bundle); + incompat.remove(); } - } - else if (updates.updates.size() > 0) { + } else if (updates.updates.size() > 0) { if (snapshot) { BOOST_LOG_TRIVIAL(info) << "Taking a snapshot..."; SnapshotDB::singleton().take_snapshot(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_UPGRADE); @@ -453,10 +501,10 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons for (const auto &update : updates.updates) { BOOST_LOG_TRIVIAL(info) << '\t' << update; - copy_file(update.source, update.target); + update.install(); PresetBundle bundle; - bundle.load_configbundle(update.target.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM); + bundle.load_configbundle(update.source.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM); BOOST_LOG_TRIVIAL(info) << boost::format("Deleting %1% conflicting presets") % (bundle.prints.size() + bundle.filaments.size() + bundle.printers.size()); @@ -491,19 +539,6 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons } } -void PresetUpdater::priv::copy_file(const fs::path &source, const fs::path &target) -{ - static const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read; // aka 644 - - // Make sure the file has correct permission both before and after we copy over it - if (fs::exists(target)) { - fs::permissions(target, perms); - } - fs::copy_file(source, target, fs::copy_option::overwrite_if_exists); - fs::permissions(target, perms); -} - - PresetUpdater::PresetUpdater() : p(new priv()) {} @@ -542,11 +577,6 @@ void PresetUpdater::slic3r_update_notify() { if (! p->enabled_version_check) { return; } - if (p->had_config_update) { - BOOST_LOG_TRIVIAL(info) << "New Slic3r version available, but there was a configuration update, notification won't be displayed"; - return; - } - auto* app_config = GUI::wxGetApp().app_config; const auto ver_online_str = app_config->get("version_online"); const auto ver_online = Semver::parse(ver_online_str); @@ -554,8 +584,8 @@ void PresetUpdater::slic3r_update_notify() if (ver_online) { // Only display the notification if the version available online is newer AND if we haven't seen it before - if (*ver_online > p->ver_slic3r && (! ver_online_seen || *ver_online_seen < *ver_online)) { - GUI::MsgUpdateSlic3r notification(p->ver_slic3r, *ver_online); + if (*ver_online > Slic3r::SEMVER && (! ver_online_seen || *ver_online_seen < *ver_online)) { + GUI::MsgUpdateSlic3r notification(Slic3r::SEMVER, *ver_online); notification.ShowModal(); if (notification.disable_version_check()) { app_config->set("version_check", "0"); @@ -594,17 +624,21 @@ PresetUpdater::UpdateResult PresetUpdater::config_update() const incompats_map.emplace(std::make_pair(incompat.vendor, std::move(restrictions))); } - p->had_config_update = true; // This needs to be done before a dialog is shown because of OnIdle() + CallAfter() in Perl - GUI::MsgDataIncompatible dlg(std::move(incompats_map)); const auto res = dlg.ShowModal(); if (res == wxID_REPLACE) { BOOST_LOG_TRIVIAL(info) << "User wants to re-configure..."; + + // This effectively removes the incompatible bundles: + // (snapshot is taken beforehand) p->perform_updates(std::move(updates)); + GUI::ConfigWizard wizard(nullptr, GUI::ConfigWizard::RR_DATA_INCOMPAT); + if (! wizard.run(GUI::wxGetApp().preset_bundle, this)) { return R_INCOMPAT_EXIT; } + GUI::wxGetApp().load_current_presets(); return R_INCOMPAT_CONFIGURED; } else { @@ -620,8 +654,6 @@ PresetUpdater::UpdateResult PresetUpdater::config_update() const updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url)); } - p->had_config_update = true; // Ditto, see above - GUI::MsgUpdateConfig dlg(updates_msg); const auto res = dlg.ShowModal(); @@ -631,7 +663,7 @@ PresetUpdater::UpdateResult PresetUpdater::config_update() const // Reload global configuration auto *app_config = GUI::wxGetApp().app_config; - GUI::wxGetApp().preset_bundle->load_presets(*app_config); + GUI::wxGetApp().preset_bundle->load_presets(*app_config); GUI::wxGetApp().load_current_presets(); return R_UPDATE_INSTALLED; } else { diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp index e9e39e6957..ab52b23443 100644 --- a/src/slic3r/Utils/PrintHost.cpp +++ b/src/slic3r/Utils/PrintHost.cpp @@ -170,8 +170,6 @@ void PrintHostJobQueue::priv::bg_thread_main() } } catch (const std::exception &e) { emit_error(e.what()); - } catch (...) { - emit_error("Unknown exception"); } // Cleanup leftover files, if any diff --git a/src/slic3r/Utils/Serial.cpp b/src/slic3r/Utils/Serial.cpp index 601719b50e..5944646926 100644 --- a/src/slic3r/Utils/Serial.cpp +++ b/src/slic3r/Utils/Serial.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -71,13 +72,10 @@ void parse_hardware_id(const std::string &hardware_id, SerialPortInfo &spi) std::regex pattern("USB\\\\.*VID_([[:xdigit:]]+)&PID_([[:xdigit:]]+).*"); std::smatch matches; if (std::regex_match(hardware_id, matches, pattern)) { - try { - vid = std::stoul(matches[1].str(), 0, 16); - pid = std::stoul(matches[2].str(), 0, 16); - spi.id_vendor = vid; - spi.id_product = pid; - } - catch (...) {} + vid = std::stoul(matches[1].str(), 0, 16); + pid = std::stoul(matches[2].str(), 0, 16); + spi.id_vendor = vid; + spi.id_product = pid; } } #endif @@ -353,6 +351,8 @@ void Serial::set_baud_rate(unsigned baud_rate) } } + +/* void Serial::set_DTR(bool on) { auto handle = native_handle(); @@ -384,7 +384,13 @@ void Serial::reset_line_num() bool Serial::read_line(unsigned timeout, std::string &line, error_code &ec) { - auto &io_service = get_io_service(); + auto& io_service = +#if BOOST_VERSION >= 107000 + //FIXME this is most certainly wrong! + (boost::asio::io_context&)this->get_executor().context(); + #else + this->get_io_service(); +#endif asio::deadline_timer timer(io_service); char c = 0; bool fail = false; @@ -489,6 +495,7 @@ std::string Serial::printer_format_line(const std::string &line, unsigned line_n return (boost::format("N%1% %2%*%3%\n") % line_num_str % line % checksum).str(); } +*/ } // namespace Utils diff --git a/src/slic3r/Utils/Serial.hpp b/src/slic3r/Utils/Serial.hpp index e4a28de09d..8bad75b315 100644 --- a/src/slic3r/Utils/Serial.hpp +++ b/src/slic3r/Utils/Serial.hpp @@ -17,6 +17,9 @@ struct SerialPortInfo { std::string friendly_name; bool is_printer = false; + SerialPortInfo() {} + SerialPortInfo(std::string port) : port(port), friendly_name(std::move(port)) {} + bool id_match(unsigned id_vendor, unsigned id_product) const { return id_vendor == this->id_vendor && id_product == this->id_product; } }; @@ -43,6 +46,17 @@ public: ~Serial(); void set_baud_rate(unsigned baud_rate); + + // The Serial implementation is currently in disarray and therefore commented out. + // The boost implementation seems to have several problems, such as lack of support + // for custom baud rates, few weird implementation bugs and a history of API breakages. + // It's questionable whether it solves more problems than causes. Probably not. + // TODO: Custom implementation not based on asio. + // + // As of now, this class is only kept for the purpose of rebooting AVR109, + // see FirmwareDialog::priv::avr109_reboot() + +/* void set_DTR(bool on); // Resets the line number both internally as well as with the firmware using M110 @@ -65,7 +79,7 @@ public: // Same as above, but with internally-managed line number size_t printer_write_line(const std::string &line); - + // Toggles DTR to reset the printer void printer_reset(); @@ -73,6 +87,7 @@ public: static std::string printer_format_line(const std::string &line, unsigned line_num); private: unsigned m_line_num = 0; +*/ }; diff --git a/src/slic3r/Utils/Time.cpp b/src/slic3r/Utils/Time.cpp index f38c4b4074..db1aa31f6e 100644 --- a/src/slic3r/Utils/Time.cpp +++ b/src/slic3r/Utils/Time.cpp @@ -21,7 +21,11 @@ time_t parse_time_ISO8601Z(const std::string &sdate) tms.tm_hour = h; // 0-23 tms.tm_min = m; // 0-59 tms.tm_sec = s; // 0-61 (0-60 in C++11) - return mktime(&tms); +#ifdef WIN32 + return _mkgmtime(&tms); +#else /* WIN32 */ + return timegm(&tms); +#endif /* WIN32 */ } std::string format_time_ISO8601Z(time_t time) @@ -47,6 +51,7 @@ std::string format_local_date_time(time_t time) { struct tm tms; #ifdef WIN32 + // Converts a time_t time value to a tm structure, and corrects for the local time zone. localtime_s(&tms, &time); #else localtime_r(&time, &tms); @@ -60,6 +65,7 @@ time_t get_current_time_utc() { #ifdef WIN32 SYSTEMTIME st; + // Retrieves the current system date and time. The system time is expressed in Coordinated Universal Time (UTC). ::GetSystemTime(&st); std::tm tm; tm.tm_sec = st.wSecond; @@ -69,10 +75,10 @@ time_t get_current_time_utc() tm.tm_mon = st.wMonth - 1; tm.tm_year = st.wYear - 1900; tm.tm_isdst = -1; - return mktime(&tm); + return _mkgmtime(&tm); #else - const time_t current_local = time(nullptr); - return mktime(gmtime(¤t_local)); + // time() returns the time as the number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC). + return time(nullptr); #endif } diff --git a/src/slic3r/Utils/Time.hpp b/src/slic3r/Utils/Time.hpp index 7b670bd3ee..6a1aefa188 100644 --- a/src/slic3r/Utils/Time.hpp +++ b/src/slic3r/Utils/Time.hpp @@ -2,7 +2,7 @@ #define slic3r_Utils_Time_hpp_ #include -#include +#include namespace Slic3r { namespace Utils { diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp new file mode 100644 index 0000000000..b3b2628e29 --- /dev/null +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -0,0 +1,1099 @@ +#include "UndoRedo.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#define CEREAL_FUTURE_EXPERIMENTAL +#include + +#include +#include +#include + +#include + +#ifndef NDEBUG +// #define SLIC3R_UNDOREDO_DEBUG +#endif /* NDEBUG */ +#if 0 + // Stop at a fraction of the normal Undo / Redo stack size. + #define UNDO_REDO_DEBUG_LOW_MEM_FACTOR 10000 +#else + #define UNDO_REDO_DEBUG_LOW_MEM_FACTOR 1 +#endif + +namespace Slic3r { +namespace UndoRedo { + +SnapshotData::SnapshotData() : printer_technology(ptUnknown), flags(0), layer_range_idx(-1) +{ +} + +static std::string topmost_snapshot_name = "@@@ Topmost @@@"; + +bool Snapshot::is_topmost() const +{ + return this->name == topmost_snapshot_name; +} + +// Time interval, start is closed, end is open. +struct Interval +{ +public: + Interval(size_t begin, size_t end) : m_begin(begin), m_end(end) {} + + size_t begin() const { return m_begin; } + size_t end() const { return m_end; } + + bool is_valid() const { return m_begin >= 0 && m_begin < m_end; } + // This interval comes strictly before the rhs interval. + bool strictly_before(const Interval &rhs) const { return this->is_valid() && rhs.is_valid() && m_end <= rhs.m_begin; } + // This interval comes strictly after the rhs interval. + bool strictly_after(const Interval &rhs) const { return this->is_valid() && rhs.is_valid() && rhs.m_end <= m_begin; } + + bool operator<(const Interval &rhs) const { return (m_begin < rhs.m_begin) || (m_begin == rhs.m_begin && m_end < rhs.m_end); } + bool operator==(const Interval &rhs) const { return m_begin == rhs.m_begin && m_end == rhs.m_end; } + + void trim_begin(size_t new_begin) { m_begin = std::max(m_begin, new_begin); } + void trim_end(size_t new_end) { m_end = std::min(m_end, new_end); } + void extend_end(size_t new_end) { assert(new_end >= m_end); m_end = new_end; } + + size_t memsize() const { return sizeof(this); } + +private: + size_t m_begin; + size_t m_end; +}; + +// History of a single object tracked by the Undo / Redo stack. The object may be mutable or immutable. +class ObjectHistoryBase +{ +public: + virtual ~ObjectHistoryBase() {} + + // Is the object captured by this history mutable or immutable? + virtual bool is_mutable() const = 0; + virtual bool is_immutable() const = 0; + // The object is optional, it may be released if the Undo / Redo stack memory grows over the limits. + virtual bool is_optional() const { return false; } + // If it is an immutable object, return its pointer. There is a map assigning a temporary ObjectID to the immutable object pointer. + virtual const void* immutable_object_ptr() const { return nullptr; } + + // If the history is empty, the ObjectHistory object could be released. + virtual bool empty() = 0; + + // Release all data before the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released. + // Return the amount of memory released. + virtual size_t release_before_timestamp(size_t timestamp) = 0; + // Release all data after the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released. + // Return the amount of memory released. + virtual size_t release_after_timestamp(size_t timestamp) = 0; + // Release all optional data of this history. + virtual size_t release_optional() = 0; + // Restore optional data possibly released by release_optional. + virtual void restore_optional() = 0; + + // Estimated size in memory, to be used to drop least recently used snapshots. + virtual size_t memsize() const = 0; + +#ifdef SLIC3R_UNDOREDO_DEBUG + // Human readable debug information. + virtual std::string format() = 0; +#endif /* SLIC3R_UNDOREDO_DEBUG */ + +#ifndef NDEBUG + virtual bool valid() = 0; +#endif /* NDEBUG */ +}; + +template class ObjectHistory : public ObjectHistoryBase +{ +public: + ~ObjectHistory() override {} + + // If the history is empty, the ObjectHistory object could be released. + bool empty() override { return m_history.empty(); } + + // Release all data before the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released. + size_t release_before_timestamp(size_t timestamp) override { + size_t mem_released = 0; + if (! m_history.empty()) { + assert(this->valid()); + // it points to an interval which either starts with timestamp, or follows the timestamp. + auto it = std::lower_bound(m_history.begin(), m_history.end(), T(timestamp, timestamp)); + // Find the first iterator with begin() < timestamp. + if (it == m_history.end()) + -- it; + while (it != m_history.begin() && it->begin() >= timestamp) + -- it; + if (it->begin() < timestamp && it->end() > timestamp) { + it->trim_begin(timestamp); + if (it != m_history.begin()) + -- it; + } + if (it->end() <= timestamp) { + auto it_end = ++ it; + for (it = m_history.begin(); it != it_end; ++ it) + mem_released += it->memsize(); + m_history.erase(m_history.begin(), it_end); + } + assert(this->valid()); + } + return mem_released; + } + + // Release all data after the given timestamp. The shared pointer is NOT released. + size_t release_after_timestamp(size_t timestamp) override { + size_t mem_released = 0; + if (! m_history.empty()) { + assert(this->valid()); + // it points to an interval which either starts with timestamp, or follows the timestamp. + auto it = std::lower_bound(m_history.begin(), m_history.end(), T(timestamp, timestamp)); + if (it != m_history.begin()) { + auto it_prev = it; + -- it_prev; + assert(it_prev->begin() < timestamp); + // Trim the last interval with timestamp. + it_prev->trim_end(timestamp); + } + for (auto it2 = it; it2 != m_history.end(); ++ it2) + mem_released += it2->memsize(); + m_history.erase(it, m_history.end()); + assert(this->valid()); + } + return mem_released; + } + +protected: + std::vector m_history; +}; + +// Big objects (mainly the triangle meshes) are tracked by Slicer using the shared pointers +// and they are immutable. +// The Undo / Redo stack therefore may keep a shared pointer to these immutable objects +// and as long as the ref counter of these objects is higher than 1 (1 reference is held +// by the Undo / Redo stack), there is no cost associated to holding the object +// at the Undo / Redo stack. Once the reference counter drops to 1 (only the Undo / Redo +// stack holds the reference), the shared pointer may get serialized (and possibly compressed) +// and the shared pointer may be released. +// The history of a single immutable object may not be continuous, as an immutable object may +// be removed from the scene while being kept at the Copy / Paste stack. +template +class ImmutableObjectHistory : public ObjectHistory +{ +public: + ImmutableObjectHistory(std::shared_ptr shared_object, bool optional) : m_shared_object(shared_object), m_optional(optional) {} + ~ImmutableObjectHistory() override {} + + bool is_mutable() const override { return false; } + bool is_immutable() const override { return true; } + bool is_optional() const override { return m_optional; } + // If it is an immutable object, return its pointer. There is a map assigning a temporary ObjectID to the immutable object pointer. + const void* immutable_object_ptr() const { return (const void*)m_shared_object.get(); } + + // Estimated size in memory, to be used to drop least recently used snapshots. + size_t memsize() const override { + size_t memsize = sizeof(*this); + if (this->is_serialized()) + memsize += m_serialized.size(); + else if (m_shared_object.use_count() == 1) + // Only count the shared object's memsize into the total Undo / Redo stack memsize if it is referenced from the Undo / Redo stack only. + memsize += m_shared_object->memsize(); + memsize += m_history.size() * sizeof(Interval); + return memsize; + } + + void save(size_t active_snapshot_time, size_t current_time) { + assert(m_history.empty() || m_history.back().end() <= active_snapshot_time || + // The snapshot of an immutable object may have already been taken from another mutable object. + (m_history.back().begin() <= active_snapshot_time && m_history.back().end() == current_time + 1)); + if (m_history.empty() || m_history.back().end() < active_snapshot_time) + m_history.emplace_back(active_snapshot_time, current_time + 1); + else + m_history.back().extend_end(current_time + 1); + } + + bool has_snapshot(size_t timestamp) { + if (m_history.empty()) + return false; + auto it = std::lower_bound(m_history.begin(), m_history.end(), Interval(timestamp, timestamp)); + if (it == m_history.end() || it->begin() > timestamp) { + if (it == m_history.begin()) + return false; + -- it; + } + return timestamp >= it->begin() && timestamp < it->end(); + } + + // Release all optional data of this history. + size_t release_optional() override { + size_t mem_released = 0; + if (m_optional) { + bool released = false; + if (this->is_serialized()) { + mem_released += m_serialized.size(); + m_serialized.clear(); + released = true; + } else if (m_shared_object.use_count() == 1) { + mem_released += m_shared_object->memsize(); + m_shared_object.reset(); + released = true; + } + if (released) { + mem_released += m_history.size() * sizeof(Interval); + m_history.clear(); + } + } else if (m_shared_object.use_count() == 1) { + // The object is in memory, but it is not shared with the scene. Let the object decide whether there is any optional data to release. + const_cast(m_shared_object.get())->release_optional(); + } + return mem_released; + } + + // Restore optional data possibly released by this->release_optional(). + void restore_optional() override { + if (m_shared_object.use_count() == 1) + const_cast(m_shared_object.get())->restore_optional(); + } + + bool is_serialized() const { return m_shared_object.get() == nullptr; } + const std::string& serialized_data() const { return m_serialized; } + std::shared_ptr& shared_ptr(StackImpl &stack); + +#ifdef SLIC3R_UNDOREDO_DEBUG + std::string format() override { + std::string out = typeid(T).name(); + out += this->is_serialized() ? + std::string(" len:") + std::to_string(m_serialized.size()) : + std::string(" shared_ptr:") + ptr_to_string(m_shared_object.get()); + for (const Interval &interval : m_history) + out += std::string(", <") + std::to_string(interval.begin()) + "," + std::to_string(interval.end()) + ")"; + return out; + } +#endif /* SLIC3R_UNDOREDO_DEBUG */ + +#ifndef NDEBUG + bool valid() override; +#endif /* NDEBUG */ + +private: + // Either the source object is held by a shared pointer and the m_serialized field is empty, + // or the shared pointer is null and the object is being serialized into m_serialized. + std::shared_ptr m_shared_object; + // If this object is optional, then it may be deleted from the Undo / Redo stack and recalculated from other data (for example mesh convex hull). + bool m_optional; + std::string m_serialized; +}; + +struct MutableHistoryInterval +{ +private: + struct Data + { + // Reference counter of this data chunk. We may have used shared_ptr, but the shared_ptr is thread safe + // with the associated cost of CPU cache invalidation on refcount change. + size_t refcnt; + size_t size; + char data[1]; + + bool matches(const std::string& rhs) { return this->size == rhs.size() && memcmp(this->data, rhs.data(), this->size) == 0; } + }; + + Interval m_interval; + Data *m_data; + +public: + MutableHistoryInterval(const Interval &interval, const std::string &input_data) : m_interval(interval), m_data(nullptr) { + m_data = (Data*)new char[offsetof(Data, data) + input_data.size()]; + m_data->refcnt = 1; + m_data->size = input_data.size(); + memcpy(m_data->data, input_data.data(), input_data.size()); + } + + MutableHistoryInterval(const Interval &interval, MutableHistoryInterval &other) : m_interval(interval), m_data(other.m_data) { + ++ m_data->refcnt; + } + + // as a key for std::lower_bound + MutableHistoryInterval(const size_t begin, const size_t end) : m_interval(begin, end), m_data(nullptr) {} + + MutableHistoryInterval(MutableHistoryInterval&& rhs) : m_interval(rhs.m_interval), m_data(rhs.m_data) { rhs.m_data = nullptr; } + MutableHistoryInterval& operator=(MutableHistoryInterval&& rhs) { m_interval = rhs.m_interval; m_data = rhs.m_data; rhs.m_data = nullptr; return *this; } + + ~MutableHistoryInterval() { + if (m_data != nullptr && -- m_data->refcnt == 0) + delete[] (char*)m_data; + } + + const Interval& interval() const { return m_interval; } + size_t begin() const { return m_interval.begin(); } + size_t end() const { return m_interval.end(); } + void trim_begin(size_t timestamp) { m_interval.trim_begin(timestamp); } + void trim_end (size_t timestamp) { m_interval.trim_end(timestamp); } + void extend_end(size_t timestamp) { m_interval.extend_end(timestamp); } + + bool operator<(const MutableHistoryInterval& rhs) const { return m_interval < rhs.m_interval; } + bool operator==(const MutableHistoryInterval& rhs) const { return m_interval == rhs.m_interval; } + + const char* data() const { return m_data->data; } + size_t size() const { return m_data->size; } + size_t refcnt() const { return m_data->refcnt; } + bool matches(const std::string& data) { return m_data->matches(data); } + size_t memsize() const { + return m_data->refcnt == 1 ? + // Count just the size of the snapshot data. + m_data->size : + // Count the size of the snapshot data divided by the number of references, rounded up. + (m_data->size + m_data->refcnt - 1) / m_data->refcnt; + } + +private: + MutableHistoryInterval(const MutableHistoryInterval &rhs); + MutableHistoryInterval& operator=(const MutableHistoryInterval &rhs); +}; + +static inline std::string ptr_to_string(const void* ptr) +{ + char buf[64]; + sprintf(buf, "%p", ptr); + return buf; +} + +// Smaller objects (Model, ModelObject, ModelInstance, ModelVolume, DynamicPrintConfig) +// are mutable and there is not tracking of the changes, therefore a snapshot needs to be +// taken every time and compared to the previous data at the Undo / Redo stack. +// The serialized data is stored if it is different from the last value on the stack, otherwise +// the serialized data is discarded. +// The history of a single mutable object may not be continuous, as an mutable object may +// be removed from the scene while being kept at the Copy / Paste stack, therefore an object snapshot +// with the same serialized object data may be shared by multiple history intervals. +template +class MutableObjectHistory : public ObjectHistory +{ +public: + ~MutableObjectHistory() override {} + + bool is_mutable() const override { return true; } + bool is_immutable() const override { return false; } + + // Estimated size in memory, to be used to drop least recently used snapshots. + size_t memsize() const override { + size_t memsize = sizeof(*this); + memsize += m_history.size() * sizeof(MutableHistoryInterval); + for (const MutableHistoryInterval &interval : m_history) + memsize += interval.memsize(); + return memsize; + } + + void save(size_t active_snapshot_time, size_t current_time, const std::string &data) { + assert(m_history.empty() || m_history.back().end() <= active_snapshot_time); + if (m_history.empty() || m_history.back().end() < active_snapshot_time) { + if (! m_history.empty() && m_history.back().matches(data)) + // Share the previous data by reference counting. + m_history.emplace_back(Interval(current_time, current_time + 1), m_history.back()); + else + // Allocate new data. + m_history.emplace_back(Interval(current_time, current_time + 1), data); + } else { + assert(! m_history.empty()); + assert(m_history.back().end() == active_snapshot_time); + if (m_history.back().matches(data)) + // Just extend the last interval using the old data. + m_history.back().extend_end(current_time + 1); + else + // Allocate new data time continuous with the previous data. + m_history.emplace_back(Interval(active_snapshot_time, current_time + 1), data); + } + } + + std::string load(size_t timestamp) const { + assert(! m_history.empty()); + auto it = std::lower_bound(m_history.begin(), m_history.end(), MutableHistoryInterval(timestamp, timestamp)); + if (it == m_history.end() || it->begin() > timestamp) { + assert(it != m_history.begin()); + -- it; + } + assert(timestamp >= it->begin() && timestamp < it->end()); + return std::string(it->data(), it->data() + it->size()); + } + + // Currently all mutable snapshots are mandatory. + size_t release_optional() override { return 0; } + // Currently there is no way to release optional data from the mutable objects. + void restore_optional() override {} + +#ifdef SLIC3R_UNDOREDO_DEBUG + std::string format() override { + std::string out = typeid(T).name(); + for (const MutableHistoryInterval &interval : m_history) + out += std::string(", ptr:") + ptr_to_string(interval.data()) + " len:" + std::to_string(interval.size()) + " <" + std::to_string(interval.begin()) + "," + std::to_string(interval.end()) + ")"; + return out; + } +#endif /* SLIC3R_UNDOREDO_DEBUG */ + +#ifndef NDEBUG + bool valid() override; +#endif /* NDEBUG */ +}; + +#ifndef NDEBUG +template +bool ImmutableObjectHistory::valid() +{ + // The immutable object content is captured either by a shared object, or by its serialization, but not both. + assert(! m_shared_object == ! m_serialized.empty()); + // Verify that the history intervals are sorted and do not overlap. + if (! m_history.empty()) + for (size_t i = 1; i < m_history.size(); ++ i) + assert(m_history[i - 1].strictly_before(m_history[i])); + return true; +} +#endif /* NDEBUG */ + +#ifndef NDEBUG +template +bool MutableObjectHistory::valid() +{ + // Verify that the history intervals are sorted and do not overlap, and that the data reference counters are correct. + if (! m_history.empty()) { + std::map refcntrs; + assert(m_history.front().data() != nullptr); + ++ refcntrs[m_history.front().data()]; + for (size_t i = 1; i < m_history.size(); ++ i) { + assert(m_history[i - 1].interval().strictly_before(m_history[i].interval())); + ++ refcntrs[m_history[i].data()]; + } + for (const auto &hi : m_history) { + assert(hi.data() != nullptr); + assert(refcntrs[hi.data()] == hi.refcnt()); + } + } + return true; +} +#endif /* NDEBUG */ + +class StackImpl +{ +public: + // Stack needs to be initialized. An empty stack is not valid, there must be a "New Project" status stored at the beginning. + // Initially enable Undo / Redo stack to occupy maximum 10% of the total system physical memory. + StackImpl() : m_memory_limit(std::min(Slic3r::total_physical_memory() / 10, size_t(1 * 16384 * 65536 / UNDO_REDO_DEBUG_LOW_MEM_FACTOR))), m_active_snapshot_time(0), m_current_time(0) {} + + void clear() { + m_objects.clear(); + m_shared_ptr_to_object_id.clear(); + m_snapshots.clear(); + m_active_snapshot_time = 0; + m_current_time = 0; + m_selection.clear(); + } + + bool empty() const { + assert(m_objects.empty() == m_snapshots.empty()); + assert(! m_objects.empty() || (m_current_time == 0 && m_active_snapshot_time == 0)); + return m_snapshots.empty(); + } + + void set_memory_limit(size_t memsize) { m_memory_limit = memsize; } + size_t get_memory_limit() const { return m_memory_limit; } + + size_t memsize() const { + size_t memsize = 0; + for (const auto &object : m_objects) + memsize += object.second->memsize(); + return memsize; + } + + // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. + void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, const SnapshotData &snapshot_data); + void load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos); + + bool has_undo_snapshot() const; + bool has_redo_snapshot() const; + bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, Slic3r::GUI::GLGizmosManager &gizmos, const SnapshotData &snapshot_data, size_t jump_to_time); + bool redo(Slic3r::Model &model, Slic3r::GUI::GLGizmosManager &gizmos, size_t jump_to_time); + void release_least_recently_used(); + + // Snapshot history (names with timestamps). + const std::vector& snapshots() const { return m_snapshots; } + // Timestamp of the active snapshot. + size_t active_snapshot_time() const { return m_active_snapshot_time; } + bool temp_snapshot_active() const { return m_snapshots.back().timestamp == m_active_snapshot_time && ! m_snapshots.back().is_topmost_captured(); } + + const Selection& selection_deserialized() const { return m_selection; } + +//protected: + template ObjectID save_mutable_object(const T &object); + template ObjectID save_immutable_object(std::shared_ptr &object, bool optional); + template T* load_mutable_object(const Slic3r::ObjectID id); + template std::shared_ptr load_immutable_object(const Slic3r::ObjectID id, bool optional); + template void load_mutable_object(const Slic3r::ObjectID id, T &target); + +#ifdef SLIC3R_UNDOREDO_DEBUG + std::string format() const { + std::string out = "Objects\n"; + for (const std::pair> &kvp : m_objects) + out += std::string("ObjectID:") + std::to_string(kvp.first.id) + " " + kvp.second->format() + "\n"; + out += "Snapshots\n"; + for (const Snapshot &snapshot : m_snapshots) { + if (snapshot.timestamp == m_active_snapshot_time) + out += ">>> "; + out += std::string("Name: \"") + snapshot.name + "\", timestamp: " + std::to_string(snapshot.timestamp) + + ", Model ID:" + ((snapshot.model_id == 0) ? "Invalid" : std::to_string(snapshot.model_id)) + "\n"; + } + if (m_active_snapshot_time > m_snapshots.back().timestamp) + out += ">>>\n"; + out += "Current time: " + std::to_string(m_current_time) + "\n"; + out += "Total memory occupied: " + std::to_string(this->memsize()) + "\n"; + return out; + } + void print() const { + std::cout << "Undo / Redo stack" << std::endl; + std::cout << this->format() << std::endl; + } +#endif /* SLIC3R_UNDOREDO_DEBUG */ + + +#ifndef NDEBUG + bool valid() const { + assert(! m_snapshots.empty()); + assert(m_snapshots.back().is_topmost()); + auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); + assert(it != m_snapshots.begin() && it != m_snapshots.end() && it->timestamp == m_active_snapshot_time); + assert(m_active_snapshot_time <= m_snapshots.back().timestamp); + for (auto it = m_objects.begin(); it != m_objects.end(); ++ it) + assert(it->second->valid()); + return true; + } +#endif /* NDEBUG */ + +private: + template ObjectID immutable_object_id(const std::shared_ptr &ptr) { + return this->immutable_object_id_impl((const void*)ptr.get()); + } + ObjectID immutable_object_id_impl(const void *ptr) { + auto it = m_shared_ptr_to_object_id.find(ptr); + if (it == m_shared_ptr_to_object_id.end()) { + // Allocate a new temporary ObjectID for this shared pointer. + ObjectBase object_with_id; + it = m_shared_ptr_to_object_id.insert(it, std::make_pair(ptr, object_with_id.id())); + } + return it->second; + } + void collect_garbage(); + + // Maximum memory allowed to be occupied by the Undo / Redo stack. If the limit is exceeded, + // least recently used snapshots will be released. + size_t m_memory_limit; + // Each individual object (Model, ModelObject, ModelInstance, ModelVolume, Selection, TriangleMesh) + // is stored with its own history, referenced by the ObjectID. Immutable objects do not provide + // their own IDs, therefore there are temporary IDs generated for them and stored to m_shared_ptr_to_object_id. + std::map> m_objects; + std::map m_shared_ptr_to_object_id; + // Snapshot history (names with timestamps). + std::vector m_snapshots; + // Timestamp of the active snapshot. + size_t m_active_snapshot_time; + // Logical time counter. m_current_time is being incremented with each snapshot taken. + size_t m_current_time; + // Last selection serialized or deserialized. + Selection m_selection; +}; + +using InputArchive = cereal::UserDataAdapter; +using OutputArchive = cereal::UserDataAdapter; + +} // namespace UndoRedo + +class Model; +class ModelObject; +class ModelVolume; +class ModelInstance; +class ModelMaterial; +class DynamicPrintConfig; +class TriangleMesh; + +} // namespace Slic3r + +namespace cereal +{ + // Let cereal know that there are load / save non-member functions declared for ModelObject*, ignore serialization of pointers triggering + // static assert, that cereal does not support serialization of raw pointers. + template struct specialize {}; + template struct specialize {}; + template struct specialize {}; + template struct specialize {}; + template struct specialize {}; + template struct specialize, cereal::specialization::non_member_load_save> {}; + + // Store ObjectBase derived class onto the Undo / Redo stack as a separate object, + // store just the ObjectID to this stream. + template void save(BinaryOutputArchive& ar, T* const& ptr) + { + ar(cereal::get_user_data(ar).save_mutable_object(*ptr)); + } + + // Load ObjectBase derived class from the Undo / Redo stack as a separate object + // based on the ObjectID loaded from this stream. + template void load(BinaryInputArchive& ar, T*& ptr) + { + Slic3r::UndoRedo::StackImpl& stack = cereal::get_user_data(ar); + size_t id; + ar(id); + ptr = stack.load_mutable_object(Slic3r::ObjectID(id)); + } + + // Store ObjectBase derived class onto the Undo / Redo stack as a separate object, + // store just the ObjectID to this stream. + template void save(BinaryOutputArchive &ar, const std::unique_ptr &ptr) + { + ar(cereal::get_user_data(ar).save_mutable_object(*ptr.get())); + } + + // Load ObjectBase derived class from the Undo / Redo stack as a separate object + // based on the ObjectID loaded from this stream. + template void load(BinaryInputArchive &ar, std::unique_ptr &ptr) + { + Slic3r::UndoRedo::StackImpl& stack = cereal::get_user_data(ar); + size_t id; + ar(id); + ptr.reset(stack.load_mutable_object(Slic3r::ObjectID(id))); + } + + // Store ObjectBase derived class onto the Undo / Redo stack as a separate object, + // store just the ObjectID to this stream. + template void save_by_value(BinaryOutputArchive& ar, const T &cfg) + { + ar(cereal::get_user_data(ar).save_mutable_object(cfg)); + } + // Load ObjectBase derived class from the Undo / Redo stack as a separate object + // based on the ObjectID loaded from this stream. + template void load_by_value(BinaryInputArchive& ar, T &cfg) + { + Slic3r::UndoRedo::StackImpl& stack = cereal::get_user_data(ar); + size_t id; + ar(id); + stack.load_mutable_object(Slic3r::ObjectID(id), cfg); + } + + // Store ObjectBase derived class onto the Undo / Redo stack as a separate object, + // store just the ObjectID to this stream. + template void save(BinaryOutputArchive &ar, const std::shared_ptr &ptr) + { + ar(cereal::get_user_data(ar).save_immutable_object(const_cast&>(ptr), false)); + } + template void save_optional(BinaryOutputArchive &ar, const std::shared_ptr &ptr) + { + ar(cereal::get_user_data(ar).save_immutable_object(const_cast&>(ptr), true)); + } + + // Load ObjectBase derived class from the Undo / Redo stack as a separate object + // based on the ObjectID loaded from this stream. + template void load(BinaryInputArchive &ar, std::shared_ptr &ptr) + { + Slic3r::UndoRedo::StackImpl &stack = cereal::get_user_data(ar); + size_t id; + ar(id); + ptr = stack.load_immutable_object(Slic3r::ObjectID(id), false); + } + template void load_optional(BinaryInputArchive &ar, std::shared_ptr &ptr) + { + Slic3r::UndoRedo::StackImpl &stack = cereal::get_user_data(ar); + size_t id; + ar(id); + ptr = stack.load_immutable_object(Slic3r::ObjectID(id), true); + } +} + +#include +#include +#include +#include + +namespace Slic3r { +namespace UndoRedo { + +template std::shared_ptr& ImmutableObjectHistory::shared_ptr(StackImpl &stack) +{ + if (m_shared_object.get() == nullptr && ! this->m_serialized.empty()) { + // Deserialize the object. + std::istringstream iss(m_serialized); + { + Slic3r::UndoRedo::InputArchive archive(stack, iss); + typedef typename std::remove_const::type Type; + std::unique_ptr mesh(new Type()); + archive(*mesh.get()); + m_shared_object = std::move(mesh); + } + } + return m_shared_object; +} + +template ObjectID StackImpl::save_mutable_object(const T &object) +{ + // First find or allocate a history stack for the ObjectID of this object instance. + auto it_object_history = m_objects.find(object.id()); + if (it_object_history == m_objects.end()) + it_object_history = m_objects.insert(it_object_history, std::make_pair(object.id(), std::unique_ptr>(new MutableObjectHistory()))); + auto *object_history = static_cast*>(it_object_history->second.get()); + // Then serialize the object into a string. + std::ostringstream oss; + { + Slic3r::UndoRedo::OutputArchive archive(*this, oss); + archive(object); + } + object_history->save(m_active_snapshot_time, m_current_time, oss.str()); + return object.id(); +} + +template ObjectID StackImpl::save_immutable_object(std::shared_ptr &object, bool optional) +{ + // First allocate a temporary ObjectID for this pointer. + ObjectID object_id = this->immutable_object_id(object); + // and find or allocate a history stack for the ObjectID associated to this shared_ptr. + auto it_object_history = m_objects.find(object_id); + if (it_object_history == m_objects.end()) + it_object_history = m_objects.emplace_hint(it_object_history, object_id, std::unique_ptr>(new ImmutableObjectHistory(object, optional))); + else + assert(it_object_history->second.get()->is_optional() == optional); + // Then save the interval. + static_cast*>(it_object_history->second.get())->save(m_active_snapshot_time, m_current_time); + return object_id; +} + +template T* StackImpl::load_mutable_object(const Slic3r::ObjectID id) +{ + T *target = new T(); + this->load_mutable_object(id, *target); + return target; +} + +template std::shared_ptr StackImpl::load_immutable_object(const Slic3r::ObjectID id, bool optional) +{ + // First find a history stack for the ObjectID of this object instance. + auto it_object_history = m_objects.find(id); + assert(optional || it_object_history != m_objects.end()); + if (it_object_history == m_objects.end()) + return std::shared_ptr(); + auto *object_history = static_cast*>(it_object_history->second.get()); + assert(object_history->has_snapshot(m_active_snapshot_time)); + object_history->restore_optional(); + return object_history->shared_ptr(*this); +} + +template void StackImpl::load_mutable_object(const Slic3r::ObjectID id, T &target) +{ + // First find a history stack for the ObjectID of this object instance. + auto it_object_history = m_objects.find(id); + assert(it_object_history != m_objects.end()); + auto *object_history = static_cast*>(it_object_history->second.get()); + // Then get the data associated with the object history and m_active_snapshot_time. + std::istringstream iss(object_history->load(m_active_snapshot_time)); + Slic3r::UndoRedo::InputArchive archive(*this, iss); + target.m_id = id; + archive(target); +} + +// Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. +void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, const SnapshotData &snapshot_data) +{ + // Release old snapshot data. + assert(m_active_snapshot_time <= m_current_time); + for (auto &kvp : m_objects) + kvp.second->release_after_timestamp(m_active_snapshot_time); + { + auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); + m_snapshots.erase(it, m_snapshots.end()); + } + // Take new snapshots. + this->save_mutable_object(model); + m_selection.volumes_and_instances.clear(); + m_selection.volumes_and_instances.reserve(selection.get_volume_idxs().size()); + m_selection.mode = selection.get_mode(); + for (unsigned int volume_idx : selection.get_volume_idxs()) + m_selection.volumes_and_instances.emplace_back(selection.get_volume(volume_idx)->geometry_id); + this->save_mutable_object(m_selection); + this->save_mutable_object(gizmos); + // Save the snapshot info. + m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id, snapshot_data); + m_active_snapshot_time = m_current_time; + // Save snapshot info of the last "current" aka "top most" state, that is only being serialized + // if undoing an action. Such a snapshot has an invalid Model ID assigned if it was not taken yet. + m_snapshots.emplace_back(topmost_snapshot_name, m_active_snapshot_time, 0, snapshot_data); + // Release empty objects from the history. + this->collect_garbage(); + assert(this->valid()); +#ifdef SLIC3R_UNDOREDO_DEBUG + std::cout << "After snapshot" << std::endl; + this->print(); +#endif /* SLIC3R_UNDOREDO_DEBUG */ +} + +void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos) +{ + // Find the snapshot by time. It must exist. + const auto it_snapshot = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(timestamp)); + if (it_snapshot == m_snapshots.end() || it_snapshot->timestamp != timestamp) + throw std::runtime_error((boost::format("Snapshot with timestamp %1% does not exist") % timestamp).str()); + + m_active_snapshot_time = timestamp; + model.clear_objects(); + model.clear_materials(); + this->load_mutable_object(ObjectID(it_snapshot->model_id), model); + model.update_links_bottom_up_recursive(); + m_selection.volumes_and_instances.clear(); + this->load_mutable_object(m_selection.id(), m_selection); + //gizmos.reset_all_states(); FIXME: is this really necessary? It is quite unpleasant for the gizmo undo/redo substack + this->load_mutable_object(gizmos.id(), gizmos); + // Sort the volumes so that we may use binary search. + std::sort(m_selection.volumes_and_instances.begin(), m_selection.volumes_and_instances.end()); + this->m_active_snapshot_time = timestamp; + assert(this->valid()); +} + +bool StackImpl::has_undo_snapshot() const +{ + assert(this->valid()); + auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); + return -- it != m_snapshots.begin(); +} + +bool StackImpl::has_redo_snapshot() const +{ + assert(this->valid()); + auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); + return ++ it != m_snapshots.end(); +} + +bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, Slic3r::GUI::GLGizmosManager &gizmos, const SnapshotData &snapshot_data, size_t time_to_load) +{ + assert(this->valid()); + if (time_to_load == SIZE_MAX) { + auto it_current = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); + if (-- it_current == m_snapshots.begin()) + return false; + time_to_load = it_current->timestamp; + } + assert(time_to_load < m_active_snapshot_time); + assert(std::binary_search(m_snapshots.begin(), m_snapshots.end(), Snapshot(time_to_load))); + bool new_snapshot_taken = false; + if (m_active_snapshot_time == m_snapshots.back().timestamp && ! m_snapshots.back().is_topmost_captured()) { + // The current state is temporary. The current state needs to be captured to be redoable. + this->take_snapshot(topmost_snapshot_name, model, selection, gizmos, snapshot_data); + // The line above entered another topmost_snapshot_name. + assert(m_snapshots.back().is_topmost()); + assert(! m_snapshots.back().is_topmost_captured()); + // Pop it back, it is not needed as there is now a captured topmost state. + m_snapshots.pop_back(); + // current_time was extended, but it should not cause any harm. Resetting it back may complicate the logic unnecessarily. + //-- m_current_time; + assert(m_snapshots.back().is_topmost()); + assert(m_snapshots.back().is_topmost_captured()); + new_snapshot_taken = true; + } + this->load_snapshot(time_to_load, model, gizmos); + if (new_snapshot_taken) { + // Release old snapshots if the memory allocated due to capturing the top most state is excessive. + // Don't release the snapshots here, release them first after the scene and background processing gets updated, as this will release some references + // to the shared TriangleMeshes. + //this->release_least_recently_used(); + } +#ifdef SLIC3R_UNDOREDO_DEBUG + std::cout << "After undo" << std::endl; + this->print(); +#endif /* SLIC3R_UNDOREDO_DEBUG */ + return true; +} + +bool StackImpl::redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load) +{ + assert(this->valid()); + if (time_to_load == SIZE_MAX) { + auto it_current = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); + if (++ it_current == m_snapshots.end()) + return false; + time_to_load = it_current->timestamp; + } + assert(time_to_load > m_active_snapshot_time); + assert(std::binary_search(m_snapshots.begin(), m_snapshots.end(), Snapshot(time_to_load))); + this->load_snapshot(time_to_load, model, gizmos); +#ifdef SLIC3R_UNDOREDO_DEBUG + std::cout << "After redo" << std::endl; + this->print(); +#endif /* SLIC3R_UNDOREDO_DEBUG */ + return true; +} + +void StackImpl::collect_garbage() +{ + // Purge objects with empty histories. + for (auto it = m_objects.begin(); it != m_objects.end();) { + if (it->second->empty()) { + if (it->second->immutable_object_ptr() != nullptr) + // Release the immutable object from the ptr to ObjectID map. + m_shared_ptr_to_object_id.erase(it->second->immutable_object_ptr()); + it = m_objects.erase(it); + } else + ++ it; + } +} + +void StackImpl::release_least_recently_used() +{ + assert(this->valid()); + size_t current_memsize = this->memsize(); +#ifdef SLIC3R_UNDOREDO_DEBUG + bool released = false; +#endif + // First try to release the optional immutable data (for example the convex hulls), + // or the shared vertices of triangle meshes. + for (auto it = m_objects.begin(); current_memsize > m_memory_limit && it != m_objects.end();) { + const void *ptr = it->second->immutable_object_ptr(); + size_t mem_released = it->second->release_optional(); + if (it->second->empty()) { + if (ptr != nullptr) + // Release the immutable object from the ptr to ObjectID map. + m_shared_ptr_to_object_id.erase(ptr); + mem_released += it->second->memsize(); + it = m_objects.erase(it); + } else + ++ it; + assert(current_memsize >= mem_released); + if (current_memsize >= mem_released) + current_memsize -= mem_released; + else + current_memsize = 0; + } + while (current_memsize > m_memory_limit && m_snapshots.size() >= 3) { + // From which side to remove a snapshot? + assert(m_snapshots.front().timestamp < m_active_snapshot_time); + size_t mem_released = 0; + if (m_snapshots[1].timestamp == m_active_snapshot_time) { + // Remove the last snapshot. +#if 0 + for (auto it = m_objects.begin(); it != m_objects.end();) { + mem_released += it->second->release_after_timestamp(m_snapshots.back().timestamp); + if (it->second->empty()) { + if (it->second->immutable_object_ptr() != nullptr) + // Release the immutable object from the ptr to ObjectID map. + m_shared_ptr_to_object_id.erase(it->second->immutable_object_ptr()); + mem_released += it->second->memsize(); + it = m_objects.erase(it); + } else + ++ it; + } + m_snapshots.pop_back(); + m_snapshots.back().name = topmost_snapshot_name; +#else + // Rather don't release the last snapshot as it will be very confusing to the user + // as of why he cannot jump to the top most state. The Undo / Redo stack maximum size + // should be set low enough to accomodate for the top most snapshot. + break; +#endif + } else { + // Remove the first snapshot. + for (auto it = m_objects.begin(); it != m_objects.end();) { + mem_released += it->second->release_before_timestamp(m_snapshots[1].timestamp); + if (it->second->empty()) { + if (it->second->immutable_object_ptr() != nullptr) + // Release the immutable object from the ptr to ObjectID map. + m_shared_ptr_to_object_id.erase(it->second->immutable_object_ptr()); + mem_released += it->second->memsize(); + it = m_objects.erase(it); + } else + ++ it; + } + m_snapshots.erase(m_snapshots.begin()); + } + assert(current_memsize >= mem_released); + if (current_memsize >= mem_released) + current_memsize -= mem_released; + else + current_memsize = 0; +#ifdef SLIC3R_UNDOREDO_DEBUG + released = true; +#endif + } + assert(this->valid()); +#ifdef SLIC3R_UNDOREDO_DEBUG + std::cout << "After release_least_recently_used" << std::endl; + this->print(); +#endif /* SLIC3R_UNDOREDO_DEBUG */ +} + +// Wrappers of the private implementation. +Stack::Stack() : pimpl(new StackImpl()) {} +Stack::~Stack() {} +void Stack::clear() { pimpl->clear(); } +bool Stack::empty() const { return pimpl->empty(); } + +void Stack::set_memory_limit(size_t memsize) { pimpl->set_memory_limit(memsize); } +size_t Stack::get_memory_limit() const { return pimpl->get_memory_limit(); } +size_t Stack::memsize() const { return pimpl->memsize(); } +void Stack::release_least_recently_used() { pimpl->release_least_recently_used(); } +void Stack::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, const SnapshotData &snapshot_data) + { pimpl->take_snapshot(snapshot_name, model, selection, gizmos, snapshot_data); } +bool Stack::has_undo_snapshot() const { return pimpl->has_undo_snapshot(); } +bool Stack::has_redo_snapshot() const { return pimpl->has_redo_snapshot(); } +bool Stack::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, const SnapshotData &snapshot_data, size_t time_to_load) + { return pimpl->undo(model, selection, gizmos, snapshot_data, time_to_load); } +bool Stack::redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load) { return pimpl->redo(model, gizmos, time_to_load); } +const Selection& Stack::selection_deserialized() const { return pimpl->selection_deserialized(); } + +const std::vector& Stack::snapshots() const { return pimpl->snapshots(); } +size_t Stack::active_snapshot_time() const { return pimpl->active_snapshot_time(); } +bool Stack::temp_snapshot_active() const { return pimpl->temp_snapshot_active(); } + +} // namespace UndoRedo +} // namespace Slic3r + + +//FIXME we should have unit tests for testing serialization of basic types as DynamicPrintConfig. +#if 0 +#include "libslic3r/Config.hpp" +#include "libslic3r/PrintConfig.hpp" +namespace Slic3r { + bool test_dynamic_print_config_serialization() { + FullPrintConfig full_print_config; + DynamicPrintConfig cfg; + cfg.apply(full_print_config, false); + + std::string serialized; + try { + std::ostringstream ss; + cereal::BinaryOutputArchive oarchive(ss); + oarchive(cfg); + serialized = ss.str(); + } catch (std::runtime_error e) { + e.what(); + } + + DynamicPrintConfig cfg2; + try { + std::stringstream ss(serialized); + cereal::BinaryInputArchive iarchive(ss); + iarchive(cfg2); + } catch (std::runtime_error e) { + e.what(); + } + + if (cfg == cfg2) { + printf("Yes!\n"); + return true; + } + printf("No!\n"); + return false; + } +} // namespace Slic3r +#endif diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp new file mode 100644 index 0000000000..2901eacebf --- /dev/null +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -0,0 +1,143 @@ +#ifndef slic3r_Utils_UndoRedo_hpp_ +#define slic3r_Utils_UndoRedo_hpp_ + +#include +#include +#include +#include +#include + +#include + +typedef double coordf_t; +typedef std::pair t_layer_height_range; + +namespace Slic3r { + +class Model; +enum PrinterTechnology : unsigned char; + +namespace GUI { + class Selection; + class GLGizmosManager; +} // namespace GUI + +namespace UndoRedo { + +// Data structure to be stored with each snapshot. +// Storing short data (bit masks, ints) with each snapshot instead of being serialized into the Undo / Redo stack +// is likely cheaper in term of both the runtime and memory allocation. +// Also the SnapshotData is available without having to deserialize the snapshot from the Undo / Redo stack, +// which may be handy sometimes. +struct SnapshotData +{ + // Constructor is defined in .cpp due to the forward declaration of enum PrinterTechnology. + SnapshotData(); + + PrinterTechnology printer_technology; + // Bitmap of Flags (see the Flags enum). + unsigned int flags; + int layer_range_idx; + + // Bitmask of various binary flags to be stored with the snapshot. + enum Flags { + VARIABLE_LAYER_EDITING_ACTIVE = 1, + SELECTED_SETTINGS_ON_SIDEBAR = 2, + SELECTED_LAYERROOT_ON_SIDEBAR = 4, + SELECTED_LAYER_ON_SIDEBAR = 8, + RECALCULATE_SLA_SUPPORTS = 16 + }; +}; + +struct Snapshot +{ + Snapshot(size_t timestamp) : timestamp(timestamp) {} + Snapshot(const std::string &name, size_t timestamp, size_t model_id, const SnapshotData &snapshot_data) : + name(name), timestamp(timestamp), model_id(model_id), snapshot_data(snapshot_data) {} + + std::string name; + size_t timestamp; + size_t model_id; + SnapshotData snapshot_data; + + bool operator< (const Snapshot &rhs) const { return this->timestamp < rhs.timestamp; } + bool operator==(const Snapshot &rhs) const { return this->timestamp == rhs.timestamp; } + + // The topmost snapshot represents the current state when going forward. + bool is_topmost() const; + // The topmost snapshot is not being serialized to the Undo / Redo stack until going back in time, + // when the top most state is being serialized, so we can redo back to the top most state. + bool is_topmost_captured() const { assert(this->is_topmost()); return model_id > 0; } +}; + +// Excerpt of Slic3r::GUI::Selection for serialization onto the Undo / Redo stack. +struct Selection : public Slic3r::ObjectBase { + void clear() { mode = 0; volumes_and_instances.clear(); } + unsigned char mode = 0; + std::vector> volumes_and_instances; + template void serialize(Archive &ar) { ar(mode, volumes_and_instances); } +}; + +class StackImpl; + +class Stack +{ +public: + // Stack needs to be initialized. An empty stack is not valid, there must be a "New Project" status stored at the beginning. + // The first "New Project" snapshot shall not be removed. + Stack(); + ~Stack(); + + void clear(); + bool empty() const; + + // Set maximum memory threshold. If the threshold is exceeded, least recently used snapshots are released. + void set_memory_limit(size_t memsize); + size_t get_memory_limit() const; + + // Estimate size of the RAM consumed by the Undo / Redo stack. + size_t memsize() const; + + // Release least recently used snapshots up to the memory limit set above. + void release_least_recently_used(); + + // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. + void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, const SnapshotData &snapshot_data); + + // To be queried to enable / disable the Undo / Redo buttons at the UI. + bool has_undo_snapshot() const; + bool has_redo_snapshot() const; + + // Roll back the time. If time_to_load is SIZE_MAX, the previous snapshot is activated. + // Undoing an action may need to take a snapshot of the current application state, so that redo to the current state is possible. + bool undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, const SnapshotData &snapshot_data, size_t time_to_load = SIZE_MAX); + + // Jump forward in time. If time_to_load is SIZE_MAX, the next snapshot is activated. + bool redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load = SIZE_MAX); + + // Snapshot history (names with timestamps). + // Each snapshot indicates start of an interval in which this operation is performed. + // There is one additional snapshot taken at the very end, which indicates the current unnamed state. + + const std::vector& snapshots() const; + // Timestamp of the active snapshot. One of the snapshots of this->snapshots() shall have Snapshot::timestamp equal to this->active_snapshot_time(). + // The snapshot time indicates start of an operation, which is finished at the time of the following snapshot, therefore + // the active snapshot is the successive snapshot. The same logic applies to the time_to_load parameter of undo() and redo() operations. + size_t active_snapshot_time() const; + // Temporary snapshot is active if the topmost snapshot is active and it has not been captured yet. + // In that case the Undo action will capture the last snapshot. + bool temp_snapshot_active() const; + + // After load_snapshot() / undo() / redo() the selection is deserialized into a list of ObjectIDs, which needs to be converted + // into the list of GLVolume pointers once the 3D scene is updated. + const Selection& selection_deserialized() const; + +private: + friend class StackImpl; + std::unique_ptr pimpl; +}; + +}; // namespace UndoRedo +}; // namespace Slic3r + +#endif /* slic3r_Utils_UndoRedo_hpp_ */ diff --git a/src/stb_dxt/stb_dxt.h b/src/stb_dxt/stb_dxt.h new file mode 100644 index 0000000000..db19110f43 --- /dev/null +++ b/src/stb_dxt/stb_dxt.h @@ -0,0 +1,1049 @@ +// stb_dxt.h - Real-Time DXT1/DXT5 compressor +// Based on original by fabian "ryg" giesen v1.04 +// Custom version, modified by Yann Collet +// +/* + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - RygsDXTc source repository : http://code.google.com/p/rygsdxtc/ + +*/ +// use '#define STB_DXT_IMPLEMENTATION' before including to create the implementation +// +// USAGE: +// call stb_compress_dxt_block() for every block (you must pad) +// source should be a 4x4 block of RGBA data in row-major order; +// A is ignored if you specify alpha=0; you can turn on dithering +// and "high quality" using mode. +// +// version history: +// v1.06 - (cyan) implement Fabian Giesen's comments +// v1.05 - (cyan) speed optimizations +// v1.04 - (ryg) default to no rounding bias for lerped colors (as per S3TC/DX10 spec); +// single color match fix (allow for inexact color interpolation); +// optimal DXT5 index finder; "high quality" mode that runs multiple refinement steps. +// v1.03 - (stb) endianness support +// v1.02 - (stb) fix alpha encoding bug +// v1.01 - (stb) fix bug converting to RGB that messed up quality, thanks ryg & cbloom +// v1.00 - (stb) first release + +#ifndef STB_INCLUDE_STB_DXT_H +#define STB_INCLUDE_STB_DXT_H + + +//******************************************************************* +// Enable custom Optimisations +// Comment this define if you want to revert to ryg's original code +#define NEW_OPTIMISATIONS +//******************************************************************* + +// compression mode (bitflags) +#define STB_DXT_NORMAL 0 +#define STB_DXT_DITHER 1 // use dithering. dubious win. never use for normal maps and the like! +#define STB_DXT_HIGHQUAL 2 // high quality mode, does two refinement steps instead of 1. ~30-40% slower. + +// The original signature has been modified by adding the parameter compressed_size which returns +// the size in bytes of the compressed data contained into dst +void rygCompress(unsigned char *dst, unsigned char *src, int w, int h, int isDxt5, int& compressed_size); + +// TODO remove these, not working properly.. +void rygCompressYCoCg( unsigned char *dst, unsigned char *src, int w, int h ); +void linearize( unsigned char * dst, const unsigned char * src, int n ); + +void stb_compress_dxt_block(unsigned char *dest, const unsigned char *src, int alpha, int mode); +#define STB_COMPRESS_DXT_BLOCK + +#ifdef STB_DXT_IMPLEMENTATION + +// configuration options for DXT encoder. set them in the project/makefile or just define +// them at the top. + +// STB_DXT_USE_ROUNDING_BIAS +// use a rounding bias during color interpolation. this is closer to what "ideal" +// interpolation would do but doesn't match the S3TC/DX10 spec. old versions (pre-1.03) +// implicitly had this turned on. +// +// in case you're targeting a specific type of hardware (e.g. console programmers): +// NVidia and Intel GPUs (as of 2010) as well as DX9 ref use DXT decoders that are closer +// to STB_DXT_USE_ROUNDING_BIAS. AMD/ATI, S3 and DX10 ref are closer to rounding with no bias. +// you also see "(a*5 + b*3) / 8" on some old GPU designs. +// #define STB_DXT_USE_ROUNDING_BIAS + +#include +#include +#include +#include // memset +#include +#include +#include + + +static unsigned char stb__Expand5[32]; +static unsigned char stb__Expand6[64]; +static unsigned char stb__OMatch5[256][2]; +static unsigned char stb__OMatch6[256][2]; +static unsigned char stb__QuantRBTab[256+16]; +static unsigned char stb__QuantGTab[256+16]; + +static int stb__Mul8Bit(int a, int b) +{ + int t = a*b + 128; + return (t + (t >> 8)) >> 8; +} + +static void stb__From16Bit(unsigned char *out, unsigned short v) +{ + int rv = (v & 0xf800) >> 11; + int gv = (v & 0x07e0) >> 5; + int bv = (v & 0x001f) >> 0; + + out[0] = stb__Expand5[rv]; + out[1] = stb__Expand6[gv]; + out[2] = stb__Expand5[bv]; + out[3] = 0; +} + +static unsigned short stb__As16Bit(int r, int g, int b) +{ + return (stb__Mul8Bit(r,31) << 11) + (stb__Mul8Bit(g,63) << 5) + stb__Mul8Bit(b,31); +} + +// linear interpolation at 1/3 point between a and b, using desired rounding type +static int stb__Lerp13(int a, int b) +{ +#ifdef STB_DXT_USE_ROUNDING_BIAS + // with rounding bias + return a + stb__Mul8Bit(b-a, 0x55); +#else + // without rounding bias + // replace "/ 3" by "* 0xaaab) >> 17" if your compiler sucks or you really need every ounce of speed. + return (2*a + b) / 3; +#endif +} + +// lerp RGB color +static void stb__Lerp13RGB(unsigned char *out, unsigned char *p1, unsigned char *p2) +{ + out[0] = stb__Lerp13(p1[0], p2[0]); + out[1] = stb__Lerp13(p1[1], p2[1]); + out[2] = stb__Lerp13(p1[2], p2[2]); +} + +/****************************************************************************/ + +// compute table to reproduce constant colors as accurately as possible +static void stb__PrepareOptTable(unsigned char *Table,const unsigned char *expand,int size) +{ + int i,mn,mx; + for (i=0;i<256;i++) { + int bestErr = 256; + for (mn=0;mn> 4)]; + ep1[0] = bp[ 0] - dp[ 0]; + dp[ 4] = quant[bp[ 4] + ((7*ep1[0] + 3*ep2[2] + 5*ep2[1] + ep2[0]) >> 4)]; + ep1[1] = bp[ 4] - dp[ 4]; + dp[ 8] = quant[bp[ 8] + ((7*ep1[1] + 3*ep2[3] + 5*ep2[2] + ep2[1]) >> 4)]; + ep1[2] = bp[ 8] - dp[ 8]; + dp[12] = quant[bp[12] + ((7*ep1[2] + 5*ep2[3] + ep2[2]) >> 4)]; + ep1[3] = bp[12] - dp[12]; + bp += 16; + dp += 16; + et = ep1, ep1 = ep2, ep2 = et; // swap + } + } +} + +// The color matching function +static unsigned int stb__MatchColorsBlock(unsigned char *block, unsigned char *color,int dither) +{ + unsigned int mask = 0; + int dirr = color[0*4+0] - color[1*4+0]; + int dirg = color[0*4+1] - color[1*4+1]; + int dirb = color[0*4+2] - color[1*4+2]; + int dots[16]; + int stops[4]; + int i; + int c0Point, halfPoint, c3Point; + + for(i=0;i<16;i++) + dots[i] = block[i*4+0]*dirr + block[i*4+1]*dirg + block[i*4+2]*dirb; + + for(i=0;i<4;i++) + stops[i] = color[i*4+0]*dirr + color[i*4+1]*dirg + color[i*4+2]*dirb; + + // think of the colors as arranged on a line; project point onto that line, then choose + // next color out of available ones. we compute the crossover points for "best color in top + // half"/"best in bottom half" and then the same inside that subinterval. + // + // relying on this 1d approximation isn't always optimal in terms of euclidean distance, + // but it's very close and a lot faster. + // http://cbloomrants.blogspot.com/2008/12/12-08-08-dxtc-summary.html + + c0Point = (stops[1] + stops[3]) >> 1; + halfPoint = (stops[3] + stops[2]) >> 1; + c3Point = (stops[2] + stops[0]) >> 1; + + if(!dither) + { + // the version without dithering is straightforward + +#ifdef NEW_OPTIMISATIONS + const int indexMap[8] = { 0 << 30,2 << 30,0 << 30,2 << 30,3 << 30,3 << 30,1 << 30,1 << 30 }; + + for(int i=0;i<16;i++) + { + int dot = dots[i]; + mask >>= 2; + + int bits =( (dot < halfPoint) ? 4 : 0 ) + | ( (dot < c0Point) ? 2 : 0 ) + | ( (dot < c3Point) ? 1 : 0 ); + + mask |= indexMap[bits]; + } + +#else + for (i=15;i>=0;i--) { + int dot = dots[i]; + mask <<= 2; + + if(dot < halfPoint) + mask |= (dot < c0Point) ? 1 : 3; + else + mask |= (dot < c3Point) ? 2 : 0; + } +#endif + + } else { + // with floyd-steinberg dithering + int err[8],*ep1 = err,*ep2 = err+4; + int *dp = dots, y; + + c0Point <<= 4; + halfPoint <<= 4; + c3Point <<= 4; + for(i=0;i<8;i++) + err[i] = 0; + + for(y=0;y<4;y++) + { + int dot,lmask,step; + + dot = (dp[0] << 4) + (3*ep2[1] + 5*ep2[0]); + if(dot < halfPoint) + step = (dot < c0Point) ? 1 : 3; + else + step = (dot < c3Point) ? 2 : 0; + ep1[0] = dp[0] - stops[step]; + lmask = step; + + dot = (dp[1] << 4) + (7*ep1[0] + 3*ep2[2] + 5*ep2[1] + ep2[0]); + if(dot < halfPoint) + step = (dot < c0Point) ? 1 : 3; + else + step = (dot < c3Point) ? 2 : 0; + ep1[1] = dp[1] - stops[step]; + lmask |= step<<2; + + dot = (dp[2] << 4) + (7*ep1[1] + 3*ep2[3] + 5*ep2[2] + ep2[1]); + if(dot < halfPoint) + step = (dot < c0Point) ? 1 : 3; + else + step = (dot < c3Point) ? 2 : 0; + ep1[2] = dp[2] - stops[step]; + lmask |= step<<4; + + dot = (dp[3] << 4) + (7*ep1[2] + 5*ep2[3] + ep2[2]); + if(dot < halfPoint) + step = (dot < c0Point) ? 1 : 3; + else + step = (dot < c3Point) ? 2 : 0; + ep1[3] = dp[3] - stops[step]; + lmask |= step<<6; + + dp += 4; + mask |= lmask << (y*8); + { int *et = ep1; ep1 = ep2; ep2 = et; } // swap + } + } + + return mask; +} + +// The color optimization function. (Clever code, part 1) +static void stb__OptimizeColorsBlock(unsigned char *block, unsigned short *pmax16, unsigned short *pmin16) +{ + unsigned char *minp, *maxp; + double magn; + int v_r,v_g,v_b; + static const int nIterPower = 4; + float covf[6],vfr,vfg,vfb; + + // determine color distribution + int cov[6]; + int mu[3],min[3],max[3]; + int ch,i,iter; + + for(ch=0;ch<3;ch++) + { + const unsigned char *bp = ((const unsigned char *) block) + ch; + int muv,minv,maxv; + +#ifdef NEW_OPTIMISATIONS +# define MIN(a,b) (int)a + ( ((int)b-a) & ( ((int)b-a) >> 31 ) ) +# define MAX(a,b) (int)a + ( ((int)b-a) & ( ((int)a-b) >> 31 ) ) +# define RANGE(a,b,n) int min##n = MIN(a,b); int max##n = a+b - min##n; muv += a+b; +# define MINMAX(a,b,n) int min##n = MIN(min##a, min##b); int max##n = MAX(max##a, max##b); + + muv = 0; + RANGE(bp[0], bp[4], 1); + RANGE(bp[8], bp[12], 2); + RANGE(bp[16], bp[20], 3); + RANGE(bp[24], bp[28], 4); + RANGE(bp[32], bp[36], 5); + RANGE(bp[40], bp[44], 6); + RANGE(bp[48], bp[52], 7); + RANGE(bp[56], bp[60], 8); + + MINMAX(1,2,9); + MINMAX(3,4,10); + MINMAX(5,6,11); + MINMAX(7,8,12); + + MINMAX(9,10,13); + MINMAX(11,12,14); + + minv = MIN(min13,min14); + maxv = MAX(max13,max14); + +#else + muv = minv = maxv = bp[0]; + for(i=4;i<64;i+=4) + { + muv += bp[i]; + if (bp[i] < minv) minv = bp[i]; + else if (bp[i] > maxv) maxv = bp[i]; + } +#endif + + mu[ch] = (muv + 8) >> 4; + min[ch] = minv; + max[ch] = maxv; + } + + // determine covariance matrix + for (i=0;i<6;i++) + cov[i] = 0; + + for (i=0;i<16;i++) + { + int r = block[i*4+0] - mu[0]; + int g = block[i*4+1] - mu[1]; + int b = block[i*4+2] - mu[2]; + + cov[0] += r*r; + cov[1] += r*g; + cov[2] += r*b; + cov[3] += g*g; + cov[4] += g*b; + cov[5] += b*b; + } + + // convert covariance matrix to float, find principal axis via power iter + for(i=0;i<6;i++) + covf[i] = cov[i] / 255.0f; + + vfr = (float) (max[0] - min[0]); + vfg = (float) (max[1] - min[1]); + vfb = (float) (max[2] - min[2]); + + for(iter=0;iter magn) magn = fabs(vfg); + if (fabs(vfb) > magn) magn = fabs(vfb); + + if(magn < 4.0f) + { // too small, default to luminance + v_r = 299; // JPEG YCbCr luma coefs, scaled by 1000. + v_g = 587; + v_b = 114; + } else { + magn = 512.0 / magn; + v_r = (int) (vfr * magn); + v_g = (int) (vfg * magn); + v_b = (int) (vfb * magn); + } + + +#ifdef NEW_OPTIMISATIONS + // Pick colors at extreme points + int mind, maxd; + mind = maxd = block[0]*v_r + block[1]*v_g + block[2]*v_b; + minp = maxp = block; + for(i=1;i<16;i++) + { + int dot = block[i*4+0]*v_r + block[i*4+1]*v_g + block[i*4+2]*v_b; + + if (dot < mind) { + mind = dot; + minp = block+i*4; + continue; + } + + if (dot > maxd) { + maxd = dot; + maxp = block+i*4; + } + } +#else + int mind = 0x7fffffff,maxd = -0x7fffffff; + // Pick colors at extreme points + for(i=0;i<16;i++) + { + int dot = block[i*4+0]*v_r + block[i*4+1]*v_g + block[i*4+2]*v_b; + + if (dot < mind) { + mind = dot; + minp = block+i*4; + } + + if (dot > maxd) { + maxd = dot; + maxp = block+i*4; + } + } +#endif + + *pmax16 = stb__As16Bit(maxp[0],maxp[1],maxp[2]); + *pmin16 = stb__As16Bit(minp[0],minp[1],minp[2]); +} + +inline static int stb__sclamp(float y, int p0, int p1) +{ + int x = (int) y; + +#ifdef NEW_OPTIMISATIONS + x = x>p1 ? p1 : x; + return x p1) return p1; + return x; +#endif +} + +// The refinement function. (Clever code, part 2) +// Tries to optimize colors to suit block contents better. +// (By solving a least squares system via normal equations+Cramer's rule) +static int stb__RefineBlock(unsigned char *block, unsigned short *pmax16, unsigned short *pmin16, unsigned int mask) +{ + static const int w1Tab[4] = { 3,0,2,1 }; + static const int prods[4] = { 0x090000,0x000900,0x040102,0x010402 }; + // ^some magic to save a lot of multiplies in the accumulating loop... + // (precomputed products of weights for least squares system, accumulated inside one 32-bit register) + + float frb,fg; + unsigned short oldMin, oldMax, min16, max16; + int i, akku = 0, xx,xy,yy; + int At1_r,At1_g,At1_b; + int At2_r,At2_g,At2_b; + unsigned int cm = mask; + + oldMin = *pmin16; + oldMax = *pmax16; + + if((mask ^ (mask<<2)) < 4) // all pixels have the same index? + { + // yes, linear system would be singular; solve using optimal + // single-color match on average color + int r = 8, g = 8, b = 8; + for (i=0;i<16;++i) { + r += block[i*4+0]; + g += block[i*4+1]; + b += block[i*4+2]; + } + + r >>= 4; g >>= 4; b >>= 4; + + max16 = (stb__OMatch5[r][0]<<11) | (stb__OMatch6[g][0]<<5) | stb__OMatch5[b][0]; + min16 = (stb__OMatch5[r][1]<<11) | (stb__OMatch6[g][1]<<5) | stb__OMatch5[b][1]; + } else { + At1_r = At1_g = At1_b = 0; + At2_r = At2_g = At2_b = 0; + for (i=0;i<16;++i,cm>>=2) + { + int step = cm&3; + int w1 = w1Tab[step]; + int r = block[i*4+0]; + int g = block[i*4+1]; + int b = block[i*4+2]; + + akku += prods[step]; + At1_r += w1*r; + At1_g += w1*g; + At1_b += w1*b; + At2_r += r; + At2_g += g; + At2_b += b; + } + + At2_r = 3*At2_r - At1_r; + At2_g = 3*At2_g - At1_g; + At2_b = 3*At2_b - At1_b; + + // extract solutions and decide solvability + xx = akku >> 16; + yy = (akku >> 8) & 0xff; + xy = (akku >> 0) & 0xff; + + frb = 3.0f * 31.0f / 255.0f / (xx*yy - xy*xy); + fg = frb * 63.0f / 31.0f; + + // solve. + max16 = stb__sclamp((At1_r*yy - At2_r*xy)*frb+0.5f,0,31) << 11; + max16 |= stb__sclamp((At1_g*yy - At2_g*xy)*fg +0.5f,0,63) << 5; + max16 |= stb__sclamp((At1_b*yy - At2_b*xy)*frb+0.5f,0,31) << 0; + + min16 = stb__sclamp((At2_r*xx - At1_r*xy)*frb+0.5f,0,31) << 11; + min16 |= stb__sclamp((At2_g*xx - At1_g*xy)*fg +0.5f,0,63) << 5; + min16 |= stb__sclamp((At2_b*xx - At1_b*xy)*frb+0.5f,0,31) << 0; + } + + *pmin16 = min16; + *pmax16 = max16; + return oldMin != min16 || oldMax != max16; +} + +// Color block compression +static void stb__CompressColorBlock(unsigned char *dest, unsigned char *block, int mode) +{ + unsigned int mask; + int i; + int dither; + int refinecount; + unsigned short max16, min16; + unsigned char dblock[16*4],color[4*4]; + + dither = mode & STB_DXT_DITHER; + refinecount = (mode & STB_DXT_HIGHQUAL) ? 2 : 1; + + // check if block is constant + for (i=1;i<16;i++) + if (((unsigned int *) block)[i] != ((unsigned int *) block)[0]) + break; + + if(i == 16) + { // constant color + int r = block[0], g = block[1], b = block[2]; + mask = 0xaaaaaaaa; + max16 = (stb__OMatch5[r][0]<<11) | (stb__OMatch6[g][0]<<5) | stb__OMatch5[b][0]; + min16 = (stb__OMatch5[r][1]<<11) | (stb__OMatch6[g][1]<<5) | stb__OMatch5[b][1]; + } else + { + // first step: compute dithered version for PCA if desired + if(dither) + stb__DitherBlock(dblock,block); + + // second step: pca+map along principal axis + stb__OptimizeColorsBlock(dither ? dblock : block,&max16,&min16); + if (max16 != min16) + { + stb__EvalColors(color,max16,min16); + mask = stb__MatchColorsBlock(block,color,dither); + } else + mask = 0; + + // third step: refine (multiple times if requested) + for (i=0;i> 8); + dest[2] = (unsigned char) (min16); + dest[3] = (unsigned char) (min16 >> 8); + dest[4] = (unsigned char) (mask); + dest[5] = (unsigned char) (mask >> 8); + dest[6] = (unsigned char) (mask >> 16); + dest[7] = (unsigned char) (mask >> 24); +} + +// Alpha block compression (this is easy for a change) +static void stb__CompressAlphaBlock(unsigned char *dest,unsigned char *src,int mode) +{ + int i,dist,bias,dist4,dist2,bits,mask; + + // find min/max color + int mn,mx; + + mn = mx = src[3]; + for (i=1;i<16;i++) + { + if (src[i*4+3] < mn) mn = src[i*4+3]; + else if (src[i*4+3] > mx) mx = src[i*4+3]; + } + + // encode them + ((unsigned char *)dest)[0] = mx; + ((unsigned char *)dest)[1] = mn; + dest += 2; + +#ifdef NEW_OPTIMISATIONS + // mono-alpha shortcut + if (mn==mx) + { + *(unsigned short*)dest = 0; + dest += 2; + *(unsigned int*)dest = 0; + return; + } +#endif + + // determine bias and emit color indices + // given the choice of mx/mn, these indices are optimal: + // http://fgiesen.wordpress.com/2009/12/15/dxt5-alpha-block-index-determination/ + dist = mx-mn; + //printf("mn = %i; mx = %i; dist = %i\n", mn, mx, dist); + dist4 = dist*4; + dist2 = dist*2; + bias = (dist < 8) ? (dist - 1) : (dist/2 + 2); + bias -= mn * 7; + bits = 0, mask=0; + + for (i=0;i<16;i++) + { + int a = src[i*4+3]*7 + bias; + int ind,t; + + // select index. this is a "linear scale" lerp factor between 0 (val=min) and 7 (val=max). + t = (a >= dist4) ? -1 : 0; ind = t & 4; a -= dist4 & t; + t = (a >= dist2) ? -1 : 0; ind += t & 2; a -= dist2 & t; + ind += (a >= dist); + + // turn linear scale into DXT index (0/1 are extremal pts) + ind = -ind & 7; + ind ^= (2 > ind); + + // write index + mask |= ind << bits; + if((bits += 3) >= 8) + { + *dest++ = mask; + mask >>= 8; + bits -= 8; + } + } +} + + +static void stb__InitDXT() +{ + int i; + for(i=0;i<32;i++) + stb__Expand5[i] = (i<<3)|(i>>2); + + for(i=0;i<64;i++) + stb__Expand6[i] = (i<<2)|(i>>4); + + for(i=0;i<256+16;i++) + { + int v = i-8 < 0 ? 0 : i-8 > 255 ? 255 : i-8; + stb__QuantRBTab[i] = stb__Expand5[stb__Mul8Bit(v,31)]; + stb__QuantGTab[i] = stb__Expand6[stb__Mul8Bit(v,63)]; + } + + stb__PrepareOptTable(&stb__OMatch5[0][0],stb__Expand5,32); + stb__PrepareOptTable(&stb__OMatch6[0][0],stb__Expand6,64); +} + + +void stb_compress_dxt_block(unsigned char *dest, const unsigned char *src, int alpha, int mode) +{ + static int init=1; + if (init) + { + stb__InitDXT(); + init=0; + } + + if (alpha) + { + stb__CompressAlphaBlock(dest,(unsigned char*) src,mode); + dest += 8; + } + + stb__CompressColorBlock(dest,(unsigned char*) src,mode); +} + +int imin(int x, int y) { return (x < y) ? x : y; } + + + + + +static void extractBlock(const unsigned char *src, int x, int y, + int w, int h, unsigned char *block) +{ + int i, j; + +#ifdef NEW_OPTIMISATIONS + if ((w-x >=4) && (h-y >=4)) + { + // Full Square shortcut + src += x*4; + src += y*w*4; + for (i=0; i < 4; ++i) + { + *(unsigned int*)block = *(unsigned int*) src; block += 4; src += 4; + *(unsigned int*)block = *(unsigned int*) src; block += 4; src += 4; + *(unsigned int*)block = *(unsigned int*) src; block += 4; src += 4; + *(unsigned int*)block = *(unsigned int*) src; block += 4; + src += (w*4) - 12; + } + return; + } +#endif + + int bw = imin(w - x, 4); + int bh = imin(h - y, 4); + int bx, by; + + const int rem[] = + { + 0, 0, 0, 0, + 0, 1, 0, 1, + 0, 1, 2, 0, + 0, 1, 2, 3 + }; + + for(i = 0; i < 4; ++i) + { + by = rem[(bh - 1) * 4 + i] + y; + for(j = 0; j < 4; ++j) + { + bx = rem[(bw - 1) * 4 + j] + x; + block[(i * 4 * 4) + (j * 4) + 0] = + src[(by * (w * 4)) + (bx * 4) + 0]; + block[(i * 4 * 4) + (j * 4) + 1] = + src[(by * (w * 4)) + (bx * 4) + 1]; + block[(i * 4 * 4) + (j * 4) + 2] = + src[(by * (w * 4)) + (bx * 4) + 2]; + block[(i * 4 * 4) + (j * 4) + 3] = + src[(by * (w * 4)) + (bx * 4) + 3]; + } + } +} + + // should be a pretty optimized 0-255 clamper +inline static unsigned char clamp255( int n ) +{ + if( n > 255 ) n = 255; + if( n < 0 ) n = 0; + return n; +} + + +void rgbToYCoCgBlock( unsigned char * dst, const unsigned char * src ) +{ + // Calculate Co and Cg extents + int extents = 0; + int n = 0; + int iY, iCo, iCg; //, r, g, b; + int blockCo[16]; + int blockCg[16]; + int i; + + const unsigned char *px = src; + for(i=0;i extents) extents = -iCo; + if( iCo > extents) extents = iCo; + if(-iCg > extents) extents = -iCg; + if( iCg > extents) extents = iCg; + + blockCo[n] = iCo; + blockCg[n++] = iCg; + + px += 4; + } + + // Co = -510..510 + // Cg = -510..510 + float scaleFactor = 1.0f; + if(extents > 127) + scaleFactor = (float)extents * 4.0f / 510.0f; + + // Convert to quantized scalefactor + unsigned char scaleFactorQuantized = (unsigned char)(ceil((scaleFactor - 1.0f) * 31.0f / 3.0f)); + + // Unquantize + scaleFactor = 1.0f + (float)(scaleFactorQuantized / 31.0f) * 3.0f; + + unsigned char bVal = (unsigned char)((scaleFactorQuantized << 3) | (scaleFactorQuantized >> 2)); + + unsigned char *outPx = dst; + + n = 0; + px = src; + /* + for(i=0;i<16;i++) + { + // Calculate components + iY = ( px[0] + (px[1]<<1) + px[2] + 2 ) / 4; + iCo = ((blockCo[n] / scaleFactor) + 128); + iCg = ((blockCg[n] / scaleFactor) + 128); + + if(iCo < 0) iCo = 0; else if(iCo > 255) iCo = 255; + if(iCg < 0) iCg = 0; else if(iCg > 255) iCg = 255; + if(iY < 0) iY = 0; else if(iY > 255) iY = 255; + + px += 4; + + outPx[0] = (unsigned char)iCo; + outPx[1] = (unsigned char)iCg; + outPx[2] = bVal; + outPx[3] = (unsigned char)iY; + + outPx += 4; + }*/ + for(i=0;i<16;i++) + { + // Calculate components + int r = px[0]; + int g = (px[1] + 1) >> 1; + int b = px[2]; + int tmp = (2 + r + b) >> 2; + + // Co + iCo = clamp255( 128 + ((r - b + 1) >> 1) ); + // Y + iY = clamp255( g + tmp ); + // Cg + iCg = clamp255( 128 + g - tmp ); + + px += 4; + + outPx[0] = (unsigned char)iCo; + outPx[1] = (unsigned char)iCg; + outPx[2] = bVal; + outPx[3] = (unsigned char)iY; + + outPx += 4; + } + +} + + +void rygCompress(unsigned char *dst, unsigned char *src, int w, int h, int isDxt5, int& compressed_size) +{ + + unsigned char block[64]; + int x, y; + + unsigned char* initial_dst = dst; + + for (y = 0; y < h; y += 4) + { + for(x = 0; x < w; x += 4) + { + extractBlock(src, x, y, w, h, block); + stb_compress_dxt_block(dst, block, isDxt5, STB_DXT_NORMAL); + dst += isDxt5 ? 16 : 8; + } + } + + compressed_size = dst - initial_dst; +} + +void rygCompressYCoCg( unsigned char *dst, unsigned char *src, int w, int h ) +{ + unsigned char block[64]; + unsigned char ycocgblock[64]; + int x, y; + + for(y = 0; y < h; y += 4) + { + for(x = 0; x < w; x += 4) + { + extractBlock(src, x, y, w, h, block); + rgbToYCoCgBlock(ycocgblock,block); + stb_compress_dxt_block(dst, ycocgblock, 1, 10); + dst += 16; + } + } + +} + +static void stbgl__compress(unsigned char *p, unsigned char *rgba, int w, int h, int isDxt5) +{ + int i,j,y,y2; + int alpha = isDxt5; + + for (j=0; j < w; j += 4) { + int x=4; + for (i=0; i < h; i += 4) { + unsigned char block[16*4]; + if (i+3 >= w) x = w-i; + for (y=0; y < 4; ++y) { + if (j+y >= h) break; + memcpy(block+y*16, rgba + w*4*(j+y) + i*4, x*4); + } + if (x < 4) { + switch (x) { + case 0: assert(0); + case 1: + for (y2=0; y2 < y; ++y2) { + memcpy(block+y2*16+1*4, block+y2*16+0*4, 4); + memcpy(block+y2*16+2*4, block+y2*16+0*4, 8); + } + break; + case 2: + for (y2=0; y2 < y; ++y2) + memcpy(block+y2*16+2*4, block+y2*16+0*4, 8); + break; + case 3: + for (y2=0; y2 < y; ++y2) + memcpy(block+y2*16+3*4, block+y2*16+1*4, 4); + break; + } + } + y2 = 0; + for(; y<4; ++y,++y2) + memcpy(block+y*16, block+y2*16, 4*4); + stb_compress_dxt_block(p, block, alpha, 10); + p += alpha ? 16 : 8; + } + } + // assert(p <= end); +} + +static inline unsigned char linearize(unsigned char inByte) +{ + float srgbVal = ((float)inByte) / 255.0f; + float linearVal; + + if(srgbVal < 0.04045) + linearVal = srgbVal / 12.92f; + else + linearVal = pow( (srgbVal + 0.055f) / 1.055f, 2.4f); + + return (unsigned char)(floor(sqrt(linearVal)* 255.0 + 0.5)); +} + +void linearize( unsigned char * dst, const unsigned char * src, int n ) +{ + n*=4; + for( int i = 0; i < n; i++ ) + dst[i] = linearize(src[i]); +} + + + +#endif // STB_DXT_IMPLEMENTATION + +#endif // STB_INCLUDE_STB_DXT_H diff --git a/t/combineinfill.t b/t/combineinfill.t index 8aa0ff5e39..282bf467ac 100644 --- a/t/combineinfill.t +++ b/t/combineinfill.t @@ -89,7 +89,7 @@ plan tests => 8; # we disable combination after infill has been generated $config->set('infill_every_layers', 1); - $print->apply_config_perl_tests_only($config); + $print->apply($print->print->model->clone, $config); $print->process; ok !(defined first { @{$_->get_region(0)->fill_surfaces} == 0 } diff --git a/t/multi.t b/t/multi.t index 75ce0c286c..8e7225bec7 100644 --- a/t/multi.t +++ b/t/multi.t @@ -25,7 +25,9 @@ use Slic3r::Test; $config->set('extruder_offset', [ [0,0], [20,0], [0,20], [20,20] ]); $config->set('temperature', [200, 180, 170, 160]); $config->set('first_layer_temperature', [206, 186, 166, 156]); - $config->set('toolchange_gcode', ';toolchange'); # test that it doesn't crash when this is supplied + $config->set('toolchange_gcode', 'T[next_extruder] ;toolchange'); # test that it doesn't crash when this is supplied + # Since July 2019, PrusaSlicer only emits automatic Tn command in case that the toolchange_gcode is empty + # The "T[next_extruder]" is therefore needed in this test. my $print = Slic3r::Test::init_print('20mm_cube', config => $config); diff --git a/t/print.t b/t/print.t index be2db34318..2144e80c1e 100644 --- a/t/print.t +++ b/t/print.t @@ -34,25 +34,30 @@ use Slic3r::Test; { # this represents the aggregate config from presets my $config = Slic3r::Config::new_from_defaults; + # Define 4 extruders. + $config->set('nozzle_diameter', [0.4, 0.4, 0.4, 0.4]); # user adds one object to the plater my $print = Slic3r::Test::init_print(my $model = Slic3r::Test::model('20mm_cube'), config => $config); # user sets a per-region option - $print->print->objects->[0]->model_object->config->set('fill_density', 100); - $print->print->reload_object(0); + my $model2 = $model->clone; + $model2->get_object(0)->config->set('fill_density', 100); + $print->apply($model2, $config); + is $print->print->regions->[0]->config->fill_density, 100, 'region config inherits model object config'; # user exports G-code, thus the default config is reapplied - $print->print->apply_config_perl_tests_only($config); - - is $print->print->regions->[0]->config->fill_density, 100, 'apply_config() does not override per-object settings'; + $model2->get_object(0)->config->erase('fill_density'); + $print->apply($model2, $config); + + is $print->print->regions->[0]->config->fill_density, 20, 'region config is resetted'; # user assigns object extruders - $print->print->objects->[0]->model_object->config->set('extruder', 3); - $print->print->objects->[0]->model_object->config->set('perimeter_extruder', 2); - $print->print->reload_object(0); - + $model2->get_object(0)->config->set('extruder', 3); + $model2->get_object(0)->config->set('perimeter_extruder', 2); + $print->apply($model2, $config); + is $print->print->regions->[0]->config->infill_extruder, 3, 'extruder setting is correctly expanded'; is $print->print->regions->[0]->config->perimeter_extruder, 2, 'extruder setting does not override explicitely specified extruders'; } diff --git a/t/skirt_brim.t b/t/skirt_brim.t index b054357841..beb3c94c50 100644 --- a/t/skirt_brim.t +++ b/t/skirt_brim.t @@ -91,6 +91,8 @@ use Slic3r::Test; { my $config = Slic3r::Config::new_from_defaults; + # Define 4 extruders. + $config->set('nozzle_diameter', [0.4, 0.4, 0.4, 0.4]); $config->set('layer_height', 0.4); $config->set('first_layer_height', 0.4); $config->set('skirts', 1); @@ -106,7 +108,7 @@ use Slic3r::Test; # we enable support material after skirt has been generated $config->set('support_material', 1); - $print->apply_config_perl_tests_only($config); + $print->apply($print->print->model->clone, $config); my $skirt_length = 0; my @extrusion_points = (); diff --git a/version.inc b/version.inc index 8f5ff57d1c..f84354ab81 100644 --- a/version.inc +++ b/version.inc @@ -3,7 +3,7 @@ set(SLIC3R_APP_NAME "PrusaSlicer") set(SLIC3R_APP_KEY "PrusaSlicer") -set(SLIC3R_VERSION "2.0.0") +set(SLIC3R_VERSION "2.1.0-alpha1") set(SLIC3R_BUILD_ID "PrusaSlicer-${SLIC3R_VERSION}+UNKNOWN") -set(SLIC3R_RC_VERSION "2,0,0,0") -set(SLIC3R_RC_VERSION_DOTS "2.0.0.0") +set(SLIC3R_RC_VERSION "2,1,0,0") +set(SLIC3R_RC_VERSION_DOTS "2.1.0.0") diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 4696badc49..aaf943970e 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -119,8 +119,6 @@ target_include_directories(XS PRIVATE src ${LIBDIR}/libslic3r) target_compile_definitions(XS PRIVATE -DSLIC3RXS) set_target_properties(XS PROPERTIES PREFIX "") # Prevent cmake from generating libXS.so instead of XS.so -target_link_libraries(XS ${Boost_LIBRARIES}) - if (APPLE) # -liconv: boost links to libiconv by default target_link_libraries(XS "-liconv -framework IOKit" "-framework CoreFoundation" -lc++) @@ -156,12 +154,6 @@ if (WIN32) target_link_libraries(XS ${PERL_LIBRARY}) endif() -target_link_libraries(XS ${Boost_LIBRARIES}) -target_link_libraries(XS ${TBB_LIBRARIES}) -# target_link_libraries(XS ${wxWidgets_LIBRARIES}) -target_link_libraries(XS ${EXPAT_LIBRARIES}) -# target_link_libraries(XS ${GLEW_LIBRARIES}) - # Install the XS.pm and XS.{so,dll,bundle} into the local-lib directory. set(PERL_LOCAL_LIB_DIR "../../local-lib/lib/perl5/${PerlEmbed_ARCHNAME}") add_custom_command( @@ -181,10 +173,6 @@ if(APPLE) ) endif() -if(SLIC3R_PROFILE) - target_link_libraries(Shiny) -endif() - if (MSVC) # Here we associate some additional properties with the MSVC project to enable compilation and debugging out of the box. get_filename_component(PROPS_PERL_BIN_PATH "${PERL_EXECUTABLE}" DIRECTORY) diff --git a/xs/src/xsinit.h b/xs/src/xsinit.h index 506ef0a0be..f14e1262dc 100644 --- a/xs/src/xsinit.h +++ b/xs/src/xsinit.h @@ -69,7 +69,10 @@ extern "C" { #undef fwrite #undef fclose #undef sleep + #undef snprintf + #undef strerror #undef test + #undef times #undef accept #undef wait diff --git a/xs/t/03_point.t b/xs/t/03_point.t index cb71f68f5f..c950998fbb 100644 --- a/xs/t/03_point.t +++ b/xs/t/03_point.t @@ -44,7 +44,7 @@ ok !$point->coincides_with($point2), 'coincides_with'; { my $line = Slic3r::Line->new([50,50], [125,-25]); - is +Slic3r::Point->new(100,0)->distance_to_line($line), 0, 'distance_to_line()'; + cmp_ok(abs(Slic3r::Point->new(100,0)->distance_to_line($line)), '<=', 4e-15, 'distance_to_line()'); } { diff --git a/xs/t/15_config.t b/xs/t/15_config.t index 397f7e4796..1f9fc939bc 100644 --- a/xs/t/15_config.t +++ b/xs/t/15_config.t @@ -9,19 +9,19 @@ use Test::More tests => 147; foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintConfig) { $config->set('layer_height', 0.3); ok abs($config->get('layer_height') - 0.3) < 1e-4, 'set/get float'; - is $config->serialize('layer_height'), '0.3', 'serialize float'; + is $config->opt_serialize('layer_height'), '0.3', 'serialize float'; $config->set('perimeters', 2); is $config->get('perimeters'), 2, 'set/get int'; - is $config->serialize('perimeters'), '2', 'serialize int'; + is $config->opt_serialize('perimeters'), '2', 'serialize int'; $config->set('extrusion_axis', 'A'); is $config->get('extrusion_axis'), 'A', 'set/get string'; - is $config->serialize('extrusion_axis'), 'A', 'serialize string'; + is $config->opt_serialize('extrusion_axis'), 'A', 'serialize string'; $config->set('notes', "foo\nbar"); is $config->get('notes'), "foo\nbar", 'set/get string with newline'; - is $config->serialize('notes'), 'foo\nbar', 'serialize string with newline'; + is $config->opt_serialize('notes'), 'foo\nbar', 'serialize string with newline'; $config->set_deserialize('notes', 'bar\nbaz'); is $config->get('notes'), "bar\nbaz", 'deserialize string with newline'; @@ -59,7 +59,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo ) { $config->set('filament_notes', $test_data->{values}); - is $config->serialize('filament_notes'), $test_data->{serialized}, 'serialize multi-string value ' . $test_data->{name}; + is $config->opt_serialize('filament_notes'), $test_data->{serialized}, 'serialize multi-string value ' . $test_data->{name}; $config->set_deserialize('filament_notes', ''); is_deeply $config->get('filament_notes'), [], 'deserialize multi-string value - empty ' . $test_data->{name}; $config->set_deserialize('filament_notes', $test_data->{serialized}); @@ -68,12 +68,12 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo $config->set('first_layer_height', 0.3); ok abs($config->get('first_layer_height') - 0.3) < 1e-4, 'set/get absolute floatOrPercent'; - is $config->serialize('first_layer_height'), '0.3', 'serialize absolute floatOrPercent'; + is $config->opt_serialize('first_layer_height'), '0.3', 'serialize absolute floatOrPercent'; $config->set('first_layer_height', '50%'); $config->get_abs_value('first_layer_height'); ok abs($config->get_abs_value('first_layer_height') - 0.15) < 1e-4, 'set/get relative floatOrPercent'; - is $config->serialize('first_layer_height'), '50%', 'serialize relative floatOrPercent'; + is $config->opt_serialize('first_layer_height'), '50%', 'serialize relative floatOrPercent'; # Uh-oh, we have no point option to test at the moment #ok $config->set('print_center', [50,80]), 'valid point coordinates'; @@ -86,11 +86,10 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo $config->set('use_relative_e_distances', 1); is $config->get('use_relative_e_distances'), 1, 'set/get bool'; - is $config->serialize('use_relative_e_distances'), '1', 'serialize bool'; - + is $config->opt_serialize('use_relative_e_distances'), '1', 'serialize bool'; $config->set('gcode_flavor', 'teacup'); is $config->get('gcode_flavor'), 'teacup', 'set/get enum'; - is $config->serialize('gcode_flavor'), 'teacup', 'serialize enum'; + is $config->opt_serialize('gcode_flavor'), 'teacup', 'serialize enum'; $config->set_deserialize('gcode_flavor', 'mach3'); is $config->get('gcode_flavor'), 'mach3', 'deserialize enum (gcode_flavor)'; $config->set_deserialize('gcode_flavor', 'machinekit'); @@ -106,7 +105,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [[10,20],[30,45]], 'set/get points'; $config->set('extruder_offset', [Slic3r::Pointf->new(10,20),Slic3r::Pointf->new(30,45)]); is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [[10,20],[30,45]], 'set/get points'; - is $config->serialize('extruder_offset'), '10x20,30x45', 'serialize points'; + is $config->opt_serialize('extruder_offset'), '10x20,30x45', 'serialize points'; $config->set_deserialize('extruder_offset', '20x10'); is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [[20,10]], 'deserialize points'; $config->set_deserialize('extruder_offset', '0x0'); @@ -120,7 +119,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo # truncate ->get() to first decimal digit $config->set('nozzle_diameter', [0.2,3]); is_deeply [ map int($_*10)/10, @{$config->get('nozzle_diameter')} ], [0.2,3], 'set/get floats'; - is $config->serialize('nozzle_diameter'), '0.2,3', 'serialize floats'; + is $config->opt_serialize('nozzle_diameter'), '0.2,3', 'serialize floats'; $config->set_deserialize('nozzle_diameter', '0.1,0.4'); is_deeply [ map int($_*10)/10, @{$config->get('nozzle_diameter')} ], [0.1,0.4], 'deserialize floats'; $config->set_deserialize('nozzle_diameter', '3'); @@ -133,7 +132,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo $config->set('temperature', [180,210]); is_deeply $config->get('temperature'), [180,210], 'set/get ints'; - is $config->serialize('temperature'), '180,210', 'serialize ints'; + is $config->opt_serialize('temperature'), '180,210', 'serialize ints'; $config->set_deserialize('temperature', '195,220'); is_deeply $config->get('temperature'), [195,220], 'deserialize ints'; { @@ -147,7 +146,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo is $config->get_at('wipe', 0), 1, 'get_at bools'; is $config->get_at('wipe', 1), 0, 'get_at bools'; is $config->get_at('wipe', 9), 1, 'get_at bools'; - is $config->serialize('wipe'), '1,0', 'serialize bools'; + is $config->opt_serialize('wipe'), '1,0', 'serialize bools'; $config->set_deserialize('wipe', '0,1,1'); is_deeply $config->get('wipe'), [0,1,1], 'deserialize bools'; $config->set_deserialize('wipe', ''); @@ -162,7 +161,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo $config->set('post_process', ['foo','bar']); is_deeply $config->get('post_process'), ['foo','bar'], 'set/get strings'; - is $config->serialize('post_process'), 'foo;bar', 'serialize strings'; + is $config->opt_serialize('post_process'), 'foo;bar', 'serialize strings'; $config->set_deserialize('post_process', 'bar;baz'); is_deeply $config->get('post_process'), ['bar','baz'], 'deserialize strings'; { diff --git a/xs/t/19_model.t b/xs/t/19_model.t index d6f6d97a18..48a000e463 100644 --- a/xs/t/19_model.t +++ b/xs/t/19_model.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 4; +use Test::More tests => 3; { my $model = Slic3r::Model->new; @@ -14,9 +14,9 @@ use Test::More tests => 4; $object->origin_translation->translate(10,0,0); is_deeply \@{$object->origin_translation}, [10,0,0], 'origin_translation is modified by ref'; - my $lhr = [ [ 5, 10, 0.1 ] ]; - $object->set_layer_height_ranges($lhr); - is_deeply $object->layer_height_ranges, $lhr, 'layer_height_ranges roundtrip'; +# my $lhr = [ [ 5, 10, 0.1 ] ]; +# $object->set_layer_height_ranges($lhr); +# is_deeply $object->layer_height_ranges, $lhr, 'layer_height_ranges roundtrip'; } __END__ diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index 4d48a2c6f1..f5e6ffb05e 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -33,7 +33,7 @@ %code{% RETVAL = ConfigBase__set_deserialize(THIS, opt_key, str); %}; void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false) %code{% ConfigBase__set_ifndef(THIS, opt_key, value, deserialize); %}; - std::string serialize(t_config_option_key opt_key); + std::string opt_serialize(t_config_option_key opt_key); double get_abs_value(t_config_option_key opt_key); %name{get_abs_value_over} double get_abs_value(t_config_option_key opt_key, double ratio_over); @@ -49,7 +49,7 @@ void erase(t_config_option_key opt_key); void normalize(); %name{setenv} void setenv_(); - double min_object_distance() %code{% RETVAL = PrintConfig::min_object_distance(THIS); %}; + double min_object_distance() %code{% PrintConfig cfg; cfg.apply(*THIS, true); RETVAL = cfg.min_object_distance(); %}; static DynamicPrintConfig* load(char *path) %code%{ auto config = new DynamicPrintConfig(); @@ -95,7 +95,7 @@ %code{% RETVAL = ConfigBase__set_deserialize(THIS, opt_key, str); %}; void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false) %code{% ConfigBase__set_ifndef(THIS, opt_key, value, deserialize); %}; - std::string serialize(t_config_option_key opt_key); + std::string opt_serialize(t_config_option_key opt_key); double get_abs_value(t_config_option_key opt_key); %name{get_abs_value_over} double get_abs_value(t_config_option_key opt_key, double ratio_over); diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index a1c8890ef5..35b1c01ce8 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -206,16 +206,12 @@ ModelMaterial::attributes() Ref model() %code%{ RETVAL = THIS->get_model(); %}; - t_layer_height_ranges layer_height_ranges() - %code%{ RETVAL = THIS->layer_height_ranges; %}; - void set_layer_height_ranges(t_layer_height_ranges ranges) - %code%{ THIS->layer_height_ranges = ranges; %}; - Ref origin_translation() %code%{ RETVAL = &THIS->origin_translation; %}; void set_origin_translation(Vec3d* point) %code%{ THIS->origin_translation = *point; %}; + void ensure_on_bed(); bool needed_repair() const; int materials_count() const; int facets_count(); @@ -253,7 +249,7 @@ ModelMaterial::attributes() Ref config() %code%{ RETVAL = &THIS->config; %}; Ref mesh() - %code%{ RETVAL = &THIS->mesh; %}; + %code%{ RETVAL = &THIS->mesh(); %}; bool modifier() %code%{ RETVAL = THIS->is_modifier(); %}; diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index c35f967f87..74c0f4b0ce 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -70,10 +70,12 @@ _constant() Print(); ~Print(); + Ref model() + %code%{ RETVAL = const_cast(&THIS->model()); %}; Ref config() %code%{ RETVAL = const_cast(static_cast(&THIS->config())); %}; Ref placeholder_parser() - %code%{ RETVAL = &THIS->placeholder_parser(); %}; + %code%{ RETVAL = const_cast(&THIS->placeholder_parser()); %}; Ref skirt() %code%{ RETVAL = const_cast(&THIS->skirt()); %}; Ref brim() @@ -100,7 +102,6 @@ _constant() %code%{ RETVAL = const_cast(&THIS->objects()); %}; Ref get_object(int idx) %code%{ RETVAL = THIS->objects()[idx]; %}; - void reload_object(int idx); size_t object_count() %code%{ RETVAL = THIS->objects().size(); %}; @@ -141,9 +142,8 @@ _constant() } %}; - void add_model_object(ModelObject* model_object, int idx = -1); - bool apply_config_perl_tests_only(DynamicPrintConfig* config) - %code%{ RETVAL = THIS->apply_config_perl_tests_only(*config); %}; + bool apply(Model *model, DynamicPrintConfig* config) + %code%{ RETVAL = THIS->apply(*model, *config); %}; bool has_infinite_skirt(); std::vector extruders() const; int validate() %code%{ diff --git a/xs/xsp/TriangleMesh.xsp b/xs/xsp/TriangleMesh.xsp index e519f9210c..f3153665cb 100644 --- a/xs/xsp/TriangleMesh.xsp +++ b/xs/xsp/TriangleMesh.xsp @@ -46,7 +46,6 @@ TriangleMesh::ReadFromPerl(vertices, facets) SV* facets CODE: stl_file &stl = THIS->stl; - stl.error = 0; stl.stats.type = inmemory; // count facets and allocate memory @@ -99,20 +98,18 @@ SV* TriangleMesh::vertices() CODE: if (!THIS->repaired) CONFESS("vertices() requires repair()"); - - if (THIS->stl.v_shared == NULL) - stl_generate_shared_vertices(&(THIS->stl)); + THIS->require_shared_vertices(); // vertices AV* vertices = newAV(); - av_extend(vertices, THIS->stl.stats.shared_vertices); - for (int i = 0; i < THIS->stl.stats.shared_vertices; i++) { + av_extend(vertices, THIS->its.vertices.size()); + for (size_t i = 0; i < THIS->its.vertices.size(); i++) { AV* vertex = newAV(); av_store(vertices, i, newRV_noinc((SV*)vertex)); av_extend(vertex, 2); - av_store(vertex, 0, newSVnv(THIS->stl.v_shared[i](0))); - av_store(vertex, 1, newSVnv(THIS->stl.v_shared[i](1))); - av_store(vertex, 2, newSVnv(THIS->stl.v_shared[i](2))); + av_store(vertex, 0, newSVnv(THIS->its.vertices[i](0))); + av_store(vertex, 1, newSVnv(THIS->its.vertices[i](1))); + av_store(vertex, 2, newSVnv(THIS->its.vertices[i](2))); } RETVAL = newRV_noinc((SV*)vertices); @@ -123,9 +120,7 @@ SV* TriangleMesh::facets() CODE: if (!THIS->repaired) CONFESS("facets() requires repair()"); - - if (THIS->stl.v_shared == NULL) - stl_generate_shared_vertices(&(THIS->stl)); + THIS->require_shared_vertices(); // facets AV* facets = newAV(); @@ -134,9 +129,9 @@ TriangleMesh::facets() AV* facet = newAV(); av_store(facets, i, newRV_noinc((SV*)facet)); av_extend(facet, 2); - av_store(facet, 0, newSVnv(THIS->stl.v_indices[i].vertex[0])); - av_store(facet, 1, newSVnv(THIS->stl.v_indices[i].vertex[1])); - av_store(facet, 2, newSVnv(THIS->stl.v_indices[i].vertex[2])); + av_store(facet, 0, newSVnv(THIS->its.indices[i][0])); + av_store(facet, 1, newSVnv(THIS->its.indices[i][1])); + av_store(facet, 2, newSVnv(THIS->its.indices[i][2])); } RETVAL = newRV_noinc((SV*)facets);