diff --git a/CMakeLists.txt b/CMakeLists.txt index 98ae4f0c68..9a5fc83870 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -146,21 +146,33 @@ 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() + if(NOT DEFINED WIN10SDK_PATH) + if(DEFINED ENV{WIN10SDK_PATH}) + set(WIN10SDK_PATH "$ENV{WIN10SDK_PATH}") 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(DEFINED WIN10SDK_PATH) + if (EXISTS "${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h") + set(WIN10SDK_INCLUDE_PATH "${WIN10SDK_PATH}/Include") + else() + 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) + else() + # Try to use the default Windows 10 SDK path. + set(WIN10SDK_INCLUDE_PATH "$ENV{WindowsSdkDir}/Include/$ENV{WindowsSDKVersion}") + if (NOT EXISTS "${WIN10SDK_INCLUDE_PATH}/winrt/windows.graphics.printing3d.h") + message("${WIN10SDK_INCLUDE_PATH}/winrt/windows.graphics.printing3d.h was not found") + message("STL fixing by the Netfabb service will not be compiled") + unset(WIN10SDK_INCLUDE_PATH) + endif() + endif() + if(WIN10SDK_INCLUDE_PATH) message("Building with Win10 Netfabb STL fixing service support") add_definitions(-DHAS_WIN10SDK) - include_directories("${WIN10SDK_PATH}/Include") + include_directories("${WIN10SDK_INCLUDE_PATH}") else() message("Building without Win10 Netfabb STL fixing service support") endif() @@ -226,17 +238,23 @@ if (NOT MSVC AND ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMP add_compile_options(-Wno-unknown-pragmas) endif() - if (SLIC3R_ASAN) - add_compile_options(-fsanitize=address -fno-omit-frame-pointer) +endif() + +if (SLIC3R_ASAN) + # ASAN should be available on MSVC starting with Visual Studio 2019 16.9 + # https://devblogs.microsoft.com/cppblog/address-sanitizer-for-msvc-now-generally-available/ + add_compile_options(-fsanitize=address -fno-omit-frame-pointer) + + if (NOT MSVC) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fsanitize=address") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -fsanitize=address") - - if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lasan") - endif () endif () -endif() + + if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lasan") + endif () +endif () if (APPLE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror=partial-availability -Werror=unguarded-availability -Werror=unguarded-availability-new") @@ -300,7 +318,7 @@ endif() # boost::process was introduced first in version 1.64.0, # boost::beast::detail::base64 was introduced first in version 1.66.0 set(MINIMUM_BOOST_VERSION "1.66.0") -set(_boost_components "system;filesystem;thread;log;locale;regex;chrono;atomic;date_time") +set(_boost_components "system;filesystem;thread;log;locale;regex;chrono;atomic;date_time;iostreams") find_package(Boost ${MINIMUM_BOOST_VERSION} REQUIRED COMPONENTS ${_boost_components}) add_library(boost_libs INTERFACE) diff --git a/deps/deps-linux.cmake b/deps/deps-linux.cmake index 35522504ca..c81d6785db 100644 --- a/deps/deps-linux.cmake +++ b/deps/deps-linux.cmake @@ -13,7 +13,7 @@ include("deps-unix-common.cmake") ExternalProject_Add(dep_boost EXCLUDE_FROM_ALL 1 - URL "https://dl.bintray.com/boostorg/release/1.75.0/source/boost_1_75_0.tar.gz" + URL "https://boostorg.jfrog.io/artifactory/main/release/1.75.0/source/boost_1_75_0.tar.gz" URL_HASH SHA256=aeb26f80e80945e82ee93e5939baebdca47b9dee80a07d3144be1e1a6a66dd6a BUILD_IN_SOURCE 1 CONFIGURE_COMMAND ./bootstrap.sh @@ -100,5 +100,5 @@ ExternalProject_Add(dep_libcurl BUILD_COMMAND make "-j${NPROC}" INSTALL_COMMAND make install "DESTDIR=${DESTDIR}" ) - add_dependencies(dep_openvdb dep_boost) + diff --git a/deps/deps-macos.cmake b/deps/deps-macos.cmake index 53ba008c3e..de77dafa80 100644 --- a/deps/deps-macos.cmake +++ b/deps/deps-macos.cmake @@ -18,7 +18,7 @@ include("deps-unix-common.cmake") ExternalProject_Add(dep_boost EXCLUDE_FROM_ALL 1 - URL "https://dl.bintray.com/boostorg/release/1.75.0/source/boost_1_75_0.tar.gz" + URL "https://boostorg.jfrog.io/artifactory/main/release/1.75.0/source/boost_1_75_0.tar.gz" URL_HASH SHA256=aeb26f80e80945e82ee93e5939baebdca47b9dee80a07d3144be1e1a6a66dd6a BUILD_IN_SOURCE 1 CONFIGURE_COMMAND ./bootstrap.sh @@ -87,5 +87,5 @@ ExternalProject_Add(dep_libcurl BUILD_COMMAND make "-j${NPROC}" INSTALL_COMMAND make install "DESTDIR=${DESTDIR}" ) +add_dependencies(dep_openvdb dep_boost) -add_dependencies(dep_openvdb dep_boost) \ No newline at end of file diff --git a/deps/deps-mingw.cmake b/deps/deps-mingw.cmake index c97346bb03..cae7fc3719 100644 --- a/deps/deps-mingw.cmake +++ b/deps/deps-mingw.cmake @@ -9,7 +9,7 @@ include("deps-unix-common.cmake") ExternalProject_Add(dep_boost EXCLUDE_FROM_ALL 1 - URL "https://dl.bintray.com/boostorg/release/1.75.0/source/boost_1_75_0.tar.gz" + URL "https://boostorg.jfrog.io/artifactory/main/release/1.75.0/source/boost_1_75_0.tar.gz" URL_HASH SHA256=aeb26f80e80945e82ee93e5939baebdca47b9dee80a07d3144be1e1a6a66dd6a BUILD_IN_SOURCE 1 CONFIGURE_COMMAND bootstrap.bat @@ -58,4 +58,5 @@ ExternalProject_Add(dep_libcurl -DCURL_DISABLE_GOPHER=ON -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local ${DEP_CMAKE_OPTS} -) \ No newline at end of file +) + diff --git a/deps/deps-windows.cmake b/deps/deps-windows.cmake index c0d80bb291..fc9f55f5f4 100644 --- a/deps/deps-windows.cmake +++ b/deps/deps-windows.cmake @@ -55,7 +55,7 @@ endmacro() ExternalProject_Add(dep_boost EXCLUDE_FROM_ALL 1 - URL "https://dl.bintray.com/boostorg/release/1.75.0/source/boost_1_75_0.tar.gz" + URL "https://boostorg.jfrog.io/artifactory/main/release/1.75.0/source/boost_1_75_0.tar.gz" URL_HASH SHA256=aeb26f80e80945e82ee93e5939baebdca47b9dee80a07d3144be1e1a6a66dd6a BUILD_IN_SOURCE 1 CONFIGURE_COMMAND bootstrap.bat @@ -295,4 +295,5 @@ if (${DEP_DEBUG}) COMMAND msbuild /m /P:Configuration=Debug INSTALL.vcxproj WORKING_DIRECTORY "${BINARY_DIR}" ) -endif () \ No newline at end of file +endif () + diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake index bf23698cf4..0c8eaca97e 100644 --- a/deps/wxWidgets/wxWidgets.cmake +++ b/deps/wxWidgets/wxWidgets.cmake @@ -1,11 +1,4 @@ -if (APPLE) - # The new OSX 11 (Big Sur) is not compatible with wxWidgets 3.1.3. - # Let's use patched wxWidgets 3.1.4, even though it is not quite tested. - set(_wx_git_tag v3.1.4-patched) -else () - # Use the tested patched wxWidgets 3.1.3 everywhere else. - set(_wx_git_tag v3.1.3-patched) -endif () +set(_wx_git_tag v3.1.4-patched) # set(_patch_command "") set(_wx_toolkit "") @@ -27,6 +20,7 @@ prusaslicer_add_cmake_project(wxWidgets ${_wx_toolkit} "-DCMAKE_DEBUG_POSTFIX:STRING=" -DwxBUILD_DEBUG_LEVEL=0 + -DwxUSE_MEDIACTRL=OFF -DwxUSE_DETECT_SM=OFF -DwxUSE_UNICODE=ON -DwxUSE_OPENGL=ON diff --git a/resources/icons/fuzzy_skin.svg b/resources/icons/fuzzy_skin.svg new file mode 100644 index 0000000000..b8ba0a6513 --- /dev/null +++ b/resources/icons/fuzzy_skin.svg @@ -0,0 +1,8 @@ + + + + + + + diff --git a/resources/icons/info.png b/resources/icons/info.png new file mode 100644 index 0000000000..9eeee9b3cd Binary files /dev/null and b/resources/icons/info.png differ diff --git a/resources/icons/notification_cancel.svg b/resources/icons/notification_cancel.svg new file mode 100644 index 0000000000..d849e24c61 --- /dev/null +++ b/resources/icons/notification_cancel.svg @@ -0,0 +1,67 @@ + +image/svg+xml + + + + + + + diff --git a/resources/icons/notification_cancel_hover.svg b/resources/icons/notification_cancel_hover.svg new file mode 100644 index 0000000000..746d053e48 --- /dev/null +++ b/resources/icons/notification_cancel_hover.svg @@ -0,0 +1,67 @@ + +image/svg+xml + + + + + + + diff --git a/resources/localization/list.txt b/resources/localization/list.txt index 64d50591a7..a2618b44e2 100644 --- a/resources/localization/list.txt +++ b/resources/localization/list.txt @@ -28,6 +28,7 @@ src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp src/slic3r/GUI/GUI.cpp src/slic3r/GUI/GUI_App.cpp src/slic3r/GUI/GUI_Init.cpp +src/slic3r/GUI/GUI_Factories.cpp src/slic3r/GUI/GUI_ObjectLayers.cpp src/slic3r/GUI/GUI_ObjectList.cpp src/slic3r/GUI/GUI_ObjectManipulation.cpp diff --git a/resources/profiles/Anycubic.idx b/resources/profiles/Anycubic.idx index b57fe3e660..24a881f303 100644 --- a/resources/profiles/Anycubic.idx +++ b/resources/profiles/Anycubic.idx @@ -1,3 +1,5 @@ +min_slic3r_version = 2.3.1-beta +0.0.9 Updated bed textures min_slic3r_version = 2.3.0-beta2 0.0.8 Updated start and end g-code for Anycubic Mega. 0.0.7 Updated start g-code for Anycubic Mega. diff --git a/resources/profiles/Anycubic.ini b/resources/profiles/Anycubic.ini index 639d5df475..44308abc89 100644 --- a/resources/profiles/Anycubic.ini +++ b/resources/profiles/Anycubic.ini @@ -5,7 +5,7 @@ name = Anycubic # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the PrusaSlicer configuration to be downgraded. -config_version = 0.0.8 +config_version = 0.0.9 # Where to get the updates from? config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Anycubic/ # changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% diff --git a/resources/profiles/Artillery.idx b/resources/profiles/Artillery.idx index eca18b8b44..d1e3657728 100644 --- a/resources/profiles/Artillery.idx +++ b/resources/profiles/Artillery.idx @@ -1,2 +1,2 @@ -min_slic3r_version = 2.3.0 +min_slic3r_version = 2.3.1-beta 0.0.1 Initial Artillery bundle diff --git a/resources/profiles/Artillery/genius_thumbnail.png b/resources/profiles/Artillery/Genius_thumbnail.png similarity index 100% rename from resources/profiles/Artillery/genius_thumbnail.png rename to resources/profiles/Artillery/Genius_thumbnail.png diff --git a/resources/profiles/Artillery/x1_thumbnail.png b/resources/profiles/Artillery/X1_thumbnail.png similarity index 100% rename from resources/profiles/Artillery/x1_thumbnail.png rename to resources/profiles/Artillery/X1_thumbnail.png diff --git a/resources/profiles/Creality.idx b/resources/profiles/Creality.idx index 328ae4cf33..c1242f27ec 100644 --- a/resources/profiles/Creality.idx +++ b/resources/profiles/Creality.idx @@ -1,3 +1,6 @@ +min_slic3r_version = 2.3.1-beta +0.0.16 Updated CR6-SE start g-code. Added and updated filament profiles. +0.0.15 Added new printer models, filament profiles. Various improvements. min_slic3r_version = 2.3.0-rc2 0.0.14 Optimized start g-code. Added filament profile. Various improvements. 0.0.13 Optimized start and end g-code. General improvements. diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini index 1426ee5963..5248155250 100644 --- a/resources/profiles/Creality.ini +++ b/resources/profiles/Creality.ini @@ -5,7 +5,7 @@ name = Creality # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the PrusaSlicer configuration to be downgraded. -config_version = 0.0.14 +config_version = 0.0.16 # Where to get the updates from? config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Creality/ # changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% @@ -21,7 +21,7 @@ technology = FFF family = ENDER bed_model = ender3_bed.stl bed_texture = ender3.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER3BLTOUCH] name = Creality Ender-3 BLTouch @@ -30,7 +30,7 @@ technology = FFF family = ENDER bed_model = ender3_bed.stl bed_texture = ender3.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER3V2] name = Creality Ender-3 V2 @@ -39,25 +39,25 @@ technology = FFF family = ENDER bed_model = ender3v2_bed.stl bed_texture = ender3v2.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY -#[printer_model:ENDER3MAX] -#name = Creality Ender-3 Max -#variants = 0.4 -#technology = FFF -#family = ENDER -#bed_model = cr10v2_bed.stl -#bed_texture = cr10spro.svg -#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +[printer_model:ENDER3MAX] +name = Creality Ender-3 Max +variants = 0.4 +technology = FFF +family = ENDER +bed_model = cr10v2_bed.stl +bed_texture = cr10spro.svg +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY -#[printer_model:ENDER4] -#name = Creality Ender-4 -#variants = 0.4 -#technology = FFF -#family = ENDER -#bed_model = ender3v2_bed.stl -#bed_texture = ender3v2.svg -#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +[printer_model:ENDER4] +name = Creality Ender-4 +variants = 0.4 +technology = FFF +family = ENDER +bed_model = ender3v2_bed.stl +bed_texture = ender3v2.svg +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER5] name = Creality Ender-5 @@ -66,7 +66,7 @@ technology = FFF family = ENDER bed_model = ender3_bed.stl bed_texture = ender3.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER5PLUS] name = Creality Ender-5 Plus @@ -75,16 +75,16 @@ technology = FFF family = ENDER bed_model = ender5plus_bed.stl bed_texture = ender5plus.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY -#[printer_model:ENDER6] -#name = Creality Ender-6 -#variants = 0.4 -#technology = FFF -#family = ENDER -#bed_model = ender6_bed.stl -#bed_texture = ender6.svg -#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +[printer_model:ENDER6] +name = Creality Ender-6 +variants = 0.4 +technology = FFF +family = ENDER +bed_model = ender6_bed.stl +bed_texture = ender6.svg +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER2] name = Creality Ender-2 @@ -93,25 +93,43 @@ technology = FFF family = ENDER bed_model = ender2_bed.stl bed_texture = ender2.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY -#[printer_model:CR6SE] -#name = Creality CR-6 SE -#variants = 0.4 -#technology = FFF -#family = CR -#bed_model = cr6se_bed.stl -#bed_texture = cr6se.svg -#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +[printer_model:CR5PRO] +name = Creality CR-5 Pro +variants = 0.4 +technology = FFF +family = CR +bed_model = cr5pro_bed.stl +bed_texture = cr5pro.svg +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY -#[printer_model:CR6MAX] -#name = Creality CR-6 Max -#variants = 0.4 -#technology = FFF -#family = CR -#bed_model = cr10s4_bed.stl -#bed_texture = cr10s4.svg -#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +[printer_model:CR5PROH] +name = Creality CR-5 Pro H +variants = 0.4 +technology = FFF +family = CR +bed_model = cr5pro_bed.stl +bed_texture = cr5pro.svg +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY + +[printer_model:CR6SE] +name = Creality CR-6 SE +variants = 0.4 +technology = FFF +family = CR +bed_model = cr6se_bed.stl +bed_texture = cr6se.svg +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY + +[printer_model:CR6MAX] +name = Creality CR-6 Max +variants = 0.4 +technology = FFF +family = CR +bed_model = cr10s4_bed.stl +bed_texture = cr10s4.svg +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10MINI] name = Creality CR-10 Mini @@ -120,16 +138,16 @@ technology = FFF family = CR bed_model = cr10mini_bed.stl bed_texture = cr10mini.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY -#[printer_model:CR10MAX] -#name = Creality CR-10 Max -#variants = 0.4 -#technology = FFF -#family = CR -#bed_model = cr10max_bed.stl -#bed_texture = cr10max.svg -#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +[printer_model:CR10MAX] +name = Creality CR-10 Max +variants = 0.4 +technology = FFF +family = CR +bed_model = cr10max_bed.stl +bed_texture = cr10max.svg +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10] name = Creality CR-10 @@ -138,7 +156,7 @@ technology = FFF family = CR bed_model = cr10_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10V2] name = Creality CR-10 V2 @@ -147,7 +165,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10V3] name = Creality CR-10 V3 @@ -156,7 +174,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10S] name = Creality CR-10 S @@ -165,7 +183,7 @@ technology = FFF family = CR bed_model = cr10_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10SPRO] name = Creality CR-10 S Pro @@ -174,7 +192,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10spro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10SPROV2] name = Creality CR-10 S Pro V2 @@ -183,7 +201,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10S4] name = Creality CR-10 S4 @@ -192,7 +210,7 @@ technology = FFF family = CR bed_model = cr10s4_bed.stl bed_texture = cr10s4.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10S5] name = Creality CR-10 S5 @@ -201,7 +219,7 @@ technology = FFF family = CR bed_model = cr10s5_bed.stl bed_texture = cr10s5.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR20] name = Creality CR-20 @@ -210,7 +228,7 @@ technology = FFF family = CR bed_model = ender3_bed.stl bed_texture = cr20.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR20PRO] name = Creality CR-20 Pro @@ -219,16 +237,25 @@ technology = FFF family = CR bed_model = ender3_bed.stl bed_texture = cr20.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY -#[printer_model:CR8] -#name = Creality CR-8 -#variants = 0.4 -#technology = FFF -#family = CR -#bed_model = cr8_bed.stl -#bed_texture = cr8.svg -#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +[printer_model:CR200B] +name = Creality CR-200B +variants = 0.4 +technology = FFF +family = CR +bed_model = cr200b_bed.stl +bed_texture = cr200b.svg +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY + +[printer_model:CR8] +name = Creality CR-8 +variants = 0.4 +technology = FFF +family = CR +bed_model = cr8_bed.stl +bed_texture = cr8.svg +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY #[printer_model:CRX] #name = Creality CR-X @@ -237,7 +264,7 @@ default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @ #family = CR-X #bed_model = cr10v2_bed.stl #bed_texture = cr10spro.svg -#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY #[printer_model:CRXPRO] #name = Creality CR-X Pro @@ -246,7 +273,7 @@ default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @ #family = CR-X #bed_model = cr10v2_bed.stl #bed_texture = cr10spro.svg -#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY # All presets starting with asterisk, for example *common*, are intermediate and they will # not make it into the user interface. @@ -254,6 +281,7 @@ default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @ # Common print preset [print:*common*] avoid_crossing_perimeters = 0 +bridge_acceleration = 250 bridge_angle = 0 bridge_flow_ratio = 0.95 bridge_speed = 25 @@ -261,6 +289,7 @@ brim_width = 0 clip_multipart_objects = 1 compatible_printers = complete_objects = 0 +default_acceleration = 500 dont_support_bridges = 1 elefant_foot_compensation = 0.1 ensure_vertical_shell_thickness = 1 @@ -269,11 +298,11 @@ external_perimeters_first = 0 external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 25 extra_perimeters = 0 -extruder_clearance_height = 25 -extruder_clearance_radius = 45 +extruder_clearance_height = 34 +extruder_clearance_radius = 47 extrusion_width = 0.45 fill_angle = 45 -fill_density = 20% +fill_density = 15% fill_pattern = grid first_layer_extrusion_width = 0.42 first_layer_height = 0.2 @@ -307,9 +336,9 @@ print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest -single_extruder_multi_material_priming = 1 +single_extruder_multi_material_priming = 0 skirts = 1 -skirt_distance = 2 +skirt_distance = 3 skirt_height = 2 small_perimeter_speed = 25 solid_infill_below_area = 0 @@ -335,7 +364,7 @@ support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 40 support_material_synchronize_layers = 0 -support_material_threshold = 45 +support_material_threshold = 40 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 @@ -356,13 +385,15 @@ layer_height = 0.08 perimeters = 3 bottom_solid_layers = 9 top_solid_layers = 11 +bridge_flow_ratio = 0.70 [print:*0.10mm*] inherits = *common* -layer_height = 0.1 +layer_height = 0.10 perimeters = 3 bottom_solid_layers = 7 top_solid_layers = 9 +bridge_flow_ratio = 0.70 [print:*0.12mm*] inherits = *common* @@ -370,12 +401,14 @@ layer_height = 0.12 perimeters = 3 bottom_solid_layers = 6 top_solid_layers = 7 +bridge_flow_ratio = 0.70 [print:*0.16mm*] inherits = *common* layer_height = 0.16 bottom_solid_layers = 5 top_solid_layers = 7 +bridge_flow_ratio = 0.85 [print:*0.20mm*] inherits = *common* @@ -604,9 +637,11 @@ first_layer_bed_temperature = 60 filament_cost = 19.00 filament_density = 1.24 filament_colour = #FF0000 +filament_spool_weight = 256 -[filament:Devil Design PLA (Galaxy) @CREALITY] +[filament:Devil Design PLA Galaxy @CREALITY] inherits = *PLA* +renamed_from = "Devil Design PLA (Galaxy) @CREALITY" filament_vendor = Devil Design temperature = 225 bed_temperature = 65 @@ -615,6 +650,7 @@ first_layer_bed_temperature = 65 filament_cost = 19.00 filament_density = 1.24 filament_colour = #FF0000 +filament_spool_weight = 256 [filament:Extrudr PLA NX2 @CREALITY] inherits = *PLA* @@ -673,6 +709,40 @@ filament_density = 1.24 filament_colour = #125467 filament_spool_weight = 238 +[filament:3DJAKE ecoPLA Matt @CREALITY] +inherits = *PLA* +filament_vendor = 3DJAKE +temperature = 195 +bed_temperature = 60 +first_layer_temperature = 195 +first_layer_bed_temperature = 60 +filament_cost = 24.99 +filament_density = 1.38 +filament_colour = #125467 +filament_spool_weight = 238 + +[filament:3DJAKE ecoPLA Tough @CREALITY] +inherits = *PLA* +filament_vendor = 3DJAKE +temperature = 215 +bed_temperature = 60 +first_layer_temperature = 215 +first_layer_bed_temperature = 60 +filament_cost = 29.99 +filament_density = 1.21 +filament_colour = #125467 + +[filament:FormFutura Tough PLA @CREALITY] +inherits = *PLA* +filament_vendor = FormFutura +temperature = 215 +bed_temperature = 60 +first_layer_temperature = 215 +first_layer_bed_temperature = 60 +filament_cost = 46.65 +filament_density = 1.21 +filament_colour = #ed000e + [filament:123-3D Jupiter PLA @CREALITY] inherits = *PLA* filament_vendor = 123-3D @@ -712,7 +782,7 @@ printer_technology = FFF before_layer_gcode = ;BEFORE_LAYER_CHANGE\nG92 E0\n;[layer_z]\n\n between_objects_gcode = pause_print_gcode = -deretract_speed = 0 +deretract_speed = 40 extruder_colour = #FCE94F extruder_offset = 0x0 gcode_flavor = marlin @@ -735,23 +805,24 @@ machine_max_jerk_z = 0.4 machine_min_extruding_rate = 0 machine_min_travel_rate = 0 layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] -max_layer_height = 0.3 -min_layer_height = 0.07 +max_layer_height = 0.28 +min_layer_height = 0.08 max_print_height = 250 nozzle_diameter = 0.4 printer_notes = printer_settings_id = -retract_before_travel = 1 -retract_before_wipe = 0% +printer_variant = 0.4 +retract_before_travel = 2 +retract_before_wipe = 70% retract_layer_change = 1 -retract_length = 1 +retract_length = 5 retract_length_toolchange = 1 retract_lift = 0 retract_lift_above = 0 retract_lift_below = 0 retract_restart_extra = 0 retract_restart_extra_toolchange = 0 -retract_speed = 35 +retract_speed = 60 single_extruder_multi_material = 0 toolchange_gcode = use_firmware_retraction = 0 @@ -763,39 +834,6 @@ z_offset = 0 printer_model = default_print_profile = 0.16mm OPTIMAL @CREALITY default_filament_profile = Generic PLA @CREALITY - -[printer:Creality Ender-3] -inherits = *common* -renamed_from = "Creality ENDER-3" -printer_model = ENDER3 -printer_variant = 0.4 -max_layer_height = 0.28 -min_layer_height = 0.08 -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_CREALITY\nPRINTER_MODEL_ENDER3\nPRINTER_HAS_BOWDEN -bed_shape = 3x3,228x3,228x228,3x228 -max_print_height = 250 -machine_max_acceleration_e = 5000 -machine_max_acceleration_extruding = 500 -machine_max_acceleration_retracting = 1000 -machine_max_acceleration_x = 500 -machine_max_acceleration_y = 500 -machine_max_acceleration_z = 100 -machine_max_feedrate_e = 60 -machine_max_feedrate_x = 500 -machine_max_feedrate_y = 500 -machine_max_feedrate_z = 10 -machine_max_jerk_e = 5 -machine_max_jerk_x = 8 -machine_max_jerk_y = 8 -machine_max_jerk_z = 0.4 -machine_min_extruding_rate = 0 -machine_min_travel_rate = 0 -nozzle_diameter = 0.4 -retract_before_travel = 2 -retract_length = 5 -retract_speed = 60 -deretract_speed = 40 -retract_before_wipe = 70% start_gcode = G90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S120 ; set temporary nozzle temp to prevent oozing during homing and auto bed leveling\nM140 S[first_layer_bed_temperature] ; set final bed temp\nG4 S10 ; allow partial nozzle warmup\nG28 ; home all axis\nG1 Z50 F240\nG1 X2 Y10 F3000\nM104 S[first_layer_temperature] ; set final nozzle temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp to stabilize\nM109 S[first_layer_temperature] ; wait for nozzle temp to stabilize\nG1 Z0.28 F240\nG92 E0\nG1 Y140 E10 F1500 ; prime the nozzle\nG1 X2.3 F5000\nG92 E0\nG1 Y10 E10 F1200 ; prime the nozzle\nG92 E0 end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+2, max_print_height)} F600 ; Move print head up{endif}\nG1 X5 Y{print_bed_max[1]*0.8} F{travel_speed*60} ; present print\n{if max_layer_z < max_print_height-10}G1 Z{z_offset+min(max_layer_z+70, max_print_height-10)} F600 ; Move print head further up{endif}\n{if max_layer_z < max_print_height*0.6}G1 Z{max_print_height*0.6} F600 ; Move print head further up{endif}\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM107 ; turn off fan\nM84 X Y E ; disable motors @@ -813,7 +851,6 @@ pause_print_gcode = M25 ; pause print # Intended for printers where the Z-axis lowers the print bed during printing, like the Ender-5 series [printer:*descendingz*] -renamed_from = "*invertedz*" end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+2, max_print_height)} F600{endif} ; Move print bed down\nG1 X50 Y50 F{travel_speed*60} ; move print head out of the way\n{if max_layer_z < max_print_height-10}G1 Z{z_offset+max_print_height-10} F600{endif} ; Move print bed close to the bottom\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM107 ; turn off fan\nM84 X Y E ; disable motors # Intended for printers with dual extruders and a single hotend/nozzle, like the CR-X series @@ -842,34 +879,44 @@ retract_restart_extra_toolchange = 0,0 retract_speed = 60,60 wipe = 1,1 +[printer:Creality Ender-3] +inherits = *common* +renamed_from = "Creality ENDER-3" +bed_shape = 3x3,228x3,228x228,3x228 +max_print_height = 250 +printer_model = ENDER3 +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_CREALITY\nPRINTER_MODEL_ENDER3\nPRINTER_HAS_BOWDEN + [printer:Creality Ender-3 BLTouch] inherits = Creality Ender-3; *fastabl* renamed_from = "Creality ENDER-3 BLTouch" printer_model = ENDER3BLTOUCH [printer:Creality Ender-3 V2] -inherits = Creality Ender-3 +inherits = *common* renamed_from = "Creality Ender-3V2" -printer_model = ENDER3V2 bed_shape = 5x0,215x0,215x220,5x220 +max_print_height = 250 +printer_model = ENDER3V2 +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_CREALITY\nPRINTER_MODEL_ENDER3V2\nPRINTER_HAS_BOWDEN -#[printer:Creality Ender-3 Max] -#inherits = Creality Ender-3 -#retract_length = 6 -#bed_shape = 5x5,295x5,295x295,5x295 -#max_print_height = 340 -#printer_model = ENDER3MAX -#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_CREALITY\nPRINTER_MODEL_ENDER3MAX\nPRINTER_HAS_BOWDEN +[printer:Creality Ender-3 Max] +inherits = *common* +retract_length = 6 +bed_shape = 5x5,295x5,295x295,5x295 +max_print_height = 340 +printer_model = ENDER3MAX +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_CREALITY\nPRINTER_MODEL_ENDER3MAX\nPRINTER_HAS_BOWDEN -#[printer:Creality Ender-4] -#inherits = Creality Ender-3; *descendingz* -#bed_shape = 5x0,215x0,215x220,5x220 -#max_print_height = 300 -#printer_model = ENDER4 -#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_CREALITY\nPRINTER_MODEL_ENDER4\nPRINTER_HAS_BOWDEN +[printer:Creality Ender-4] +inherits = *common*; *descendingz* +bed_shape = 5x0,215x0,215x220,5x220 +max_print_height = 300 +printer_model = ENDER4 +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_CREALITY\nPRINTER_MODEL_ENDER4\nPRINTER_HAS_BOWDEN [printer:Creality Ender-5] -inherits = Creality Ender-3; *descendingz* +inherits = *common*; *descendingz* retract_length = 6 bed_shape = 5x2.5,225x2.5,225x222.5,5x222.5 max_print_height = 300 @@ -879,7 +926,7 @@ machine_max_acceleration_e = 1000 machine_max_feedrate_z = 5 [printer:Creality Ender-5 Plus] -inherits = Creality Ender-3; *slowabl*; *descendingz* +inherits = *common*; *slowabl*; *descendingz* retract_length = 6 bed_shape = 5x5,355x5,355x355,5x355 max_print_height = 400 @@ -890,53 +937,69 @@ machine_max_feedrate_z = 5 machine_max_feedrate_x = 300 machine_max_feedrate_y = 300 -#[printer:Creality Ender-6] -#inherits = Creality Ender-3; *descendingz* -#bed_shape = 5x5,255x5,255x255,5x255 -#max_print_height = 400 -#printer_model = ENDER6 -#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_CREALITY\nPRINTER_MODEL_ENDER6\nPRINTER_HAS_BOWDEN +[printer:Creality Ender-6] +inherits = *common*; *descendingz* +bed_shape = 5x5,255x5,255x255,5x255 +max_print_height = 400 +printer_model = ENDER6 +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_CREALITY\nPRINTER_MODEL_ENDER6\nPRINTER_HAS_BOWDEN [printer:Creality Ender-2] -inherits = Creality Ender-3 +inherits = *common* renamed_from = "Creality ENDER-2" bed_shape = 0x0,150x0,150x150,0x150 max_print_height = 200 printer_model = ENDER2 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_CREALITY\nPRINTER_MODEL_ENDER2\nPRINTER_HAS_BOWDEN -#[printer:Creality CR-6 SE] -#inherits = Creality Ender-3; *fastabl*; *pauseprint* -#bed_shape = 5x0,230x0,230x235,5x235 -#printer_model = CR6SE -#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_CREALITY\nPRINTER_MODEL_CR6SE\nPRINTER_HAS_BOWDEN +[printer:Creality CR-5 Pro] +inherits = *common*; *slowabl*; *descendingz* +retract_length = 6 +bed_shape = 5x5,295x5,295x220,5x220 +max_print_height = 380 +printer_model = CR5PRO +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_CREALITY\nPRINTER_MODEL_CR5PRO\nPRINTER_HAS_BOWDEN -#[printer:Creality CR-6 Max] -#inherits = Creality Ender-3; *slowabl* -#retract_length = 6 -#bed_shape = 5x5,395x5,395x395,5x395 -#max_print_height = 400 -#printer_model = CR6MAX -#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_CREALITY\nPRINTER_MODEL_CR6MAX\nPRINTER_HAS_BOWDEN +[printer:Creality CR-5 Pro H] +inherits = *common*; *slowabl*; *descendingz* +retract_length = 3 +bed_shape = 5x5,295x5,295x220,5x220 +max_print_height = 380 +printer_model = CR5PROH +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_CREALITY\nPRINTER_MODEL_CR5PROH\nPRINTER_HAS_BOWDEN + +[printer:Creality CR-6 SE] +inherits = *common*; *pauseprint* +bed_shape = 5x0,230x0,230x235,5x235 +printer_model = CR6SE +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_CREALITY\nPRINTER_MODEL_CR6SE\nPRINTER_HAS_BOWDEN + +[printer:Creality CR-6 Max] +inherits = *common*; *slowabl* +retract_length = 6 +bed_shape = 5x5,395x5,395x395,5x395 +max_print_height = 400 +printer_model = CR6MAX +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_CREALITY\nPRINTER_MODEL_CR6MAX\nPRINTER_HAS_BOWDEN [printer:Creality CR-10 Mini] -inherits = Creality Ender-3 +inherits = *common* retract_length = 6 -bed_shape = 2.5x5,2.5x225,302.5x225,302.5x5 +bed_shape = 2.5x5,302.5x5,302.5x225,2.5x225 max_print_height = 300 printer_model = CR10MINI 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_CREALITY\nPRINTER_MODEL_CR10MINI\nPRINTER_HAS_BOWDEN -#[printer:Creality CR-10 Max] -#inherits = Creality Ender-3; *slowabl* -#retract_length = 6 -#bed_shape = 5x5,445x5,445x445,5x445 -#max_print_height = 470 -#printer_model = CR10MAX -#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_CREALITY\nPRINTER_MODEL_CR10MAX\nPRINTER_HAS_BOWDEN +[printer:Creality CR-10 Max] +inherits = *common*; *slowabl* +retract_length = 6 +bed_shape = 5x5,445x5,445x445,5x445 +max_print_height = 470 +printer_model = CR10MAX +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_CREALITY\nPRINTER_MODEL_CR10MAX\nPRINTER_HAS_BOWDEN [printer:Creality CR-10] -inherits = Creality Ender-3 +inherits = *common* retract_length = 6 bed_shape = 5x5,305x5,305x305,5x305 max_print_height = 400 @@ -944,7 +1007,7 @@ printer_model = CR10 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_CREALITY\nPRINTER_MODEL_CR10\nPRINTER_HAS_BOWDEN [printer:Creality CR-10 V2] -inherits = Creality Ender-3 +inherits = *common* retract_length = 6 bed_shape = 5x5,305x5,305x305,5x305 max_print_height = 400 @@ -952,7 +1015,7 @@ printer_model = CR10V2 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_CREALITY\nPRINTER_MODEL_CR10V2\nPRINTER_HAS_BOWDEN [printer:Creality CR-10 V3] -inherits = Creality Ender-3 +inherits = *common* retract_length = 1 bed_shape = 5x5,305x5,305x305,5x305 max_print_height = 400 @@ -960,7 +1023,7 @@ printer_model = CR10V3 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_CREALITY\nPRINTER_MODEL_CR10V3 [printer:Creality CR-10 S] -inherits = Creality Ender-3 +inherits = *common* retract_length = 6 bed_shape = 5x5,305x5,305x305,5x305 max_print_height = 400 @@ -968,7 +1031,7 @@ printer_model = CR10S 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_CREALITY\nPRINTER_MODEL_CR10S\nPRINTER_HAS_BOWDEN [printer:Creality CR-10 S Pro] -inherits = Creality Ender-3; *slowabl* +inherits = *common*; *slowabl* retract_length = 6 bed_shape = 5x5,295x5,295x295,5x295 max_print_height = 400 @@ -976,7 +1039,7 @@ printer_model = CR10SPRO 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_CREALITY\nPRINTER_MODEL_CR10SPRO\nPRINTER_HAS_BOWDEN [printer:Creality CR-10 S Pro V2] -inherits = Creality Ender-3; *slowabl* +inherits = *common*; *slowabl* retract_length = 6 bed_shape = 5x5,305x5,305x305,5x305 max_print_height = 400 @@ -984,7 +1047,7 @@ printer_model = CR10SPROV2 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_CREALITY\nPRINTER_MODEL_CR10SPROV2\nPRINTER_HAS_BOWDEN [printer:Creality CR-10 S4] -inherits = Creality Ender-3 +inherits = *common* retract_length = 6 bed_shape = 5x5,395x5,395x395,5x395 max_print_height = 400 @@ -992,7 +1055,7 @@ printer_model = CR10S4 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_CREALITY\nPRINTER_MODEL_CR10S4\nPRINTER_HAS_BOWDEN [printer:Creality CR-10 S5] -inherits = Creality Ender-3 +inherits = *common* retract_length = 6 bed_shape = 5x5,505x5,505x505,5x505 max_print_height = 500 @@ -1000,25 +1063,32 @@ printer_model = CR10S5 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_CREALITY\nPRINTER_MODEL_CR10S5\nPRINTER_HAS_BOWDEN [printer:Creality CR-20] -inherits = Creality Ender-3 +inherits = *common* printer_model = CR20 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_CREALITY\nPRINTER_MODEL_CR20\nPRINTER_HAS_BOWDEN [printer:Creality CR-20 Pro] -inherits = Creality Ender-3; *fastabl* +inherits = *common*; *fastabl* retract_length = 4 printer_model = CR20PRO 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_CREALITY\nPRINTER_MODEL_CR20PRO\nPRINTER_HAS_BOWDEN -#[printer:Creality CR-8] -#inherits = Creality Ender-3 -#bed_shape = 5x5,215x5,215x215,5x215 -#max_print_height = 210 -#printer_model = CR8 -#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_CREALITY\nPRINTER_MODEL_CR8\nPRINTER_HAS_BOWDEN +[printer:Creality CR-200B] +inherits = *common*; *descendingz* +bed_shape = 5x5,195x5,195x195,5x195 +max_print_height = 200 +printer_model = CR200B +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_CREALITY\nPRINTER_MODEL_CR200B\nPRINTER_HAS_BOWDEN + +[printer:Creality CR-8] +inherits = *common* +bed_shape = 5x5,215x5,215x215,5x215 +max_print_height = 210 +printer_model = CR8 +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_CREALITY\nPRINTER_MODEL_CR8\nPRINTER_HAS_BOWDEN #[printer:Creality CR-X] -#inherits = Creality Ender-3; *dualextruder* +#inherits = *common*; *dualextruder* #retract_length = 6,6 #bed_shape = 5x5,295x5,295x295,5x295 #max_print_height = 400 @@ -1026,7 +1096,7 @@ printer_notes = Don't remove the following keywords! These keywords are used in #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_CREALITY\nPRINTER_MODEL_CRX\nPRINTER_HAS_BOWDEN #[printer:Creality CR-X Pro] -#inherits = Creality Ender-3; *dualextruder*; *slowabl* +#inherits = *common*; *dualextruder*; *slowabl* #retract_length = 6,6 #bed_shape = 5x5,295x5,295x295,5x295 #max_print_height = 400 diff --git a/resources/profiles/Creality/CR200B_thumbnail.png b/resources/profiles/Creality/CR200B_thumbnail.png new file mode 100644 index 0000000000..d5b964ac93 Binary files /dev/null and b/resources/profiles/Creality/CR200B_thumbnail.png differ diff --git a/resources/profiles/Creality/CR5PROH_thumbnail.png b/resources/profiles/Creality/CR5PROH_thumbnail.png new file mode 100644 index 0000000000..df2f1b665f Binary files /dev/null and b/resources/profiles/Creality/CR5PROH_thumbnail.png differ diff --git a/resources/profiles/Creality/CR5PRO_thumbnail.png b/resources/profiles/Creality/CR5PRO_thumbnail.png new file mode 100644 index 0000000000..df2f1b665f Binary files /dev/null and b/resources/profiles/Creality/CR5PRO_thumbnail.png differ diff --git a/resources/profiles/Creality/cr200b.svg b/resources/profiles/Creality/cr200b.svg new file mode 100644 index 0000000000..6012188d7f --- /dev/null +++ b/resources/profiles/Creality/cr200b.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/profiles/Creality/cr200b_bed.stl b/resources/profiles/Creality/cr200b_bed.stl new file mode 100644 index 0000000000..210c329e71 --- /dev/null +++ b/resources/profiles/Creality/cr200b_bed.stl @@ -0,0 +1,2774 @@ +solid OpenSCAD_Model + facet normal 0 0 -1 + outer loop + vertex 97.1047 -99.9982 -3 + vertex 97.0018 -99.9982 -3 + vertex 97.3136 -99.9836 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 97.3136 -99.9836 -3 + vertex 97.0018 -99.9982 -3 + vertex 97.5209 -99.9544 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 100 -97 -3 + vertex 97.0018 -99.9982 -3 + vertex 97.0018 99.9982 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 97.5209 -99.9544 -3 + vertex 97.0018 -99.9982 -3 + vertex 97.7258 -99.9109 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 97.7258 -99.9109 -3 + vertex 97.0018 -99.9982 -3 + vertex 97.927 -99.8532 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 97.927 -99.8532 -3 + vertex 97.0018 -99.9982 -3 + vertex 98.1238 -99.7815 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 98.1238 -99.7815 -3 + vertex 97.0018 -99.9982 -3 + vertex 98.3151 -99.6964 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 98.3151 -99.6964 -3 + vertex 97.0018 -99.9982 -3 + vertex 98.5 -99.5981 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 98.5 -99.5981 -3 + vertex 97.0018 -99.9982 -3 + vertex 98.6776 -99.4871 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 98.6776 -99.4871 -3 + vertex 97.0018 -99.9982 -3 + vertex 98.847 -99.364 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 98.847 -99.364 -3 + vertex 97.0018 -99.9982 -3 + vertex 99.0074 -99.2294 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 99.0074 -99.2294 -3 + vertex 97.0018 -99.9982 -3 + vertex 99.158 -99.084 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 99.158 -99.084 -3 + vertex 97.0018 -99.9982 -3 + vertex 99.2981 -98.9284 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 99.2981 -98.9284 -3 + vertex 97.0018 -99.9982 -3 + vertex 99.427 -98.7634 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 99.427 -98.7634 -3 + vertex 97.0018 -99.9982 -3 + vertex 99.5441 -98.5898 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 99.5441 -98.5898 -3 + vertex 97.0018 -99.9982 -3 + vertex 99.6488 -98.4084 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 99.6488 -98.4084 -3 + vertex 97.0018 -99.9982 -3 + vertex 99.7406 -98.2202 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 99.7406 -98.2202 -3 + vertex 97.0018 -99.9982 -3 + vertex 99.8191 -98.026 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 99.8191 -98.026 -3 + vertex 97.0018 -99.9982 -3 + vertex 99.8838 -97.8269 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 99.8838 -97.8269 -3 + vertex 97.0018 -99.9982 -3 + vertex 99.9344 -97.6237 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 99.9344 -97.6237 -3 + vertex 97.0018 -99.9982 -3 + vertex 99.9708 -97.4175 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 99.9708 -97.4175 -3 + vertex 97.0018 -99.9982 -3 + vertex 99.9927 -97.2093 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 99.9927 -97.2093 -3 + vertex 97.0018 -99.9982 -3 + vertex 100 -97 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 100 -97 -3 + vertex 97.0018 99.9982 -3 + vertex 100 97 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 97.0018 99.9982 -3 + vertex 99.9708 97.4175 -3 + vertex 99.9927 97.2093 -3 + endloop + endfacet + facet normal -0 -0 -1 + outer loop + vertex 99.8838 97.8269 -3 + vertex 99.9344 97.6237 -3 + vertex 97.0018 99.9982 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 99.7406 98.2202 -3 + vertex 97.0018 99.9982 -3 + vertex 99.6488 98.4084 -3 + endloop + endfacet + facet normal -0 -0 -1 + outer loop + vertex 99.7406 98.2202 -3 + vertex 99.8191 98.026 -3 + vertex 97.0018 99.9982 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 97.0018 99.9982 -3 + vertex 99.0074 99.2294 -3 + vertex 99.158 99.084 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 99.427 98.7634 -3 + vertex 97.0018 99.9982 -3 + vertex 99.2981 98.9284 -3 + endloop + endfacet + facet normal -0 -0 -1 + outer loop + vertex 99.427 98.7634 -3 + vertex 99.5441 98.5898 -3 + vertex 97.0018 99.9982 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 99.2981 98.9284 -3 + vertex 97.0018 99.9982 -3 + vertex 99.158 99.084 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 97.0018 99.9982 -3 + vertex 97.927 99.8532 -3 + vertex 98.1238 99.7815 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 98.847 99.364 -3 + vertex 97.0018 99.9982 -3 + vertex 98.6776 99.4871 -3 + endloop + endfacet + facet normal -0 -0 -1 + outer loop + vertex 98.847 99.364 -3 + vertex 99.0074 99.2294 -3 + vertex 97.0018 99.9982 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 98.6776 99.4871 -3 + vertex 97.0018 99.9982 -3 + vertex 98.5 99.5981 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 98.5 99.5981 -3 + vertex 97.0018 99.9982 -3 + vertex 98.3151 99.6964 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 98.3151 99.6964 -3 + vertex 97.0018 99.9982 -3 + vertex 98.1238 99.7815 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 99.6488 98.4084 -3 + vertex 97.0018 99.9982 -3 + vertex 99.5441 98.5898 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 97.7258 99.9109 -3 + vertex 97.0018 99.9982 -3 + vertex 97.5209 99.9544 -3 + endloop + endfacet + facet normal -0 -0 -1 + outer loop + vertex 97.7258 99.9109 -3 + vertex 97.927 99.8532 -3 + vertex 97.0018 99.9982 -3 + endloop + endfacet + facet normal -0 -0 -1 + outer loop + vertex 99.8191 98.026 -3 + vertex 99.8838 97.8269 -3 + vertex 97.0018 99.9982 -3 + endloop + endfacet + facet normal -0 -0 -1 + outer loop + vertex 97.3136 99.9836 -3 + vertex 97.5209 99.9544 -3 + vertex 97.0018 99.9982 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 97.0018 99.9982 -3 + vertex 99.9344 97.6237 -3 + vertex 99.9708 97.4175 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 97.0018 99.9982 -3 + vertex 97.1047 99.9982 -3 + vertex 97.3136 99.9836 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 100 97 -3 + vertex 97.0018 99.9982 -3 + vertex 99.9927 97.2093 -3 + endloop + endfacet + facet normal 0 -0 -1 + outer loop + vertex -97 100 -3 + vertex 97 100 -3 + vertex -97.0018 99.9982 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 99.9982 -3 + vertex 97 100 -3 + vertex 97.0018 99.9982 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 97.0018 -99.9982 -3 + vertex -97.0018 99.9982 -3 + vertex 97.0018 99.9982 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -100 -97 -3 + vertex -100 97 -3 + vertex -97.0018 -99.9982 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex -97.0018 99.9982 -3 + vertex -97.3136 99.9836 -3 + vertex -97.1047 99.9982 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 99.9982 -3 + vertex -97.5209 99.9544 -3 + vertex -97.3136 99.9836 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 99.9982 -3 + vertex -97.7258 99.9109 -3 + vertex -97.5209 99.9544 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 99.9982 -3 + vertex -97.927 99.8532 -3 + vertex -97.7258 99.9109 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 99.9982 -3 + vertex -98.1238 99.7815 -3 + vertex -97.927 99.8532 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 99.9982 -3 + vertex -98.3151 99.6964 -3 + vertex -98.1238 99.7815 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 99.9982 -3 + vertex -98.5 99.5981 -3 + vertex -98.3151 99.6964 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 99.9982 -3 + vertex -98.6776 99.4871 -3 + vertex -98.5 99.5981 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 99.9982 -3 + vertex -98.847 99.364 -3 + vertex -98.6776 99.4871 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 99.9982 -3 + vertex -99.0074 99.2294 -3 + vertex -98.847 99.364 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 99.9982 -3 + vertex -99.158 99.084 -3 + vertex -99.0074 99.2294 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 99.9982 -3 + vertex -99.2981 98.9284 -3 + vertex -99.158 99.084 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 99.9982 -3 + vertex -99.427 98.7634 -3 + vertex -99.2981 98.9284 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 99.9982 -3 + vertex -99.5441 98.5898 -3 + vertex -99.427 98.7634 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 99.9982 -3 + vertex -99.6488 98.4084 -3 + vertex -99.5441 98.5898 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 99.9982 -3 + vertex -99.7406 98.2202 -3 + vertex -99.6488 98.4084 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 99.9982 -3 + vertex -99.8191 98.026 -3 + vertex -99.7406 98.2202 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 99.9982 -3 + vertex -99.8838 97.8269 -3 + vertex -99.8191 98.026 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 99.9982 -3 + vertex -99.9344 97.6237 -3 + vertex -99.8838 97.8269 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 99.9982 -3 + vertex -99.9708 97.4175 -3 + vertex -99.9344 97.6237 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 99.9982 -3 + vertex -99.9927 97.2093 -3 + vertex -99.9708 97.4175 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 99.9982 -3 + vertex -100 97 -3 + vertex -99.9927 97.2093 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 -99.9982 -3 + vertex -100 97 -3 + vertex -97.0018 99.9982 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 97.0018 -99.9982 -3 + vertex -97.0018 -99.9982 -3 + vertex -97.0018 99.9982 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 -99.9982 -3 + vertex -99.9708 -97.4175 -3 + vertex -99.9927 -97.2093 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -99.8838 -97.8269 -3 + vertex -99.9344 -97.6237 -3 + vertex -97.0018 -99.9982 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -99.7406 -98.2202 -3 + vertex -97.0018 -99.9982 -3 + vertex -99.6488 -98.4084 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -99.7406 -98.2202 -3 + vertex -99.8191 -98.026 -3 + vertex -97.0018 -99.9982 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 -99.9982 -3 + vertex -99.0074 -99.2294 -3 + vertex -99.158 -99.084 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -99.427 -98.7634 -3 + vertex -97.0018 -99.9982 -3 + vertex -99.2981 -98.9284 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -99.427 -98.7634 -3 + vertex -99.5441 -98.5898 -3 + vertex -97.0018 -99.9982 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -99.2981 -98.9284 -3 + vertex -97.0018 -99.9982 -3 + vertex -99.158 -99.084 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 -99.9982 -3 + vertex -97.927 -99.8532 -3 + vertex -98.1238 -99.7815 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -98.847 -99.364 -3 + vertex -97.0018 -99.9982 -3 + vertex -98.6776 -99.4871 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -98.847 -99.364 -3 + vertex -99.0074 -99.2294 -3 + vertex -97.0018 -99.9982 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -98.6776 -99.4871 -3 + vertex -97.0018 -99.9982 -3 + vertex -98.5 -99.5981 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -98.5 -99.5981 -3 + vertex -97.0018 -99.9982 -3 + vertex -98.3151 -99.6964 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -98.3151 -99.6964 -3 + vertex -97.0018 -99.9982 -3 + vertex -98.1238 -99.7815 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -99.6488 -98.4084 -3 + vertex -97.0018 -99.9982 -3 + vertex -99.5441 -98.5898 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.7258 -99.9109 -3 + vertex -97.0018 -99.9982 -3 + vertex -97.5209 -99.9544 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.7258 -99.9109 -3 + vertex -97.927 -99.8532 -3 + vertex -97.0018 -99.9982 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -99.8191 -98.026 -3 + vertex -99.8838 -97.8269 -3 + vertex -97.0018 -99.9982 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.3136 -99.9836 -3 + vertex -97.5209 -99.9544 -3 + vertex -97.0018 -99.9982 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 -99.9982 -3 + vertex -99.9344 -97.6237 -3 + vertex -99.9708 -97.4175 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 -99.9982 -3 + vertex -97.1047 -99.9982 -3 + vertex -97.3136 -99.9836 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 97.0018 -99.9982 -3 + vertex 97 -100 -3 + vertex -97.0018 -99.9982 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -97.0018 -99.9982 -3 + vertex 97 -100 -3 + vertex -97 -100 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -100 -97 -3 + vertex -97.0018 -99.9982 -3 + vertex -99.9927 -97.2093 -3 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 97.3136 -99.9836 0 + vertex 97.0018 -99.9982 0 + vertex 97.1047 -99.9982 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 97.5209 -99.9544 0 + vertex 97.0018 -99.9982 0 + vertex 97.3136 -99.9836 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 97.0018 99.9982 0 + vertex 97.0018 -99.9982 0 + vertex 100 -97 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 97.7258 -99.9109 0 + vertex 97.0018 -99.9982 0 + vertex 97.5209 -99.9544 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 97.927 -99.8532 0 + vertex 97.0018 -99.9982 0 + vertex 97.7258 -99.9109 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 98.1238 -99.7815 0 + vertex 97.0018 -99.9982 0 + vertex 97.927 -99.8532 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 98.3151 -99.6964 0 + vertex 97.0018 -99.9982 0 + vertex 98.1238 -99.7815 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 98.5 -99.5981 0 + vertex 97.0018 -99.9982 0 + vertex 98.3151 -99.6964 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 98.6776 -99.4871 0 + vertex 97.0018 -99.9982 0 + vertex 98.5 -99.5981 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 98.847 -99.364 0 + vertex 97.0018 -99.9982 0 + vertex 98.6776 -99.4871 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 99.0074 -99.2294 0 + vertex 97.0018 -99.9982 0 + vertex 98.847 -99.364 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 99.158 -99.084 0 + vertex 97.0018 -99.9982 0 + vertex 99.0074 -99.2294 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 99.2981 -98.9284 0 + vertex 97.0018 -99.9982 0 + vertex 99.158 -99.084 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 99.427 -98.7634 0 + vertex 97.0018 -99.9982 0 + vertex 99.2981 -98.9284 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 99.5441 -98.5898 0 + vertex 97.0018 -99.9982 0 + vertex 99.427 -98.7634 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 99.6488 -98.4084 0 + vertex 97.0018 -99.9982 0 + vertex 99.5441 -98.5898 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 99.7406 -98.2202 0 + vertex 97.0018 -99.9982 0 + vertex 99.6488 -98.4084 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 99.8191 -98.026 0 + vertex 97.0018 -99.9982 0 + vertex 99.7406 -98.2202 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 99.8838 -97.8269 0 + vertex 97.0018 -99.9982 0 + vertex 99.8191 -98.026 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 99.9344 -97.6237 0 + vertex 97.0018 -99.9982 0 + vertex 99.8838 -97.8269 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 99.9708 -97.4175 0 + vertex 97.0018 -99.9982 0 + vertex 99.9344 -97.6237 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 99.9927 -97.2093 0 + vertex 97.0018 -99.9982 0 + vertex 99.9708 -97.4175 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 100 -97 0 + vertex 97.0018 -99.9982 0 + vertex 99.9927 -97.2093 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 100 97 0 + vertex 97.0018 99.9982 0 + vertex 100 -97 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 99.9927 97.2093 0 + vertex 99.9708 97.4175 0 + vertex 97.0018 99.9982 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 97.0018 99.9982 0 + vertex 99.9344 97.6237 0 + vertex 99.8838 97.8269 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 99.6488 98.4084 0 + vertex 97.0018 99.9982 0 + vertex 99.7406 98.2202 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 97.0018 99.9982 0 + vertex 99.8191 98.026 0 + vertex 99.7406 98.2202 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 99.158 99.084 0 + vertex 99.0074 99.2294 0 + vertex 97.0018 99.9982 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 99.2981 98.9284 0 + vertex 97.0018 99.9982 0 + vertex 99.427 98.7634 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 97.0018 99.9982 0 + vertex 99.5441 98.5898 0 + vertex 99.427 98.7634 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 99.158 99.084 0 + vertex 97.0018 99.9982 0 + vertex 99.2981 98.9284 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 98.1238 99.7815 0 + vertex 97.927 99.8532 0 + vertex 97.0018 99.9982 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 98.6776 99.4871 0 + vertex 97.0018 99.9982 0 + vertex 98.847 99.364 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 97.0018 99.9982 0 + vertex 99.0074 99.2294 0 + vertex 98.847 99.364 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 98.5 99.5981 0 + vertex 97.0018 99.9982 0 + vertex 98.6776 99.4871 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 98.3151 99.6964 0 + vertex 97.0018 99.9982 0 + vertex 98.5 99.5981 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 98.1238 99.7815 0 + vertex 97.0018 99.9982 0 + vertex 98.3151 99.6964 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 99.5441 98.5898 0 + vertex 97.0018 99.9982 0 + vertex 99.6488 98.4084 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 97.5209 99.9544 0 + vertex 97.0018 99.9982 0 + vertex 97.7258 99.9109 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 97.0018 99.9982 0 + vertex 97.927 99.8532 0 + vertex 97.7258 99.9109 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 97.0018 99.9982 0 + vertex 99.8838 97.8269 0 + vertex 99.8191 98.026 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 97.0018 99.9982 0 + vertex 97.5209 99.9544 0 + vertex 97.3136 99.9836 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 99.9708 97.4175 0 + vertex 99.9344 97.6237 0 + vertex 97.0018 99.9982 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 97.3136 99.9836 0 + vertex 97.1047 99.9982 0 + vertex 97.0018 99.9982 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 99.9927 97.2093 0 + vertex 97.0018 99.9982 0 + vertex 100 97 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -97.0018 99.9982 0 + vertex 97 100 0 + vertex -97 100 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 97.0018 99.9982 0 + vertex 97 100 0 + vertex -97.0018 99.9982 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 97.0018 99.9982 0 + vertex -97.0018 99.9982 0 + vertex 97.0018 -99.9982 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -97.0018 -99.9982 0 + vertex -100 97 0 + vertex -100 -97 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -97.1047 99.9982 0 + vertex -97.3136 99.9836 0 + vertex -97.0018 99.9982 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -97.3136 99.9836 0 + vertex -97.5209 99.9544 0 + vertex -97.0018 99.9982 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -97.5209 99.9544 0 + vertex -97.7258 99.9109 0 + vertex -97.0018 99.9982 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -97.7258 99.9109 0 + vertex -97.927 99.8532 0 + vertex -97.0018 99.9982 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -97.927 99.8532 0 + vertex -98.1238 99.7815 0 + vertex -97.0018 99.9982 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -98.1238 99.7815 0 + vertex -98.3151 99.6964 0 + vertex -97.0018 99.9982 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -98.3151 99.6964 0 + vertex -98.5 99.5981 0 + vertex -97.0018 99.9982 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -98.5 99.5981 0 + vertex -98.6776 99.4871 0 + vertex -97.0018 99.9982 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -98.6776 99.4871 0 + vertex -98.847 99.364 0 + vertex -97.0018 99.9982 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -98.847 99.364 0 + vertex -99.0074 99.2294 0 + vertex -97.0018 99.9982 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -99.0074 99.2294 0 + vertex -99.158 99.084 0 + vertex -97.0018 99.9982 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -99.158 99.084 0 + vertex -99.2981 98.9284 0 + vertex -97.0018 99.9982 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -99.2981 98.9284 0 + vertex -99.427 98.7634 0 + vertex -97.0018 99.9982 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -99.427 98.7634 0 + vertex -99.5441 98.5898 0 + vertex -97.0018 99.9982 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -99.5441 98.5898 0 + vertex -99.6488 98.4084 0 + vertex -97.0018 99.9982 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -99.6488 98.4084 0 + vertex -99.7406 98.2202 0 + vertex -97.0018 99.9982 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -99.7406 98.2202 0 + vertex -99.8191 98.026 0 + vertex -97.0018 99.9982 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -99.8191 98.026 0 + vertex -99.8838 97.8269 0 + vertex -97.0018 99.9982 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -99.8838 97.8269 0 + vertex -99.9344 97.6237 0 + vertex -97.0018 99.9982 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -99.9344 97.6237 0 + vertex -99.9708 97.4175 0 + vertex -97.0018 99.9982 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -99.9708 97.4175 0 + vertex -99.9927 97.2093 0 + vertex -97.0018 99.9982 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -99.9927 97.2093 0 + vertex -100 97 0 + vertex -97.0018 99.9982 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -97.0018 99.9982 0 + vertex -100 97 0 + vertex -97.0018 -99.9982 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -97.0018 99.9982 0 + vertex -97.0018 -99.9982 0 + vertex 97.0018 -99.9982 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -99.9927 -97.2093 0 + vertex -99.9708 -97.4175 0 + vertex -97.0018 -99.9982 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -97.0018 -99.9982 0 + vertex -99.9344 -97.6237 0 + vertex -99.8838 -97.8269 0 + endloop + endfacet + facet normal -0 -0 1 + outer loop + vertex -99.6488 -98.4084 0 + vertex -97.0018 -99.9982 0 + vertex -99.7406 -98.2202 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -97.0018 -99.9982 0 + vertex -99.8191 -98.026 0 + vertex -99.7406 -98.2202 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -99.158 -99.084 0 + vertex -99.0074 -99.2294 0 + vertex -97.0018 -99.9982 0 + endloop + endfacet + facet normal -0 -0 1 + outer loop + vertex -99.2981 -98.9284 0 + vertex -97.0018 -99.9982 0 + vertex -99.427 -98.7634 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -97.0018 -99.9982 0 + vertex -99.5441 -98.5898 0 + vertex -99.427 -98.7634 0 + endloop + endfacet + facet normal -0 -0 1 + outer loop + vertex -99.158 -99.084 0 + vertex -97.0018 -99.9982 0 + vertex -99.2981 -98.9284 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -98.1238 -99.7815 0 + vertex -97.927 -99.8532 0 + vertex -97.0018 -99.9982 0 + endloop + endfacet + facet normal -0 -0 1 + outer loop + vertex -98.6776 -99.4871 0 + vertex -97.0018 -99.9982 0 + vertex -98.847 -99.364 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -97.0018 -99.9982 0 + vertex -99.0074 -99.2294 0 + vertex -98.847 -99.364 0 + endloop + endfacet + facet normal -0 -0 1 + outer loop + vertex -98.5 -99.5981 0 + vertex -97.0018 -99.9982 0 + vertex -98.6776 -99.4871 0 + endloop + endfacet + facet normal -0 -0 1 + outer loop + vertex -98.3151 -99.6964 0 + vertex -97.0018 -99.9982 0 + vertex -98.5 -99.5981 0 + endloop + endfacet + facet normal -0 -0 1 + outer loop + vertex -98.1238 -99.7815 0 + vertex -97.0018 -99.9982 0 + vertex -98.3151 -99.6964 0 + endloop + endfacet + facet normal -0 -0 1 + outer loop + vertex -99.5441 -98.5898 0 + vertex -97.0018 -99.9982 0 + vertex -99.6488 -98.4084 0 + endloop + endfacet + facet normal -0 -0 1 + outer loop + vertex -97.5209 -99.9544 0 + vertex -97.0018 -99.9982 0 + vertex -97.7258 -99.9109 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -97.0018 -99.9982 0 + vertex -97.927 -99.8532 0 + vertex -97.7258 -99.9109 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -97.0018 -99.9982 0 + vertex -99.8838 -97.8269 0 + vertex -99.8191 -98.026 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -97.0018 -99.9982 0 + vertex -97.5209 -99.9544 0 + vertex -97.3136 -99.9836 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -99.9708 -97.4175 0 + vertex -99.9344 -97.6237 0 + vertex -97.0018 -99.9982 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -97.3136 -99.9836 0 + vertex -97.1047 -99.9982 0 + vertex -97.0018 -99.9982 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -97.0018 -99.9982 0 + vertex 97 -100 0 + vertex 97.0018 -99.9982 0 + endloop + endfacet + facet normal 0 -0 1 + outer loop + vertex -97 -100 0 + vertex 97 -100 0 + vertex -97.0018 -99.9982 0 + endloop + endfacet + facet normal -0 -0 1 + outer loop + vertex -99.9927 -97.2093 0 + vertex -97.0018 -99.9982 0 + vertex -100 -97 0 + endloop + endfacet + facet normal 0 -1 0 + outer loop + vertex 97.1047 -99.9982 -3 + vertex 97.0018 -99.9982 0 + vertex 97.0018 -99.9982 -3 + endloop + endfacet + facet normal 0 -1 0 + outer loop + vertex 97.1047 -99.9982 -3 + vertex 97.1047 -99.9982 0 + vertex 97.0018 -99.9982 0 + endloop + endfacet + facet normal 0.0697198 -0.997567 0 + outer loop + vertex 97.3136 -99.9836 -3 + vertex 97.1047 -99.9982 0 + vertex 97.1047 -99.9982 -3 + endloop + endfacet + facet normal 0.0697198 -0.997567 0 + outer loop + vertex 97.3136 -99.9836 -3 + vertex 97.3136 -99.9836 0 + vertex 97.1047 -99.9982 0 + endloop + endfacet + facet normal 0.139482 -0.990225 0 + outer loop + vertex 97.5209 -99.9544 -3 + vertex 97.3136 -99.9836 0 + vertex 97.3136 -99.9836 -3 + endloop + endfacet + facet normal 0.139482 -0.990225 0 + outer loop + vertex 97.5209 -99.9544 -3 + vertex 97.5209 -99.9544 0 + vertex 97.3136 -99.9836 0 + endloop + endfacet + facet normal 0.20767 -0.978199 0 + outer loop + vertex 97.7258 -99.9109 -3 + vertex 97.5209 -99.9544 0 + vertex 97.5209 -99.9544 -3 + endloop + endfacet + facet normal 0.20767 -0.978199 0 + outer loop + vertex 97.7258 -99.9109 -3 + vertex 97.7258 -99.9109 0 + vertex 97.5209 -99.9544 0 + endloop + endfacet + facet normal 0.275668 -0.961253 0 + outer loop + vertex 97.927 -99.8532 -3 + vertex 97.7258 -99.9109 0 + vertex 97.7258 -99.9109 -3 + endloop + endfacet + facet normal 0.275668 -0.961253 0 + outer loop + vertex 97.927 -99.8532 -3 + vertex 97.927 -99.8532 0 + vertex 97.7258 -99.9109 0 + endloop + endfacet + facet normal 0.342318 -0.939584 0 + outer loop + vertex 98.1238 -99.7815 -3 + vertex 97.927 -99.8532 0 + vertex 97.927 -99.8532 -3 + endloop + endfacet + facet normal 0.342318 -0.939584 0 + outer loop + vertex 98.1238 -99.7815 -3 + vertex 98.1238 -99.7815 0 + vertex 97.927 -99.8532 0 + endloop + endfacet + facet normal 0.406449 -0.913674 0 + outer loop + vertex 98.3151 -99.6964 -3 + vertex 98.1238 -99.7815 0 + vertex 98.1238 -99.7815 -3 + endloop + endfacet + facet normal 0.406449 -0.913674 0 + outer loop + vertex 98.3151 -99.6964 -3 + vertex 98.3151 -99.6964 0 + vertex 98.1238 -99.7815 0 + endloop + endfacet + facet normal 0.469423 -0.882973 0 + outer loop + vertex 98.5 -99.5981 -3 + vertex 98.3151 -99.6964 0 + vertex 98.3151 -99.6964 -3 + endloop + endfacet + facet normal 0.469423 -0.882973 0 + outer loop + vertex 98.5 -99.5981 -3 + vertex 98.5 -99.5981 0 + vertex 98.3151 -99.6964 0 + endloop + endfacet + facet normal 0.529999 -0.847998 0 + outer loop + vertex 98.6776 -99.4871 -3 + vertex 98.5 -99.5981 0 + vertex 98.5 -99.5981 -3 + endloop + endfacet + facet normal 0.529999 -0.847998 0 + outer loop + vertex 98.6776 -99.4871 -3 + vertex 98.6776 -99.4871 0 + vertex 98.5 -99.5981 0 + endloop + endfacet + facet normal 0.587859 -0.808963 0 + outer loop + vertex 98.847 -99.364 -3 + vertex 98.6776 -99.4871 0 + vertex 98.6776 -99.4871 -3 + endloop + endfacet + facet normal 0.587859 -0.808963 0 + outer loop + vertex 98.847 -99.364 -3 + vertex 98.847 -99.364 0 + vertex 98.6776 -99.4871 0 + endloop + endfacet + facet normal 0.642811 -0.766025 0 + outer loop + vertex 99.0074 -99.2294 -3 + vertex 98.847 -99.364 0 + vertex 98.847 -99.364 -3 + endloop + endfacet + facet normal 0.642811 -0.766025 0 + outer loop + vertex 99.0074 -99.2294 -3 + vertex 99.0074 -99.2294 0 + vertex 98.847 -99.364 0 + endloop + endfacet + facet normal 0.694577 -0.719418 0 + outer loop + vertex 99.158 -99.084 -3 + vertex 99.0074 -99.2294 0 + vertex 99.0074 -99.2294 -3 + endloop + endfacet + facet normal 0.694577 -0.719418 0 + outer loop + vertex 99.158 -99.084 -3 + vertex 99.158 -99.084 0 + vertex 99.0074 -99.2294 0 + endloop + endfacet + facet normal 0.743152 -0.669123 0 + outer loop + vertex 99.2981 -98.9284 -3 + vertex 99.158 -99.084 0 + vertex 99.158 -99.084 -3 + endloop + endfacet + facet normal 0.743152 -0.669123 0 + outer loop + vertex 99.2981 -98.9284 -3 + vertex 99.2981 -98.9284 0 + vertex 99.158 -99.084 0 + endloop + endfacet + facet normal 0.788039 -0.615626 0 + outer loop + vertex 99.427 -98.7634 -3 + vertex 99.2981 -98.9284 0 + vertex 99.2981 -98.9284 -3 + endloop + endfacet + facet normal 0.788039 -0.615626 0 + outer loop + vertex 99.427 -98.7634 -3 + vertex 99.427 -98.7634 0 + vertex 99.2981 -98.9284 0 + endloop + endfacet + facet normal 0.829026 -0.55921 0 + outer loop + vertex 99.5441 -98.5898 -3 + vertex 99.427 -98.7634 0 + vertex 99.427 -98.7634 -3 + endloop + endfacet + facet normal 0.829026 -0.55921 0 + outer loop + vertex 99.5441 -98.5898 -3 + vertex 99.5441 -98.5898 0 + vertex 99.427 -98.7634 0 + endloop + endfacet + facet normal 0.86609 -0.499888 0 + outer loop + vertex 99.6488 -98.4084 -3 + vertex 99.5441 -98.5898 0 + vertex 99.5441 -98.5898 -3 + endloop + endfacet + facet normal 0.86609 -0.499888 0 + outer loop + vertex 99.6488 -98.4084 -3 + vertex 99.6488 -98.4084 0 + vertex 99.5441 -98.5898 0 + endloop + endfacet + facet normal 0.898778 -0.438405 0 + outer loop + vertex 99.7406 -98.2202 -3 + vertex 99.6488 -98.4084 0 + vertex 99.6488 -98.4084 -3 + endloop + endfacet + facet normal 0.898778 -0.438405 0 + outer loop + vertex 99.7406 -98.2202 -3 + vertex 99.7406 -98.2202 0 + vertex 99.6488 -98.4084 0 + endloop + endfacet + facet normal 0.927121 -0.374763 0 + outer loop + vertex 99.8191 -98.026 -3 + vertex 99.7406 -98.2202 0 + vertex 99.7406 -98.2202 -3 + endloop + endfacet + facet normal 0.927121 -0.374763 0 + outer loop + vertex 99.8191 -98.026 -3 + vertex 99.8191 -98.026 0 + vertex 99.7406 -98.2202 0 + endloop + endfacet + facet normal 0.951045 -0.309054 0 + outer loop + vertex 99.8838 -97.8269 -3 + vertex 99.8191 -98.026 0 + vertex 99.8191 -98.026 -3 + endloop + endfacet + facet normal 0.951045 -0.309054 0 + outer loop + vertex 99.8838 -97.8269 -3 + vertex 99.8838 -97.8269 0 + vertex 99.8191 -98.026 0 + endloop + endfacet + facet normal 0.970367 -0.241637 0 + outer loop + vertex 99.9344 -97.6237 -3 + vertex 99.8838 -97.8269 0 + vertex 99.8838 -97.8269 -3 + endloop + endfacet + facet normal 0.970367 -0.241637 0 + outer loop + vertex 99.9344 -97.6237 -3 + vertex 99.9344 -97.6237 0 + vertex 99.8838 -97.8269 0 + endloop + endfacet + facet normal 0.984774 -0.17384 0 + outer loop + vertex 99.9708 -97.4175 -3 + vertex 99.9344 -97.6237 0 + vertex 99.9344 -97.6237 -3 + endloop + endfacet + facet normal 0.984774 -0.17384 0 + outer loop + vertex 99.9708 -97.4175 -3 + vertex 99.9708 -97.4175 0 + vertex 99.9344 -97.6237 0 + endloop + endfacet + facet normal 0.994513 -0.10461 0 + outer loop + vertex 99.9927 -97.2093 -3 + vertex 99.9708 -97.4175 0 + vertex 99.9708 -97.4175 -3 + endloop + endfacet + facet normal 0.994513 -0.10461 0 + outer loop + vertex 99.9927 -97.2093 -3 + vertex 99.9927 -97.2093 0 + vertex 99.9708 -97.4175 0 + endloop + endfacet + facet normal 0.999392 -0.034857 0 + outer loop + vertex 100 -97 -3 + vertex 99.9927 -97.2093 0 + vertex 99.9927 -97.2093 -3 + endloop + endfacet + facet normal 0.999392 -0.034857 0 + outer loop + vertex 100 -97 -3 + vertex 100 -97 0 + vertex 99.9927 -97.2093 0 + endloop + endfacet + facet normal 1 0 0 + outer loop + vertex 100 97 -3 + vertex 100 -97 0 + vertex 100 -97 -3 + endloop + endfacet + facet normal 1 0 -0 + outer loop + vertex 100 97 -3 + vertex 100 97 0 + vertex 100 -97 0 + endloop + endfacet + facet normal 0.999392 0.034857 0 + outer loop + vertex 99.9927 97.2093 -3 + vertex 100 97 0 + vertex 100 97 -3 + endloop + endfacet + facet normal 0.999392 0.034857 -0 + outer loop + vertex 99.9927 97.2093 -3 + vertex 99.9927 97.2093 0 + vertex 100 97 0 + endloop + endfacet + facet normal 0.994513 0.10461 0 + outer loop + vertex 99.9708 97.4175 -3 + vertex 99.9927 97.2093 0 + vertex 99.9927 97.2093 -3 + endloop + endfacet + facet normal 0.994513 0.10461 -0 + outer loop + vertex 99.9708 97.4175 -3 + vertex 99.9708 97.4175 0 + vertex 99.9927 97.2093 0 + endloop + endfacet + facet normal 0.984774 0.17384 0 + outer loop + vertex 99.9344 97.6237 -3 + vertex 99.9708 97.4175 0 + vertex 99.9708 97.4175 -3 + endloop + endfacet + facet normal 0.984774 0.17384 -0 + outer loop + vertex 99.9344 97.6237 -3 + vertex 99.9344 97.6237 0 + vertex 99.9708 97.4175 0 + endloop + endfacet + facet normal 0.970367 0.241637 0 + outer loop + vertex 99.8838 97.8269 -3 + vertex 99.9344 97.6237 0 + vertex 99.9344 97.6237 -3 + endloop + endfacet + facet normal 0.970367 0.241637 -0 + outer loop + vertex 99.8838 97.8269 -3 + vertex 99.8838 97.8269 0 + vertex 99.9344 97.6237 0 + endloop + endfacet + facet normal 0.951045 0.309054 0 + outer loop + vertex 99.8191 98.026 -3 + vertex 99.8838 97.8269 0 + vertex 99.8838 97.8269 -3 + endloop + endfacet + facet normal 0.951045 0.309054 -0 + outer loop + vertex 99.8191 98.026 -3 + vertex 99.8191 98.026 0 + vertex 99.8838 97.8269 0 + endloop + endfacet + facet normal 0.927121 0.374763 0 + outer loop + vertex 99.7406 98.2202 -3 + vertex 99.8191 98.026 0 + vertex 99.8191 98.026 -3 + endloop + endfacet + facet normal 0.927121 0.374763 -0 + outer loop + vertex 99.7406 98.2202 -3 + vertex 99.7406 98.2202 0 + vertex 99.8191 98.026 0 + endloop + endfacet + facet normal 0.898778 0.438405 0 + outer loop + vertex 99.6488 98.4084 -3 + vertex 99.7406 98.2202 0 + vertex 99.7406 98.2202 -3 + endloop + endfacet + facet normal 0.898778 0.438405 -0 + outer loop + vertex 99.6488 98.4084 -3 + vertex 99.6488 98.4084 0 + vertex 99.7406 98.2202 0 + endloop + endfacet + facet normal 0.86609 0.499888 0 + outer loop + vertex 99.5441 98.5898 -3 + vertex 99.6488 98.4084 0 + vertex 99.6488 98.4084 -3 + endloop + endfacet + facet normal 0.86609 0.499888 -0 + outer loop + vertex 99.5441 98.5898 -3 + vertex 99.5441 98.5898 0 + vertex 99.6488 98.4084 0 + endloop + endfacet + facet normal 0.829026 0.55921 0 + outer loop + vertex 99.427 98.7634 -3 + vertex 99.5441 98.5898 0 + vertex 99.5441 98.5898 -3 + endloop + endfacet + facet normal 0.829026 0.55921 -0 + outer loop + vertex 99.427 98.7634 -3 + vertex 99.427 98.7634 0 + vertex 99.5441 98.5898 0 + endloop + endfacet + facet normal 0.788039 0.615626 0 + outer loop + vertex 99.2981 98.9284 -3 + vertex 99.427 98.7634 0 + vertex 99.427 98.7634 -3 + endloop + endfacet + facet normal 0.788039 0.615626 -0 + outer loop + vertex 99.2981 98.9284 -3 + vertex 99.2981 98.9284 0 + vertex 99.427 98.7634 0 + endloop + endfacet + facet normal 0.743152 0.669123 0 + outer loop + vertex 99.158 99.084 -3 + vertex 99.2981 98.9284 0 + vertex 99.2981 98.9284 -3 + endloop + endfacet + facet normal 0.743152 0.669123 -0 + outer loop + vertex 99.158 99.084 -3 + vertex 99.158 99.084 0 + vertex 99.2981 98.9284 0 + endloop + endfacet + facet normal 0.694577 0.719418 0 + outer loop + vertex 99.0074 99.2294 -3 + vertex 99.158 99.084 0 + vertex 99.158 99.084 -3 + endloop + endfacet + facet normal 0.694577 0.719418 -0 + outer loop + vertex 99.0074 99.2294 -3 + vertex 99.0074 99.2294 0 + vertex 99.158 99.084 0 + endloop + endfacet + facet normal 0.642811 0.766025 0 + outer loop + vertex 98.847 99.364 -3 + vertex 99.0074 99.2294 0 + vertex 99.0074 99.2294 -3 + endloop + endfacet + facet normal 0.642811 0.766025 -0 + outer loop + vertex 98.847 99.364 -3 + vertex 98.847 99.364 0 + vertex 99.0074 99.2294 0 + endloop + endfacet + facet normal 0.587859 0.808963 0 + outer loop + vertex 98.6776 99.4871 -3 + vertex 98.847 99.364 0 + vertex 98.847 99.364 -3 + endloop + endfacet + facet normal 0.587859 0.808963 -0 + outer loop + vertex 98.6776 99.4871 -3 + vertex 98.6776 99.4871 0 + vertex 98.847 99.364 0 + endloop + endfacet + facet normal 0.529999 0.847998 0 + outer loop + vertex 98.5 99.5981 -3 + vertex 98.6776 99.4871 0 + vertex 98.6776 99.4871 -3 + endloop + endfacet + facet normal 0.529999 0.847998 -0 + outer loop + vertex 98.5 99.5981 -3 + vertex 98.5 99.5981 0 + vertex 98.6776 99.4871 0 + endloop + endfacet + facet normal 0.469423 0.882973 0 + outer loop + vertex 98.3151 99.6964 -3 + vertex 98.5 99.5981 0 + vertex 98.5 99.5981 -3 + endloop + endfacet + facet normal 0.469423 0.882973 -0 + outer loop + vertex 98.3151 99.6964 -3 + vertex 98.3151 99.6964 0 + vertex 98.5 99.5981 0 + endloop + endfacet + facet normal 0.406449 0.913674 0 + outer loop + vertex 98.1238 99.7815 -3 + vertex 98.3151 99.6964 0 + vertex 98.3151 99.6964 -3 + endloop + endfacet + facet normal 0.406449 0.913674 -0 + outer loop + vertex 98.1238 99.7815 -3 + vertex 98.1238 99.7815 0 + vertex 98.3151 99.6964 0 + endloop + endfacet + facet normal 0.342318 0.939584 0 + outer loop + vertex 97.927 99.8532 -3 + vertex 98.1238 99.7815 0 + vertex 98.1238 99.7815 -3 + endloop + endfacet + facet normal 0.342318 0.939584 -0 + outer loop + vertex 97.927 99.8532 -3 + vertex 97.927 99.8532 0 + vertex 98.1238 99.7815 0 + endloop + endfacet + facet normal 0.275668 0.961253 0 + outer loop + vertex 97.7258 99.9109 -3 + vertex 97.927 99.8532 0 + vertex 97.927 99.8532 -3 + endloop + endfacet + facet normal 0.275668 0.961253 -0 + outer loop + vertex 97.7258 99.9109 -3 + vertex 97.7258 99.9109 0 + vertex 97.927 99.8532 0 + endloop + endfacet + facet normal 0.20767 0.978199 0 + outer loop + vertex 97.5209 99.9544 -3 + vertex 97.7258 99.9109 0 + vertex 97.7258 99.9109 -3 + endloop + endfacet + facet normal 0.20767 0.978199 -0 + outer loop + vertex 97.5209 99.9544 -3 + vertex 97.5209 99.9544 0 + vertex 97.7258 99.9109 0 + endloop + endfacet + facet normal 0.139482 0.990225 0 + outer loop + vertex 97.3136 99.9836 -3 + vertex 97.5209 99.9544 0 + vertex 97.5209 99.9544 -3 + endloop + endfacet + facet normal 0.139482 0.990225 -0 + outer loop + vertex 97.3136 99.9836 -3 + vertex 97.3136 99.9836 0 + vertex 97.5209 99.9544 0 + endloop + endfacet + facet normal 0.0697198 0.997567 0 + outer loop + vertex 97.1047 99.9982 -3 + vertex 97.3136 99.9836 0 + vertex 97.3136 99.9836 -3 + endloop + endfacet + facet normal 0.0697198 0.997567 -0 + outer loop + vertex 97.1047 99.9982 -3 + vertex 97.1047 99.9982 0 + vertex 97.3136 99.9836 0 + endloop + endfacet + facet normal 0 1 0 + outer loop + vertex 97.0018 99.9982 -3 + vertex 97.1047 99.9982 0 + vertex 97.1047 99.9982 -3 + endloop + endfacet + facet normal 0 1 0 + outer loop + vertex 97.0018 99.9982 -3 + vertex 97.0018 99.9982 0 + vertex 97.1047 99.9982 0 + endloop + endfacet + facet normal 0.707107 0.707107 0 + outer loop + vertex 97 100 -3 + vertex 97.0018 99.9982 0 + vertex 97.0018 99.9982 -3 + endloop + endfacet + facet normal 0.707107 0.707107 -0 + outer loop + vertex 97 100 -3 + vertex 97 100 0 + vertex 97.0018 99.9982 0 + endloop + endfacet + facet normal 0 1 0 + outer loop + vertex -97 100 -3 + vertex 97 100 0 + vertex 97 100 -3 + endloop + endfacet + facet normal 0 1 0 + outer loop + vertex -97 100 -3 + vertex -97 100 0 + vertex 97 100 0 + endloop + endfacet + facet normal -0.707107 0.707107 0 + outer loop + vertex -97.0018 99.9982 -3 + vertex -97 100 0 + vertex -97 100 -3 + endloop + endfacet + facet normal -0.707107 0.707107 0 + outer loop + vertex -97.0018 99.9982 -3 + vertex -97.0018 99.9982 0 + vertex -97 100 0 + endloop + endfacet + facet normal 0 1 0 + outer loop + vertex -97.1047 99.9982 -3 + vertex -97.0018 99.9982 0 + vertex -97.0018 99.9982 -3 + endloop + endfacet + facet normal 0 1 0 + outer loop + vertex -97.1047 99.9982 -3 + vertex -97.1047 99.9982 0 + vertex -97.0018 99.9982 0 + endloop + endfacet + facet normal -0.0697198 0.997567 0 + outer loop + vertex -97.3136 99.9836 -3 + vertex -97.1047 99.9982 0 + vertex -97.1047 99.9982 -3 + endloop + endfacet + facet normal -0.0697198 0.997567 0 + outer loop + vertex -97.3136 99.9836 -3 + vertex -97.3136 99.9836 0 + vertex -97.1047 99.9982 0 + endloop + endfacet + facet normal -0.139482 0.990225 0 + outer loop + vertex -97.5209 99.9544 -3 + vertex -97.3136 99.9836 0 + vertex -97.3136 99.9836 -3 + endloop + endfacet + facet normal -0.139482 0.990225 0 + outer loop + vertex -97.5209 99.9544 -3 + vertex -97.5209 99.9544 0 + vertex -97.3136 99.9836 0 + endloop + endfacet + facet normal -0.20767 0.978199 0 + outer loop + vertex -97.7258 99.9109 -3 + vertex -97.5209 99.9544 0 + vertex -97.5209 99.9544 -3 + endloop + endfacet + facet normal -0.20767 0.978199 0 + outer loop + vertex -97.7258 99.9109 -3 + vertex -97.7258 99.9109 0 + vertex -97.5209 99.9544 0 + endloop + endfacet + facet normal -0.275668 0.961253 0 + outer loop + vertex -97.927 99.8532 -3 + vertex -97.7258 99.9109 0 + vertex -97.7258 99.9109 -3 + endloop + endfacet + facet normal -0.275668 0.961253 0 + outer loop + vertex -97.927 99.8532 -3 + vertex -97.927 99.8532 0 + vertex -97.7258 99.9109 0 + endloop + endfacet + facet normal -0.342318 0.939584 0 + outer loop + vertex -98.1238 99.7815 -3 + vertex -97.927 99.8532 0 + vertex -97.927 99.8532 -3 + endloop + endfacet + facet normal -0.342318 0.939584 0 + outer loop + vertex -98.1238 99.7815 -3 + vertex -98.1238 99.7815 0 + vertex -97.927 99.8532 0 + endloop + endfacet + facet normal -0.406449 0.913674 0 + outer loop + vertex -98.3151 99.6964 -3 + vertex -98.1238 99.7815 0 + vertex -98.1238 99.7815 -3 + endloop + endfacet + facet normal -0.406449 0.913674 0 + outer loop + vertex -98.3151 99.6964 -3 + vertex -98.3151 99.6964 0 + vertex -98.1238 99.7815 0 + endloop + endfacet + facet normal -0.469423 0.882973 0 + outer loop + vertex -98.5 99.5981 -3 + vertex -98.3151 99.6964 0 + vertex -98.3151 99.6964 -3 + endloop + endfacet + facet normal -0.469423 0.882973 0 + outer loop + vertex -98.5 99.5981 -3 + vertex -98.5 99.5981 0 + vertex -98.3151 99.6964 0 + endloop + endfacet + facet normal -0.529999 0.847998 0 + outer loop + vertex -98.6776 99.4871 -3 + vertex -98.5 99.5981 0 + vertex -98.5 99.5981 -3 + endloop + endfacet + facet normal -0.529999 0.847998 0 + outer loop + vertex -98.6776 99.4871 -3 + vertex -98.6776 99.4871 0 + vertex -98.5 99.5981 0 + endloop + endfacet + facet normal -0.587859 0.808963 0 + outer loop + vertex -98.847 99.364 -3 + vertex -98.6776 99.4871 0 + vertex -98.6776 99.4871 -3 + endloop + endfacet + facet normal -0.587859 0.808963 0 + outer loop + vertex -98.847 99.364 -3 + vertex -98.847 99.364 0 + vertex -98.6776 99.4871 0 + endloop + endfacet + facet normal -0.642811 0.766025 0 + outer loop + vertex -99.0074 99.2294 -3 + vertex -98.847 99.364 0 + vertex -98.847 99.364 -3 + endloop + endfacet + facet normal -0.642811 0.766025 0 + outer loop + vertex -99.0074 99.2294 -3 + vertex -99.0074 99.2294 0 + vertex -98.847 99.364 0 + endloop + endfacet + facet normal -0.694577 0.719418 0 + outer loop + vertex -99.158 99.084 -3 + vertex -99.0074 99.2294 0 + vertex -99.0074 99.2294 -3 + endloop + endfacet + facet normal -0.694577 0.719418 0 + outer loop + vertex -99.158 99.084 -3 + vertex -99.158 99.084 0 + vertex -99.0074 99.2294 0 + endloop + endfacet + facet normal -0.743152 0.669123 0 + outer loop + vertex -99.2981 98.9284 -3 + vertex -99.158 99.084 0 + vertex -99.158 99.084 -3 + endloop + endfacet + facet normal -0.743152 0.669123 0 + outer loop + vertex -99.2981 98.9284 -3 + vertex -99.2981 98.9284 0 + vertex -99.158 99.084 0 + endloop + endfacet + facet normal -0.788039 0.615626 0 + outer loop + vertex -99.427 98.7634 -3 + vertex -99.2981 98.9284 0 + vertex -99.2981 98.9284 -3 + endloop + endfacet + facet normal -0.788039 0.615626 0 + outer loop + vertex -99.427 98.7634 -3 + vertex -99.427 98.7634 0 + vertex -99.2981 98.9284 0 + endloop + endfacet + facet normal -0.829026 0.55921 0 + outer loop + vertex -99.5441 98.5898 -3 + vertex -99.427 98.7634 0 + vertex -99.427 98.7634 -3 + endloop + endfacet + facet normal -0.829026 0.55921 0 + outer loop + vertex -99.5441 98.5898 -3 + vertex -99.5441 98.5898 0 + vertex -99.427 98.7634 0 + endloop + endfacet + facet normal -0.86609 0.499888 0 + outer loop + vertex -99.6488 98.4084 -3 + vertex -99.5441 98.5898 0 + vertex -99.5441 98.5898 -3 + endloop + endfacet + facet normal -0.86609 0.499888 0 + outer loop + vertex -99.6488 98.4084 -3 + vertex -99.6488 98.4084 0 + vertex -99.5441 98.5898 0 + endloop + endfacet + facet normal -0.898778 0.438405 0 + outer loop + vertex -99.7406 98.2202 -3 + vertex -99.6488 98.4084 0 + vertex -99.6488 98.4084 -3 + endloop + endfacet + facet normal -0.898778 0.438405 0 + outer loop + vertex -99.7406 98.2202 -3 + vertex -99.7406 98.2202 0 + vertex -99.6488 98.4084 0 + endloop + endfacet + facet normal -0.927121 0.374763 0 + outer loop + vertex -99.8191 98.026 -3 + vertex -99.7406 98.2202 0 + vertex -99.7406 98.2202 -3 + endloop + endfacet + facet normal -0.927121 0.374763 0 + outer loop + vertex -99.8191 98.026 -3 + vertex -99.8191 98.026 0 + vertex -99.7406 98.2202 0 + endloop + endfacet + facet normal -0.951045 0.309054 0 + outer loop + vertex -99.8838 97.8269 -3 + vertex -99.8191 98.026 0 + vertex -99.8191 98.026 -3 + endloop + endfacet + facet normal -0.951045 0.309054 0 + outer loop + vertex -99.8838 97.8269 -3 + vertex -99.8838 97.8269 0 + vertex -99.8191 98.026 0 + endloop + endfacet + facet normal -0.970367 0.241637 0 + outer loop + vertex -99.9344 97.6237 -3 + vertex -99.8838 97.8269 0 + vertex -99.8838 97.8269 -3 + endloop + endfacet + facet normal -0.970367 0.241637 0 + outer loop + vertex -99.9344 97.6237 -3 + vertex -99.9344 97.6237 0 + vertex -99.8838 97.8269 0 + endloop + endfacet + facet normal -0.984774 0.17384 0 + outer loop + vertex -99.9708 97.4175 -3 + vertex -99.9344 97.6237 0 + vertex -99.9344 97.6237 -3 + endloop + endfacet + facet normal -0.984774 0.17384 0 + outer loop + vertex -99.9708 97.4175 -3 + vertex -99.9708 97.4175 0 + vertex -99.9344 97.6237 0 + endloop + endfacet + facet normal -0.994513 0.10461 0 + outer loop + vertex -99.9927 97.2093 -3 + vertex -99.9708 97.4175 0 + vertex -99.9708 97.4175 -3 + endloop + endfacet + facet normal -0.994513 0.10461 0 + outer loop + vertex -99.9927 97.2093 -3 + vertex -99.9927 97.2093 0 + vertex -99.9708 97.4175 0 + endloop + endfacet + facet normal -0.999392 0.034857 0 + outer loop + vertex -100 97 -3 + vertex -99.9927 97.2093 0 + vertex -99.9927 97.2093 -3 + endloop + endfacet + facet normal -0.999392 0.034857 0 + outer loop + vertex -100 97 -3 + vertex -100 97 0 + vertex -99.9927 97.2093 0 + endloop + endfacet + facet normal -1 0 0 + outer loop + vertex -100 -97 -3 + vertex -100 97 0 + vertex -100 97 -3 + endloop + endfacet + facet normal -1 0 0 + outer loop + vertex -100 -97 -3 + vertex -100 -97 0 + vertex -100 97 0 + endloop + endfacet + facet normal -0.999392 -0.034857 0 + outer loop + vertex -99.9927 -97.2093 -3 + vertex -100 -97 0 + vertex -100 -97 -3 + endloop + endfacet + facet normal -0.999392 -0.034857 0 + outer loop + vertex -99.9927 -97.2093 -3 + vertex -99.9927 -97.2093 0 + vertex -100 -97 0 + endloop + endfacet + facet normal -0.994513 -0.10461 0 + outer loop + vertex -99.9708 -97.4175 -3 + vertex -99.9927 -97.2093 0 + vertex -99.9927 -97.2093 -3 + endloop + endfacet + facet normal -0.994513 -0.10461 0 + outer loop + vertex -99.9708 -97.4175 -3 + vertex -99.9708 -97.4175 0 + vertex -99.9927 -97.2093 0 + endloop + endfacet + facet normal -0.984774 -0.17384 0 + outer loop + vertex -99.9344 -97.6237 -3 + vertex -99.9708 -97.4175 0 + vertex -99.9708 -97.4175 -3 + endloop + endfacet + facet normal -0.984774 -0.17384 0 + outer loop + vertex -99.9344 -97.6237 -3 + vertex -99.9344 -97.6237 0 + vertex -99.9708 -97.4175 0 + endloop + endfacet + facet normal -0.970367 -0.241637 0 + outer loop + vertex -99.8838 -97.8269 -3 + vertex -99.9344 -97.6237 0 + vertex -99.9344 -97.6237 -3 + endloop + endfacet + facet normal -0.970367 -0.241637 0 + outer loop + vertex -99.8838 -97.8269 -3 + vertex -99.8838 -97.8269 0 + vertex -99.9344 -97.6237 0 + endloop + endfacet + facet normal -0.951045 -0.309054 0 + outer loop + vertex -99.8191 -98.026 -3 + vertex -99.8838 -97.8269 0 + vertex -99.8838 -97.8269 -3 + endloop + endfacet + facet normal -0.951045 -0.309054 0 + outer loop + vertex -99.8191 -98.026 -3 + vertex -99.8191 -98.026 0 + vertex -99.8838 -97.8269 0 + endloop + endfacet + facet normal -0.927121 -0.374763 0 + outer loop + vertex -99.7406 -98.2202 -3 + vertex -99.8191 -98.026 0 + vertex -99.8191 -98.026 -3 + endloop + endfacet + facet normal -0.927121 -0.374763 0 + outer loop + vertex -99.7406 -98.2202 -3 + vertex -99.7406 -98.2202 0 + vertex -99.8191 -98.026 0 + endloop + endfacet + facet normal -0.898778 -0.438405 0 + outer loop + vertex -99.6488 -98.4084 -3 + vertex -99.7406 -98.2202 0 + vertex -99.7406 -98.2202 -3 + endloop + endfacet + facet normal -0.898778 -0.438405 0 + outer loop + vertex -99.6488 -98.4084 -3 + vertex -99.6488 -98.4084 0 + vertex -99.7406 -98.2202 0 + endloop + endfacet + facet normal -0.86609 -0.499888 0 + outer loop + vertex -99.5441 -98.5898 -3 + vertex -99.6488 -98.4084 0 + vertex -99.6488 -98.4084 -3 + endloop + endfacet + facet normal -0.86609 -0.499888 0 + outer loop + vertex -99.5441 -98.5898 -3 + vertex -99.5441 -98.5898 0 + vertex -99.6488 -98.4084 0 + endloop + endfacet + facet normal -0.829026 -0.55921 0 + outer loop + vertex -99.427 -98.7634 -3 + vertex -99.5441 -98.5898 0 + vertex -99.5441 -98.5898 -3 + endloop + endfacet + facet normal -0.829026 -0.55921 0 + outer loop + vertex -99.427 -98.7634 -3 + vertex -99.427 -98.7634 0 + vertex -99.5441 -98.5898 0 + endloop + endfacet + facet normal -0.788039 -0.615626 0 + outer loop + vertex -99.2981 -98.9284 -3 + vertex -99.427 -98.7634 0 + vertex -99.427 -98.7634 -3 + endloop + endfacet + facet normal -0.788039 -0.615626 0 + outer loop + vertex -99.2981 -98.9284 -3 + vertex -99.2981 -98.9284 0 + vertex -99.427 -98.7634 0 + endloop + endfacet + facet normal -0.743152 -0.669123 0 + outer loop + vertex -99.158 -99.084 -3 + vertex -99.2981 -98.9284 0 + vertex -99.2981 -98.9284 -3 + endloop + endfacet + facet normal -0.743152 -0.669123 0 + outer loop + vertex -99.158 -99.084 -3 + vertex -99.158 -99.084 0 + vertex -99.2981 -98.9284 0 + endloop + endfacet + facet normal -0.694577 -0.719418 0 + outer loop + vertex -99.0074 -99.2294 -3 + vertex -99.158 -99.084 0 + vertex -99.158 -99.084 -3 + endloop + endfacet + facet normal -0.694577 -0.719418 0 + outer loop + vertex -99.0074 -99.2294 -3 + vertex -99.0074 -99.2294 0 + vertex -99.158 -99.084 0 + endloop + endfacet + facet normal -0.642811 -0.766025 0 + outer loop + vertex -98.847 -99.364 -3 + vertex -99.0074 -99.2294 0 + vertex -99.0074 -99.2294 -3 + endloop + endfacet + facet normal -0.642811 -0.766025 0 + outer loop + vertex -98.847 -99.364 -3 + vertex -98.847 -99.364 0 + vertex -99.0074 -99.2294 0 + endloop + endfacet + facet normal -0.587859 -0.808963 0 + outer loop + vertex -98.6776 -99.4871 -3 + vertex -98.847 -99.364 0 + vertex -98.847 -99.364 -3 + endloop + endfacet + facet normal -0.587859 -0.808963 0 + outer loop + vertex -98.6776 -99.4871 -3 + vertex -98.6776 -99.4871 0 + vertex -98.847 -99.364 0 + endloop + endfacet + facet normal -0.529999 -0.847998 0 + outer loop + vertex -98.5 -99.5981 -3 + vertex -98.6776 -99.4871 0 + vertex -98.6776 -99.4871 -3 + endloop + endfacet + facet normal -0.529999 -0.847998 0 + outer loop + vertex -98.5 -99.5981 -3 + vertex -98.5 -99.5981 0 + vertex -98.6776 -99.4871 0 + endloop + endfacet + facet normal -0.469423 -0.882973 0 + outer loop + vertex -98.3151 -99.6964 -3 + vertex -98.5 -99.5981 0 + vertex -98.5 -99.5981 -3 + endloop + endfacet + facet normal -0.469423 -0.882973 0 + outer loop + vertex -98.3151 -99.6964 -3 + vertex -98.3151 -99.6964 0 + vertex -98.5 -99.5981 0 + endloop + endfacet + facet normal -0.406449 -0.913674 0 + outer loop + vertex -98.1238 -99.7815 -3 + vertex -98.3151 -99.6964 0 + vertex -98.3151 -99.6964 -3 + endloop + endfacet + facet normal -0.406449 -0.913674 0 + outer loop + vertex -98.1238 -99.7815 -3 + vertex -98.1238 -99.7815 0 + vertex -98.3151 -99.6964 0 + endloop + endfacet + facet normal -0.342318 -0.939584 0 + outer loop + vertex -97.927 -99.8532 -3 + vertex -98.1238 -99.7815 0 + vertex -98.1238 -99.7815 -3 + endloop + endfacet + facet normal -0.342318 -0.939584 0 + outer loop + vertex -97.927 -99.8532 -3 + vertex -97.927 -99.8532 0 + vertex -98.1238 -99.7815 0 + endloop + endfacet + facet normal -0.275668 -0.961253 0 + outer loop + vertex -97.7258 -99.9109 -3 + vertex -97.927 -99.8532 0 + vertex -97.927 -99.8532 -3 + endloop + endfacet + facet normal -0.275668 -0.961253 0 + outer loop + vertex -97.7258 -99.9109 -3 + vertex -97.7258 -99.9109 0 + vertex -97.927 -99.8532 0 + endloop + endfacet + facet normal -0.20767 -0.978199 0 + outer loop + vertex -97.5209 -99.9544 -3 + vertex -97.7258 -99.9109 0 + vertex -97.7258 -99.9109 -3 + endloop + endfacet + facet normal -0.20767 -0.978199 0 + outer loop + vertex -97.5209 -99.9544 -3 + vertex -97.5209 -99.9544 0 + vertex -97.7258 -99.9109 0 + endloop + endfacet + facet normal -0.139482 -0.990225 0 + outer loop + vertex -97.3136 -99.9836 -3 + vertex -97.5209 -99.9544 0 + vertex -97.5209 -99.9544 -3 + endloop + endfacet + facet normal -0.139482 -0.990225 0 + outer loop + vertex -97.3136 -99.9836 -3 + vertex -97.3136 -99.9836 0 + vertex -97.5209 -99.9544 0 + endloop + endfacet + facet normal -0.0697198 -0.997567 0 + outer loop + vertex -97.1047 -99.9982 -3 + vertex -97.3136 -99.9836 0 + vertex -97.3136 -99.9836 -3 + endloop + endfacet + facet normal -0.0697198 -0.997567 0 + outer loop + vertex -97.1047 -99.9982 -3 + vertex -97.1047 -99.9982 0 + vertex -97.3136 -99.9836 0 + endloop + endfacet + facet normal 0 -1 0 + outer loop + vertex -97.0018 -99.9982 -3 + vertex -97.1047 -99.9982 0 + vertex -97.1047 -99.9982 -3 + endloop + endfacet + facet normal 0 -1 0 + outer loop + vertex -97.0018 -99.9982 -3 + vertex -97.0018 -99.9982 0 + vertex -97.1047 -99.9982 0 + endloop + endfacet + facet normal -0.707107 -0.707107 0 + outer loop + vertex -97 -100 -3 + vertex -97.0018 -99.9982 0 + vertex -97.0018 -99.9982 -3 + endloop + endfacet + facet normal -0.707107 -0.707107 0 + outer loop + vertex -97 -100 -3 + vertex -97 -100 0 + vertex -97.0018 -99.9982 0 + endloop + endfacet + facet normal 0 -1 0 + outer loop + vertex 97 -100 -3 + vertex -97 -100 0 + vertex -97 -100 -3 + endloop + endfacet + facet normal 0 -1 0 + outer loop + vertex 97 -100 -3 + vertex 97 -100 0 + vertex -97 -100 0 + endloop + endfacet + facet normal 0.707107 -0.707107 0 + outer loop + vertex 97.0018 -99.9982 -3 + vertex 97 -100 0 + vertex 97 -100 -3 + endloop + endfacet + facet normal 0.707107 -0.707107 0 + outer loop + vertex 97.0018 -99.9982 -3 + vertex 97.0018 -99.9982 0 + vertex 97 -100 0 + endloop + endfacet +endsolid OpenSCAD_Model diff --git a/resources/profiles/Creality/cr5pro.svg b/resources/profiles/Creality/cr5pro.svg new file mode 100644 index 0000000000..7f44fd6020 --- /dev/null +++ b/resources/profiles/Creality/cr5pro.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/profiles/Creality/cr5pro_bed.stl b/resources/profiles/Creality/cr5pro_bed.stl new file mode 100644 index 0000000000..b3e13687bb --- /dev/null +++ b/resources/profiles/Creality/cr5pro_bed.stl @@ -0,0 +1,2774 @@ +solid OpenSCAD_Model + facet normal 0 0 -1 + outer loop + vertex 147.105 -112.498 -3 + vertex 147.002 -112.498 -3 + vertex 147.314 -112.484 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 147.314 -112.484 -3 + vertex 147.002 -112.498 -3 + vertex 147.521 -112.454 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 150 -109.5 -3 + vertex 147.002 -112.498 -3 + vertex 147.002 112.498 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 147.521 -112.454 -3 + vertex 147.002 -112.498 -3 + vertex 147.726 -112.411 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 147.726 -112.411 -3 + vertex 147.002 -112.498 -3 + vertex 147.927 -112.353 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 147.927 -112.353 -3 + vertex 147.002 -112.498 -3 + vertex 148.124 -112.282 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 148.124 -112.282 -3 + vertex 147.002 -112.498 -3 + vertex 148.315 -112.196 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 148.315 -112.196 -3 + vertex 147.002 -112.498 -3 + vertex 148.5 -112.098 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 148.5 -112.098 -3 + vertex 147.002 -112.498 -3 + vertex 148.678 -111.987 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 148.678 -111.987 -3 + vertex 147.002 -112.498 -3 + vertex 148.847 -111.864 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 148.847 -111.864 -3 + vertex 147.002 -112.498 -3 + vertex 149.007 -111.729 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 149.007 -111.729 -3 + vertex 147.002 -112.498 -3 + vertex 149.158 -111.584 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 149.158 -111.584 -3 + vertex 147.002 -112.498 -3 + vertex 149.298 -111.428 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 149.298 -111.428 -3 + vertex 147.002 -112.498 -3 + vertex 149.427 -111.263 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 149.427 -111.263 -3 + vertex 147.002 -112.498 -3 + vertex 149.544 -111.09 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 149.544 -111.09 -3 + vertex 147.002 -112.498 -3 + vertex 149.649 -110.908 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 149.649 -110.908 -3 + vertex 147.002 -112.498 -3 + vertex 149.741 -110.72 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 149.741 -110.72 -3 + vertex 147.002 -112.498 -3 + vertex 149.819 -110.526 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 149.819 -110.526 -3 + vertex 147.002 -112.498 -3 + vertex 149.884 -110.327 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 149.884 -110.327 -3 + vertex 147.002 -112.498 -3 + vertex 149.934 -110.124 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 149.934 -110.124 -3 + vertex 147.002 -112.498 -3 + vertex 149.971 -109.918 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 149.971 -109.918 -3 + vertex 147.002 -112.498 -3 + vertex 149.993 -109.709 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 149.993 -109.709 -3 + vertex 147.002 -112.498 -3 + vertex 150 -109.5 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 150 -109.5 -3 + vertex 147.002 112.498 -3 + vertex 150 109.5 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 147.002 112.498 -3 + vertex 149.971 109.918 -3 + vertex 149.993 109.709 -3 + endloop + endfacet + facet normal -0 -0 -1 + outer loop + vertex 149.884 110.327 -3 + vertex 149.934 110.124 -3 + vertex 147.002 112.498 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 149.741 110.72 -3 + vertex 147.002 112.498 -3 + vertex 149.649 110.908 -3 + endloop + endfacet + facet normal -0 -0 -1 + outer loop + vertex 149.741 110.72 -3 + vertex 149.819 110.526 -3 + vertex 147.002 112.498 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 147.002 112.498 -3 + vertex 149.007 111.729 -3 + vertex 149.158 111.584 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 149.427 111.263 -3 + vertex 147.002 112.498 -3 + vertex 149.298 111.428 -3 + endloop + endfacet + facet normal -0 -0 -1 + outer loop + vertex 149.427 111.263 -3 + vertex 149.544 111.09 -3 + vertex 147.002 112.498 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 149.298 111.428 -3 + vertex 147.002 112.498 -3 + vertex 149.158 111.584 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 147.002 112.498 -3 + vertex 147.927 112.353 -3 + vertex 148.124 112.282 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 148.847 111.864 -3 + vertex 147.002 112.498 -3 + vertex 148.678 111.987 -3 + endloop + endfacet + facet normal -0 -0 -1 + outer loop + vertex 148.847 111.864 -3 + vertex 149.007 111.729 -3 + vertex 147.002 112.498 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 148.678 111.987 -3 + vertex 147.002 112.498 -3 + vertex 148.5 112.098 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 148.5 112.098 -3 + vertex 147.002 112.498 -3 + vertex 148.315 112.196 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 148.315 112.196 -3 + vertex 147.002 112.498 -3 + vertex 148.124 112.282 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 149.649 110.908 -3 + vertex 147.002 112.498 -3 + vertex 149.544 111.09 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 147.726 112.411 -3 + vertex 147.002 112.498 -3 + vertex 147.521 112.454 -3 + endloop + endfacet + facet normal -0 -0 -1 + outer loop + vertex 147.726 112.411 -3 + vertex 147.927 112.353 -3 + vertex 147.002 112.498 -3 + endloop + endfacet + facet normal -0 -0 -1 + outer loop + vertex 149.819 110.526 -3 + vertex 149.884 110.327 -3 + vertex 147.002 112.498 -3 + endloop + endfacet + facet normal -0 -0 -1 + outer loop + vertex 147.314 112.484 -3 + vertex 147.521 112.454 -3 + vertex 147.002 112.498 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 147.002 112.498 -3 + vertex 149.934 110.124 -3 + vertex 149.971 109.918 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 147.002 112.498 -3 + vertex 147.105 112.498 -3 + vertex 147.314 112.484 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 150 109.5 -3 + vertex 147.002 112.498 -3 + vertex 149.993 109.709 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 147.002 -112.498 -3 + vertex 147 112.5 -3 + vertex 147.002 112.498 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex 147.002 -112.498 -3 + vertex 147 -112.5 -3 + vertex 147 112.5 -3 + endloop + endfacet + facet normal -0 -0 -1 + outer loop + vertex 147 112.5 -3 + vertex 147 -112.5 -3 + vertex -147 112.5 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 -112.498 -3 + vertex -150 109.5 -3 + vertex -147.002 112.498 -3 + endloop + endfacet + facet normal -0 0 -1 + outer loop + vertex -147.002 112.498 -3 + vertex -147.314 112.484 -3 + vertex -147.105 112.498 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 112.498 -3 + vertex -147.521 112.454 -3 + vertex -147.314 112.484 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 112.498 -3 + vertex -147.726 112.411 -3 + vertex -147.521 112.454 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 112.498 -3 + vertex -147.927 112.353 -3 + vertex -147.726 112.411 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 112.498 -3 + vertex -148.124 112.282 -3 + vertex -147.927 112.353 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 112.498 -3 + vertex -148.315 112.196 -3 + vertex -148.124 112.282 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 112.498 -3 + vertex -148.5 112.098 -3 + vertex -148.315 112.196 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 112.498 -3 + vertex -148.678 111.987 -3 + vertex -148.5 112.098 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 112.498 -3 + vertex -148.847 111.864 -3 + vertex -148.678 111.987 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 112.498 -3 + vertex -149.007 111.729 -3 + vertex -148.847 111.864 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 112.498 -3 + vertex -149.158 111.584 -3 + vertex -149.007 111.729 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 112.498 -3 + vertex -149.298 111.428 -3 + vertex -149.158 111.584 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 112.498 -3 + vertex -149.427 111.263 -3 + vertex -149.298 111.428 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 112.498 -3 + vertex -149.544 111.09 -3 + vertex -149.427 111.263 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 112.498 -3 + vertex -149.649 110.908 -3 + vertex -149.544 111.09 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 112.498 -3 + vertex -149.741 110.72 -3 + vertex -149.649 110.908 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 112.498 -3 + vertex -149.819 110.526 -3 + vertex -149.741 110.72 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 112.498 -3 + vertex -149.884 110.327 -3 + vertex -149.819 110.526 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 112.498 -3 + vertex -149.934 110.124 -3 + vertex -149.884 110.327 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 112.498 -3 + vertex -149.971 109.918 -3 + vertex -149.934 110.124 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 112.498 -3 + vertex -149.993 109.709 -3 + vertex -149.971 109.918 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 112.498 -3 + vertex -150 109.5 -3 + vertex -149.993 109.709 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 -112.498 -3 + vertex -150 -109.5 -3 + vertex -150 109.5 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.521 -112.454 -3 + vertex -147.002 -112.498 -3 + vertex -147.314 -112.484 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 -112.498 -3 + vertex -149.971 -109.918 -3 + vertex -149.993 -109.709 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -149.884 -110.327 -3 + vertex -149.934 -110.124 -3 + vertex -147.002 -112.498 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -149.741 -110.72 -3 + vertex -147.002 -112.498 -3 + vertex -149.649 -110.908 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -149.741 -110.72 -3 + vertex -149.819 -110.526 -3 + vertex -147.002 -112.498 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 -112.498 -3 + vertex -149.007 -111.729 -3 + vertex -149.158 -111.584 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -149.427 -111.263 -3 + vertex -147.002 -112.498 -3 + vertex -149.298 -111.428 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -149.427 -111.263 -3 + vertex -149.544 -111.09 -3 + vertex -147.002 -112.498 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -149.298 -111.428 -3 + vertex -147.002 -112.498 -3 + vertex -149.158 -111.584 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 -112.498 -3 + vertex -147.927 -112.353 -3 + vertex -148.124 -112.282 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -148.847 -111.864 -3 + vertex -147.002 -112.498 -3 + vertex -148.678 -111.987 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -148.847 -111.864 -3 + vertex -149.007 -111.729 -3 + vertex -147.002 -112.498 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -148.678 -111.987 -3 + vertex -147.002 -112.498 -3 + vertex -148.5 -112.098 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -148.5 -112.098 -3 + vertex -147.002 -112.498 -3 + vertex -148.315 -112.196 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -148.315 -112.196 -3 + vertex -147.002 -112.498 -3 + vertex -148.124 -112.282 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -149.649 -110.908 -3 + vertex -147.002 -112.498 -3 + vertex -149.544 -111.09 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.726 -112.411 -3 + vertex -147.002 -112.498 -3 + vertex -147.521 -112.454 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.726 -112.411 -3 + vertex -147.927 -112.353 -3 + vertex -147.002 -112.498 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -149.819 -110.526 -3 + vertex -149.884 -110.327 -3 + vertex -147.002 -112.498 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -149.934 -110.124 -3 + vertex -149.971 -109.918 -3 + vertex -147.002 -112.498 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.105 -112.498 -3 + vertex -147.314 -112.484 -3 + vertex -147.002 -112.498 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147.002 -112.498 -3 + vertex -149.993 -109.709 -3 + vertex -150 -109.5 -3 + endloop + endfacet + facet normal 0 -0 -1 + outer loop + vertex -147 112.5 -3 + vertex -147 -112.5 -3 + vertex -147.002 112.498 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147 112.5 -3 + vertex 147 -112.5 -3 + vertex -147 -112.5 -3 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex -147 -112.5 -3 + vertex -147.002 -112.498 -3 + vertex -147.002 112.498 -3 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 147.314 -112.484 0 + vertex 147.002 -112.498 0 + vertex 147.105 -112.498 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 147.521 -112.454 0 + vertex 147.002 -112.498 0 + vertex 147.314 -112.484 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 147.002 112.498 0 + vertex 147.002 -112.498 0 + vertex 150 -109.5 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 147.726 -112.411 0 + vertex 147.002 -112.498 0 + vertex 147.521 -112.454 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 147.927 -112.353 0 + vertex 147.002 -112.498 0 + vertex 147.726 -112.411 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 148.124 -112.282 0 + vertex 147.002 -112.498 0 + vertex 147.927 -112.353 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 148.315 -112.196 0 + vertex 147.002 -112.498 0 + vertex 148.124 -112.282 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 148.5 -112.098 0 + vertex 147.002 -112.498 0 + vertex 148.315 -112.196 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 148.678 -111.987 0 + vertex 147.002 -112.498 0 + vertex 148.5 -112.098 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 148.847 -111.864 0 + vertex 147.002 -112.498 0 + vertex 148.678 -111.987 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 149.007 -111.729 0 + vertex 147.002 -112.498 0 + vertex 148.847 -111.864 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 149.158 -111.584 0 + vertex 147.002 -112.498 0 + vertex 149.007 -111.729 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 149.298 -111.428 0 + vertex 147.002 -112.498 0 + vertex 149.158 -111.584 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 149.427 -111.263 0 + vertex 147.002 -112.498 0 + vertex 149.298 -111.428 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 149.544 -111.09 0 + vertex 147.002 -112.498 0 + vertex 149.427 -111.263 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 149.649 -110.908 0 + vertex 147.002 -112.498 0 + vertex 149.544 -111.09 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 149.741 -110.72 0 + vertex 147.002 -112.498 0 + vertex 149.649 -110.908 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 149.819 -110.526 0 + vertex 147.002 -112.498 0 + vertex 149.741 -110.72 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 149.884 -110.327 0 + vertex 147.002 -112.498 0 + vertex 149.819 -110.526 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 149.934 -110.124 0 + vertex 147.002 -112.498 0 + vertex 149.884 -110.327 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 149.971 -109.918 0 + vertex 147.002 -112.498 0 + vertex 149.934 -110.124 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 149.993 -109.709 0 + vertex 147.002 -112.498 0 + vertex 149.971 -109.918 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 150 -109.5 0 + vertex 147.002 -112.498 0 + vertex 149.993 -109.709 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 150 109.5 0 + vertex 147.002 112.498 0 + vertex 150 -109.5 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 149.993 109.709 0 + vertex 149.971 109.918 0 + vertex 147.002 112.498 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 147.002 112.498 0 + vertex 149.934 110.124 0 + vertex 149.884 110.327 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 149.649 110.908 0 + vertex 147.002 112.498 0 + vertex 149.741 110.72 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 147.002 112.498 0 + vertex 149.819 110.526 0 + vertex 149.741 110.72 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 149.158 111.584 0 + vertex 149.007 111.729 0 + vertex 147.002 112.498 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 149.298 111.428 0 + vertex 147.002 112.498 0 + vertex 149.427 111.263 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 147.002 112.498 0 + vertex 149.544 111.09 0 + vertex 149.427 111.263 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 149.158 111.584 0 + vertex 147.002 112.498 0 + vertex 149.298 111.428 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 148.124 112.282 0 + vertex 147.927 112.353 0 + vertex 147.002 112.498 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 148.678 111.987 0 + vertex 147.002 112.498 0 + vertex 148.847 111.864 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 147.002 112.498 0 + vertex 149.007 111.729 0 + vertex 148.847 111.864 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 148.5 112.098 0 + vertex 147.002 112.498 0 + vertex 148.678 111.987 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 148.315 112.196 0 + vertex 147.002 112.498 0 + vertex 148.5 112.098 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 148.124 112.282 0 + vertex 147.002 112.498 0 + vertex 148.315 112.196 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 149.544 111.09 0 + vertex 147.002 112.498 0 + vertex 149.649 110.908 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 147.521 112.454 0 + vertex 147.002 112.498 0 + vertex 147.726 112.411 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 147.002 112.498 0 + vertex 147.927 112.353 0 + vertex 147.726 112.411 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 147.002 112.498 0 + vertex 149.884 110.327 0 + vertex 149.819 110.526 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 147.002 112.498 0 + vertex 147.521 112.454 0 + vertex 147.314 112.484 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 149.971 109.918 0 + vertex 149.934 110.124 0 + vertex 147.002 112.498 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 147.314 112.484 0 + vertex 147.105 112.498 0 + vertex 147.002 112.498 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 149.993 109.709 0 + vertex 147.002 112.498 0 + vertex 150 109.5 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 147.002 112.498 0 + vertex 147 112.5 0 + vertex 147.002 -112.498 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 147 112.5 0 + vertex 147 -112.5 0 + vertex 147.002 -112.498 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -147 112.5 0 + vertex 147 -112.5 0 + vertex 147 112.5 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -147.002 112.498 0 + vertex -150 109.5 0 + vertex -147.002 -112.498 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -147.105 112.498 0 + vertex -147.314 112.484 0 + vertex -147.002 112.498 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -147.314 112.484 0 + vertex -147.521 112.454 0 + vertex -147.002 112.498 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -147.521 112.454 0 + vertex -147.726 112.411 0 + vertex -147.002 112.498 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -147.726 112.411 0 + vertex -147.927 112.353 0 + vertex -147.002 112.498 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -147.927 112.353 0 + vertex -148.124 112.282 0 + vertex -147.002 112.498 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -148.124 112.282 0 + vertex -148.315 112.196 0 + vertex -147.002 112.498 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -148.315 112.196 0 + vertex -148.5 112.098 0 + vertex -147.002 112.498 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -148.5 112.098 0 + vertex -148.678 111.987 0 + vertex -147.002 112.498 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -148.678 111.987 0 + vertex -148.847 111.864 0 + vertex -147.002 112.498 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -148.847 111.864 0 + vertex -149.007 111.729 0 + vertex -147.002 112.498 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -149.007 111.729 0 + vertex -149.158 111.584 0 + vertex -147.002 112.498 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -149.158 111.584 0 + vertex -149.298 111.428 0 + vertex -147.002 112.498 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -149.298 111.428 0 + vertex -149.427 111.263 0 + vertex -147.002 112.498 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -149.427 111.263 0 + vertex -149.544 111.09 0 + vertex -147.002 112.498 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -149.544 111.09 0 + vertex -149.649 110.908 0 + vertex -147.002 112.498 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -149.649 110.908 0 + vertex -149.741 110.72 0 + vertex -147.002 112.498 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -149.741 110.72 0 + vertex -149.819 110.526 0 + vertex -147.002 112.498 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -149.819 110.526 0 + vertex -149.884 110.327 0 + vertex -147.002 112.498 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -149.884 110.327 0 + vertex -149.934 110.124 0 + vertex -147.002 112.498 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -149.934 110.124 0 + vertex -149.971 109.918 0 + vertex -147.002 112.498 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -149.971 109.918 0 + vertex -149.993 109.709 0 + vertex -147.002 112.498 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -149.993 109.709 0 + vertex -150 109.5 0 + vertex -147.002 112.498 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -150 109.5 0 + vertex -150 -109.5 0 + vertex -147.002 -112.498 0 + endloop + endfacet + facet normal -0 -0 1 + outer loop + vertex -147.314 -112.484 0 + vertex -147.002 -112.498 0 + vertex -147.521 -112.454 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -149.993 -109.709 0 + vertex -149.971 -109.918 0 + vertex -147.002 -112.498 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -147.002 -112.498 0 + vertex -149.934 -110.124 0 + vertex -149.884 -110.327 0 + endloop + endfacet + facet normal -0 -0 1 + outer loop + vertex -149.649 -110.908 0 + vertex -147.002 -112.498 0 + vertex -149.741 -110.72 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -147.002 -112.498 0 + vertex -149.819 -110.526 0 + vertex -149.741 -110.72 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -149.158 -111.584 0 + vertex -149.007 -111.729 0 + vertex -147.002 -112.498 0 + endloop + endfacet + facet normal -0 -0 1 + outer loop + vertex -149.298 -111.428 0 + vertex -147.002 -112.498 0 + vertex -149.427 -111.263 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -147.002 -112.498 0 + vertex -149.544 -111.09 0 + vertex -149.427 -111.263 0 + endloop + endfacet + facet normal -0 -0 1 + outer loop + vertex -149.158 -111.584 0 + vertex -147.002 -112.498 0 + vertex -149.298 -111.428 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -148.124 -112.282 0 + vertex -147.927 -112.353 0 + vertex -147.002 -112.498 0 + endloop + endfacet + facet normal -0 -0 1 + outer loop + vertex -148.678 -111.987 0 + vertex -147.002 -112.498 0 + vertex -148.847 -111.864 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -147.002 -112.498 0 + vertex -149.007 -111.729 0 + vertex -148.847 -111.864 0 + endloop + endfacet + facet normal -0 -0 1 + outer loop + vertex -148.5 -112.098 0 + vertex -147.002 -112.498 0 + vertex -148.678 -111.987 0 + endloop + endfacet + facet normal -0 -0 1 + outer loop + vertex -148.315 -112.196 0 + vertex -147.002 -112.498 0 + vertex -148.5 -112.098 0 + endloop + endfacet + facet normal -0 -0 1 + outer loop + vertex -148.124 -112.282 0 + vertex -147.002 -112.498 0 + vertex -148.315 -112.196 0 + endloop + endfacet + facet normal -0 -0 1 + outer loop + vertex -149.544 -111.09 0 + vertex -147.002 -112.498 0 + vertex -149.649 -110.908 0 + endloop + endfacet + facet normal -0 -0 1 + outer loop + vertex -147.521 -112.454 0 + vertex -147.002 -112.498 0 + vertex -147.726 -112.411 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -147.002 -112.498 0 + vertex -147.927 -112.353 0 + vertex -147.726 -112.411 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -147.002 -112.498 0 + vertex -149.884 -110.327 0 + vertex -149.819 -110.526 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -147.002 -112.498 0 + vertex -149.971 -109.918 0 + vertex -149.934 -110.124 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -147.002 -112.498 0 + vertex -147.314 -112.484 0 + vertex -147.105 -112.498 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -150 -109.5 0 + vertex -149.993 -109.709 0 + vertex -147.002 -112.498 0 + endloop + endfacet + facet normal -0 0 1 + outer loop + vertex -147.002 112.498 0 + vertex -147 -112.5 0 + vertex -147 112.5 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -147 -112.5 0 + vertex 147 -112.5 0 + vertex -147 112.5 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex -147.002 112.498 0 + vertex -147.002 -112.498 0 + vertex -147 -112.5 0 + endloop + endfacet + facet normal 0 -1 0 + outer loop + vertex 147.105 -112.498 -3 + vertex 147.002 -112.498 0 + vertex 147.002 -112.498 -3 + endloop + endfacet + facet normal 0 -1 0 + outer loop + vertex 147.105 -112.498 -3 + vertex 147.105 -112.498 0 + vertex 147.002 -112.498 0 + endloop + endfacet + facet normal 0.0668359 -0.997764 0 + outer loop + vertex 147.314 -112.484 -3 + vertex 147.105 -112.498 0 + vertex 147.105 -112.498 -3 + endloop + endfacet + facet normal 0.0668359 -0.997764 0 + outer loop + vertex 147.314 -112.484 -3 + vertex 147.314 -112.484 0 + vertex 147.105 -112.498 0 + endloop + endfacet + facet normal 0.143429 -0.989661 0 + outer loop + vertex 147.521 -112.454 -3 + vertex 147.314 -112.484 0 + vertex 147.314 -112.484 -3 + endloop + endfacet + facet normal 0.143429 -0.989661 0 + outer loop + vertex 147.521 -112.454 -3 + vertex 147.521 -112.454 0 + vertex 147.314 -112.484 0 + endloop + endfacet + facet normal 0.205289 -0.978701 0 + outer loop + vertex 147.726 -112.411 -3 + vertex 147.521 -112.454 0 + vertex 147.521 -112.454 -3 + endloop + endfacet + facet normal 0.205289 -0.978701 0 + outer loop + vertex 147.726 -112.411 -3 + vertex 147.726 -112.411 0 + vertex 147.521 -112.454 0 + endloop + endfacet + facet normal 0.277246 -0.960799 0 + outer loop + vertex 147.927 -112.353 -3 + vertex 147.726 -112.411 0 + vertex 147.726 -112.411 -3 + endloop + endfacet + facet normal 0.277246 -0.960799 0 + outer loop + vertex 147.927 -112.353 -3 + vertex 147.927 -112.353 0 + vertex 147.726 -112.411 0 + endloop + endfacet + facet normal 0.339058 -0.940766 0 + outer loop + vertex 148.124 -112.282 -3 + vertex 147.927 -112.353 0 + vertex 147.927 -112.353 -3 + endloop + endfacet + facet normal 0.339058 -0.940766 0 + outer loop + vertex 148.124 -112.282 -3 + vertex 148.124 -112.282 0 + vertex 147.927 -112.353 0 + endloop + endfacet + facet normal 0.410563 -0.911832 0 + outer loop + vertex 148.315 -112.196 -3 + vertex 148.124 -112.282 0 + vertex 148.124 -112.282 -3 + endloop + endfacet + facet normal 0.410563 -0.911832 0 + outer loop + vertex 148.315 -112.196 -3 + vertex 148.315 -112.196 0 + vertex 148.124 -112.282 0 + endloop + endfacet + facet normal 0.468107 -0.883672 0 + outer loop + vertex 148.5 -112.098 -3 + vertex 148.315 -112.196 0 + vertex 148.315 -112.196 -3 + endloop + endfacet + facet normal 0.468107 -0.883672 0 + outer loop + vertex 148.5 -112.098 -3 + vertex 148.5 -112.098 0 + vertex 148.315 -112.196 0 + endloop + endfacet + facet normal 0.529142 -0.848533 0 + outer loop + vertex 148.678 -111.987 -3 + vertex 148.5 -112.098 0 + vertex 148.5 -112.098 -3 + endloop + endfacet + facet normal 0.529142 -0.848533 0 + outer loop + vertex 148.678 -111.987 -3 + vertex 148.678 -111.987 0 + vertex 148.5 -112.098 0 + endloop + endfacet + facet normal 0.588456 -0.808529 0 + outer loop + vertex 148.847 -111.864 -3 + vertex 148.678 -111.987 0 + vertex 148.678 -111.987 -3 + endloop + endfacet + facet normal 0.588456 -0.808529 0 + outer loop + vertex 148.847 -111.864 -3 + vertex 148.847 -111.864 0 + vertex 148.678 -111.987 0 + endloop + endfacet + facet normal 0.644871 -0.764291 0 + outer loop + vertex 149.007 -111.729 -3 + vertex 148.847 -111.864 0 + vertex 148.847 -111.864 -3 + endloop + endfacet + facet normal 0.644871 -0.764291 0 + outer loop + vertex 149.007 -111.729 -3 + vertex 149.007 -111.729 0 + vertex 148.847 -111.864 0 + endloop + endfacet + facet normal 0.692631 -0.721292 0 + outer loop + vertex 149.158 -111.584 -3 + vertex 149.007 -111.729 0 + vertex 149.007 -111.729 -3 + endloop + endfacet + facet normal 0.692631 -0.721292 0 + outer loop + vertex 149.158 -111.584 -3 + vertex 149.158 -111.584 0 + vertex 149.007 -111.729 0 + endloop + endfacet + facet normal 0.744242 -0.66791 0 + outer loop + vertex 149.298 -111.428 -3 + vertex 149.158 -111.584 0 + vertex 149.158 -111.584 -3 + endloop + endfacet + facet normal 0.744242 -0.66791 0 + outer loop + vertex 149.298 -111.428 -3 + vertex 149.298 -111.428 0 + vertex 149.158 -111.584 0 + endloop + endfacet + facet normal 0.787807 -0.615922 0 + outer loop + vertex 149.427 -111.263 -3 + vertex 149.298 -111.428 0 + vertex 149.298 -111.428 -3 + endloop + endfacet + facet normal 0.787807 -0.615922 0 + outer loop + vertex 149.427 -111.263 -3 + vertex 149.427 -111.263 0 + vertex 149.298 -111.428 0 + endloop + endfacet + facet normal 0.828349 -0.560213 0 + outer loop + vertex 149.544 -111.09 -3 + vertex 149.427 -111.263 0 + vertex 149.427 -111.263 -3 + endloop + endfacet + facet normal 0.828349 -0.560213 0 + outer loop + vertex 149.544 -111.09 -3 + vertex 149.544 -111.09 0 + vertex 149.427 -111.263 0 + endloop + endfacet + facet normal 0.866186 -0.499722 0 + outer loop + vertex 149.649 -110.908 -3 + vertex 149.544 -111.09 0 + vertex 149.544 -111.09 -3 + endloop + endfacet + facet normal 0.866186 -0.499722 0 + outer loop + vertex 149.649 -110.908 -3 + vertex 149.649 -110.908 0 + vertex 149.544 -111.09 0 + endloop + endfacet + facet normal 0.898217 -0.439553 0 + outer loop + vertex 149.741 -110.72 -3 + vertex 149.649 -110.908 0 + vertex 149.649 -110.908 -3 + endloop + endfacet + facet normal 0.898217 -0.439553 0 + outer loop + vertex 149.741 -110.72 -3 + vertex 149.741 -110.72 0 + vertex 149.649 -110.908 0 + endloop + endfacet + facet normal 0.927816 -0.373039 0 + outer loop + vertex 149.819 -110.526 -3 + vertex 149.741 -110.72 0 + vertex 149.741 -110.72 -3 + endloop + endfacet + facet normal 0.927816 -0.373039 0 + outer loop + vertex 149.819 -110.526 -3 + vertex 149.819 -110.526 0 + vertex 149.741 -110.72 0 + endloop + endfacet + facet normal 0.950577 -0.31049 0 + outer loop + vertex 149.884 -110.327 -3 + vertex 149.819 -110.526 0 + vertex 149.819 -110.526 -3 + endloop + endfacet + facet normal 0.950577 -0.31049 0 + outer loop + vertex 149.884 -110.327 -3 + vertex 149.884 -110.327 0 + vertex 149.819 -110.526 0 + endloop + endfacet + facet normal 0.970981 -0.239158 0 + outer loop + vertex 149.934 -110.124 -3 + vertex 149.884 -110.327 0 + vertex 149.884 -110.327 -3 + endloop + endfacet + facet normal 0.970981 -0.239158 0 + outer loop + vertex 149.934 -110.124 -3 + vertex 149.934 -110.124 0 + vertex 149.884 -110.327 0 + endloop + endfacet + facet normal 0.98425 -0.176783 0 + outer loop + vertex 149.971 -109.918 -3 + vertex 149.934 -110.124 0 + vertex 149.934 -110.124 -3 + endloop + endfacet + facet normal 0.98425 -0.176783 0 + outer loop + vertex 149.971 -109.918 -3 + vertex 149.971 -109.918 0 + vertex 149.934 -110.124 0 + endloop + endfacet + facet normal 0.994505 -0.104685 0 + outer loop + vertex 149.993 -109.709 -3 + vertex 149.971 -109.918 0 + vertex 149.971 -109.918 -3 + endloop + endfacet + facet normal 0.994505 -0.104685 0 + outer loop + vertex 149.993 -109.709 -3 + vertex 149.993 -109.709 0 + vertex 149.971 -109.918 0 + endloop + endfacet + facet normal 0.99944 -0.0334741 0 + outer loop + vertex 150 -109.5 -3 + vertex 149.993 -109.709 0 + vertex 149.993 -109.709 -3 + endloop + endfacet + facet normal 0.99944 -0.0334741 0 + outer loop + vertex 150 -109.5 -3 + vertex 150 -109.5 0 + vertex 149.993 -109.709 0 + endloop + endfacet + facet normal 1 0 0 + outer loop + vertex 150 109.5 -3 + vertex 150 -109.5 0 + vertex 150 -109.5 -3 + endloop + endfacet + facet normal 1 0 -0 + outer loop + vertex 150 109.5 -3 + vertex 150 109.5 0 + vertex 150 -109.5 0 + endloop + endfacet + facet normal 0.99944 0.0334741 0 + outer loop + vertex 149.993 109.709 -3 + vertex 150 109.5 0 + vertex 150 109.5 -3 + endloop + endfacet + facet normal 0.99944 0.0334741 -0 + outer loop + vertex 149.993 109.709 -3 + vertex 149.993 109.709 0 + vertex 150 109.5 0 + endloop + endfacet + facet normal 0.994505 0.104685 0 + outer loop + vertex 149.971 109.918 -3 + vertex 149.993 109.709 0 + vertex 149.993 109.709 -3 + endloop + endfacet + facet normal 0.994505 0.104685 -0 + outer loop + vertex 149.971 109.918 -3 + vertex 149.971 109.918 0 + vertex 149.993 109.709 0 + endloop + endfacet + facet normal 0.98425 0.176783 0 + outer loop + vertex 149.934 110.124 -3 + vertex 149.971 109.918 0 + vertex 149.971 109.918 -3 + endloop + endfacet + facet normal 0.98425 0.176783 -0 + outer loop + vertex 149.934 110.124 -3 + vertex 149.934 110.124 0 + vertex 149.971 109.918 0 + endloop + endfacet + facet normal 0.970981 0.239158 0 + outer loop + vertex 149.884 110.327 -3 + vertex 149.934 110.124 0 + vertex 149.934 110.124 -3 + endloop + endfacet + facet normal 0.970981 0.239158 -0 + outer loop + vertex 149.884 110.327 -3 + vertex 149.884 110.327 0 + vertex 149.934 110.124 0 + endloop + endfacet + facet normal 0.950577 0.31049 0 + outer loop + vertex 149.819 110.526 -3 + vertex 149.884 110.327 0 + vertex 149.884 110.327 -3 + endloop + endfacet + facet normal 0.950577 0.31049 -0 + outer loop + vertex 149.819 110.526 -3 + vertex 149.819 110.526 0 + vertex 149.884 110.327 0 + endloop + endfacet + facet normal 0.927816 0.373039 0 + outer loop + vertex 149.741 110.72 -3 + vertex 149.819 110.526 0 + vertex 149.819 110.526 -3 + endloop + endfacet + facet normal 0.927816 0.373039 -0 + outer loop + vertex 149.741 110.72 -3 + vertex 149.741 110.72 0 + vertex 149.819 110.526 0 + endloop + endfacet + facet normal 0.898217 0.439553 0 + outer loop + vertex 149.649 110.908 -3 + vertex 149.741 110.72 0 + vertex 149.741 110.72 -3 + endloop + endfacet + facet normal 0.898217 0.439553 -0 + outer loop + vertex 149.649 110.908 -3 + vertex 149.649 110.908 0 + vertex 149.741 110.72 0 + endloop + endfacet + facet normal 0.866186 0.499722 0 + outer loop + vertex 149.544 111.09 -3 + vertex 149.649 110.908 0 + vertex 149.649 110.908 -3 + endloop + endfacet + facet normal 0.866186 0.499722 -0 + outer loop + vertex 149.544 111.09 -3 + vertex 149.544 111.09 0 + vertex 149.649 110.908 0 + endloop + endfacet + facet normal 0.828349 0.560213 0 + outer loop + vertex 149.427 111.263 -3 + vertex 149.544 111.09 0 + vertex 149.544 111.09 -3 + endloop + endfacet + facet normal 0.828349 0.560213 -0 + outer loop + vertex 149.427 111.263 -3 + vertex 149.427 111.263 0 + vertex 149.544 111.09 0 + endloop + endfacet + facet normal 0.787807 0.615922 0 + outer loop + vertex 149.298 111.428 -3 + vertex 149.427 111.263 0 + vertex 149.427 111.263 -3 + endloop + endfacet + facet normal 0.787807 0.615922 -0 + outer loop + vertex 149.298 111.428 -3 + vertex 149.298 111.428 0 + vertex 149.427 111.263 0 + endloop + endfacet + facet normal 0.744242 0.66791 0 + outer loop + vertex 149.158 111.584 -3 + vertex 149.298 111.428 0 + vertex 149.298 111.428 -3 + endloop + endfacet + facet normal 0.744242 0.66791 -0 + outer loop + vertex 149.158 111.584 -3 + vertex 149.158 111.584 0 + vertex 149.298 111.428 0 + endloop + endfacet + facet normal 0.692631 0.721292 0 + outer loop + vertex 149.007 111.729 -3 + vertex 149.158 111.584 0 + vertex 149.158 111.584 -3 + endloop + endfacet + facet normal 0.692631 0.721292 -0 + outer loop + vertex 149.007 111.729 -3 + vertex 149.007 111.729 0 + vertex 149.158 111.584 0 + endloop + endfacet + facet normal 0.644871 0.764291 0 + outer loop + vertex 148.847 111.864 -3 + vertex 149.007 111.729 0 + vertex 149.007 111.729 -3 + endloop + endfacet + facet normal 0.644871 0.764291 -0 + outer loop + vertex 148.847 111.864 -3 + vertex 148.847 111.864 0 + vertex 149.007 111.729 0 + endloop + endfacet + facet normal 0.588456 0.808529 0 + outer loop + vertex 148.678 111.987 -3 + vertex 148.847 111.864 0 + vertex 148.847 111.864 -3 + endloop + endfacet + facet normal 0.588456 0.808529 -0 + outer loop + vertex 148.678 111.987 -3 + vertex 148.678 111.987 0 + vertex 148.847 111.864 0 + endloop + endfacet + facet normal 0.529142 0.848533 0 + outer loop + vertex 148.5 112.098 -3 + vertex 148.678 111.987 0 + vertex 148.678 111.987 -3 + endloop + endfacet + facet normal 0.529142 0.848533 -0 + outer loop + vertex 148.5 112.098 -3 + vertex 148.5 112.098 0 + vertex 148.678 111.987 0 + endloop + endfacet + facet normal 0.468107 0.883672 0 + outer loop + vertex 148.315 112.196 -3 + vertex 148.5 112.098 0 + vertex 148.5 112.098 -3 + endloop + endfacet + facet normal 0.468107 0.883672 -0 + outer loop + vertex 148.315 112.196 -3 + vertex 148.315 112.196 0 + vertex 148.5 112.098 0 + endloop + endfacet + facet normal 0.410563 0.911832 0 + outer loop + vertex 148.124 112.282 -3 + vertex 148.315 112.196 0 + vertex 148.315 112.196 -3 + endloop + endfacet + facet normal 0.410563 0.911832 -0 + outer loop + vertex 148.124 112.282 -3 + vertex 148.124 112.282 0 + vertex 148.315 112.196 0 + endloop + endfacet + facet normal 0.339058 0.940766 0 + outer loop + vertex 147.927 112.353 -3 + vertex 148.124 112.282 0 + vertex 148.124 112.282 -3 + endloop + endfacet + facet normal 0.339058 0.940766 -0 + outer loop + vertex 147.927 112.353 -3 + vertex 147.927 112.353 0 + vertex 148.124 112.282 0 + endloop + endfacet + facet normal 0.277246 0.960799 0 + outer loop + vertex 147.726 112.411 -3 + vertex 147.927 112.353 0 + vertex 147.927 112.353 -3 + endloop + endfacet + facet normal 0.277246 0.960799 -0 + outer loop + vertex 147.726 112.411 -3 + vertex 147.726 112.411 0 + vertex 147.927 112.353 0 + endloop + endfacet + facet normal 0.205289 0.978701 0 + outer loop + vertex 147.521 112.454 -3 + vertex 147.726 112.411 0 + vertex 147.726 112.411 -3 + endloop + endfacet + facet normal 0.205289 0.978701 -0 + outer loop + vertex 147.521 112.454 -3 + vertex 147.521 112.454 0 + vertex 147.726 112.411 0 + endloop + endfacet + facet normal 0.143429 0.989661 0 + outer loop + vertex 147.314 112.484 -3 + vertex 147.521 112.454 0 + vertex 147.521 112.454 -3 + endloop + endfacet + facet normal 0.143429 0.989661 -0 + outer loop + vertex 147.314 112.484 -3 + vertex 147.314 112.484 0 + vertex 147.521 112.454 0 + endloop + endfacet + facet normal 0.0668359 0.997764 0 + outer loop + vertex 147.105 112.498 -3 + vertex 147.314 112.484 0 + vertex 147.314 112.484 -3 + endloop + endfacet + facet normal 0.0668359 0.997764 -0 + outer loop + vertex 147.105 112.498 -3 + vertex 147.105 112.498 0 + vertex 147.314 112.484 0 + endloop + endfacet + facet normal 0 1 0 + outer loop + vertex 147.002 112.498 -3 + vertex 147.105 112.498 0 + vertex 147.105 112.498 -3 + endloop + endfacet + facet normal 0 1 0 + outer loop + vertex 147.002 112.498 -3 + vertex 147.002 112.498 0 + vertex 147.105 112.498 0 + endloop + endfacet + facet normal 0.707107 0.707107 0 + outer loop + vertex 147 112.5 -3 + vertex 147.002 112.498 0 + vertex 147.002 112.498 -3 + endloop + endfacet + facet normal 0.707107 0.707107 -0 + outer loop + vertex 147 112.5 -3 + vertex 147 112.5 0 + vertex 147.002 112.498 0 + endloop + endfacet + facet normal 0 1 0 + outer loop + vertex -147 112.5 -3 + vertex 147 112.5 0 + vertex 147 112.5 -3 + endloop + endfacet + facet normal 0 1 0 + outer loop + vertex -147 112.5 -3 + vertex -147 112.5 0 + vertex 147 112.5 0 + endloop + endfacet + facet normal -0.707107 0.707107 0 + outer loop + vertex -147.002 112.498 -3 + vertex -147 112.5 0 + vertex -147 112.5 -3 + endloop + endfacet + facet normal -0.707107 0.707107 0 + outer loop + vertex -147.002 112.498 -3 + vertex -147.002 112.498 0 + vertex -147 112.5 0 + endloop + endfacet + facet normal 0 1 0 + outer loop + vertex -147.105 112.498 -3 + vertex -147.002 112.498 0 + vertex -147.002 112.498 -3 + endloop + endfacet + facet normal 0 1 0 + outer loop + vertex -147.105 112.498 -3 + vertex -147.105 112.498 0 + vertex -147.002 112.498 0 + endloop + endfacet + facet normal -0.0668359 0.997764 0 + outer loop + vertex -147.314 112.484 -3 + vertex -147.105 112.498 0 + vertex -147.105 112.498 -3 + endloop + endfacet + facet normal -0.0668359 0.997764 0 + outer loop + vertex -147.314 112.484 -3 + vertex -147.314 112.484 0 + vertex -147.105 112.498 0 + endloop + endfacet + facet normal -0.143429 0.989661 0 + outer loop + vertex -147.521 112.454 -3 + vertex -147.314 112.484 0 + vertex -147.314 112.484 -3 + endloop + endfacet + facet normal -0.143429 0.989661 0 + outer loop + vertex -147.521 112.454 -3 + vertex -147.521 112.454 0 + vertex -147.314 112.484 0 + endloop + endfacet + facet normal -0.205289 0.978701 0 + outer loop + vertex -147.726 112.411 -3 + vertex -147.521 112.454 0 + vertex -147.521 112.454 -3 + endloop + endfacet + facet normal -0.205289 0.978701 0 + outer loop + vertex -147.726 112.411 -3 + vertex -147.726 112.411 0 + vertex -147.521 112.454 0 + endloop + endfacet + facet normal -0.277246 0.960799 0 + outer loop + vertex -147.927 112.353 -3 + vertex -147.726 112.411 0 + vertex -147.726 112.411 -3 + endloop + endfacet + facet normal -0.277246 0.960799 0 + outer loop + vertex -147.927 112.353 -3 + vertex -147.927 112.353 0 + vertex -147.726 112.411 0 + endloop + endfacet + facet normal -0.339058 0.940766 0 + outer loop + vertex -148.124 112.282 -3 + vertex -147.927 112.353 0 + vertex -147.927 112.353 -3 + endloop + endfacet + facet normal -0.339058 0.940766 0 + outer loop + vertex -148.124 112.282 -3 + vertex -148.124 112.282 0 + vertex -147.927 112.353 0 + endloop + endfacet + facet normal -0.410563 0.911832 0 + outer loop + vertex -148.315 112.196 -3 + vertex -148.124 112.282 0 + vertex -148.124 112.282 -3 + endloop + endfacet + facet normal -0.410563 0.911832 0 + outer loop + vertex -148.315 112.196 -3 + vertex -148.315 112.196 0 + vertex -148.124 112.282 0 + endloop + endfacet + facet normal -0.468107 0.883672 0 + outer loop + vertex -148.5 112.098 -3 + vertex -148.315 112.196 0 + vertex -148.315 112.196 -3 + endloop + endfacet + facet normal -0.468107 0.883672 0 + outer loop + vertex -148.5 112.098 -3 + vertex -148.5 112.098 0 + vertex -148.315 112.196 0 + endloop + endfacet + facet normal -0.529142 0.848533 0 + outer loop + vertex -148.678 111.987 -3 + vertex -148.5 112.098 0 + vertex -148.5 112.098 -3 + endloop + endfacet + facet normal -0.529142 0.848533 0 + outer loop + vertex -148.678 111.987 -3 + vertex -148.678 111.987 0 + vertex -148.5 112.098 0 + endloop + endfacet + facet normal -0.588456 0.808529 0 + outer loop + vertex -148.847 111.864 -3 + vertex -148.678 111.987 0 + vertex -148.678 111.987 -3 + endloop + endfacet + facet normal -0.588456 0.808529 0 + outer loop + vertex -148.847 111.864 -3 + vertex -148.847 111.864 0 + vertex -148.678 111.987 0 + endloop + endfacet + facet normal -0.644871 0.764291 0 + outer loop + vertex -149.007 111.729 -3 + vertex -148.847 111.864 0 + vertex -148.847 111.864 -3 + endloop + endfacet + facet normal -0.644871 0.764291 0 + outer loop + vertex -149.007 111.729 -3 + vertex -149.007 111.729 0 + vertex -148.847 111.864 0 + endloop + endfacet + facet normal -0.692631 0.721292 0 + outer loop + vertex -149.158 111.584 -3 + vertex -149.007 111.729 0 + vertex -149.007 111.729 -3 + endloop + endfacet + facet normal -0.692631 0.721292 0 + outer loop + vertex -149.158 111.584 -3 + vertex -149.158 111.584 0 + vertex -149.007 111.729 0 + endloop + endfacet + facet normal -0.744242 0.66791 0 + outer loop + vertex -149.298 111.428 -3 + vertex -149.158 111.584 0 + vertex -149.158 111.584 -3 + endloop + endfacet + facet normal -0.744242 0.66791 0 + outer loop + vertex -149.298 111.428 -3 + vertex -149.298 111.428 0 + vertex -149.158 111.584 0 + endloop + endfacet + facet normal -0.787807 0.615922 0 + outer loop + vertex -149.427 111.263 -3 + vertex -149.298 111.428 0 + vertex -149.298 111.428 -3 + endloop + endfacet + facet normal -0.787807 0.615922 0 + outer loop + vertex -149.427 111.263 -3 + vertex -149.427 111.263 0 + vertex -149.298 111.428 0 + endloop + endfacet + facet normal -0.828349 0.560213 0 + outer loop + vertex -149.544 111.09 -3 + vertex -149.427 111.263 0 + vertex -149.427 111.263 -3 + endloop + endfacet + facet normal -0.828349 0.560213 0 + outer loop + vertex -149.544 111.09 -3 + vertex -149.544 111.09 0 + vertex -149.427 111.263 0 + endloop + endfacet + facet normal -0.866186 0.499722 0 + outer loop + vertex -149.649 110.908 -3 + vertex -149.544 111.09 0 + vertex -149.544 111.09 -3 + endloop + endfacet + facet normal -0.866186 0.499722 0 + outer loop + vertex -149.649 110.908 -3 + vertex -149.649 110.908 0 + vertex -149.544 111.09 0 + endloop + endfacet + facet normal -0.898217 0.439553 0 + outer loop + vertex -149.741 110.72 -3 + vertex -149.649 110.908 0 + vertex -149.649 110.908 -3 + endloop + endfacet + facet normal -0.898217 0.439553 0 + outer loop + vertex -149.741 110.72 -3 + vertex -149.741 110.72 0 + vertex -149.649 110.908 0 + endloop + endfacet + facet normal -0.927816 0.373039 0 + outer loop + vertex -149.819 110.526 -3 + vertex -149.741 110.72 0 + vertex -149.741 110.72 -3 + endloop + endfacet + facet normal -0.927816 0.373039 0 + outer loop + vertex -149.819 110.526 -3 + vertex -149.819 110.526 0 + vertex -149.741 110.72 0 + endloop + endfacet + facet normal -0.950577 0.31049 0 + outer loop + vertex -149.884 110.327 -3 + vertex -149.819 110.526 0 + vertex -149.819 110.526 -3 + endloop + endfacet + facet normal -0.950577 0.31049 0 + outer loop + vertex -149.884 110.327 -3 + vertex -149.884 110.327 0 + vertex -149.819 110.526 0 + endloop + endfacet + facet normal -0.970981 0.239158 0 + outer loop + vertex -149.934 110.124 -3 + vertex -149.884 110.327 0 + vertex -149.884 110.327 -3 + endloop + endfacet + facet normal -0.970981 0.239158 0 + outer loop + vertex -149.934 110.124 -3 + vertex -149.934 110.124 0 + vertex -149.884 110.327 0 + endloop + endfacet + facet normal -0.98425 0.176783 0 + outer loop + vertex -149.971 109.918 -3 + vertex -149.934 110.124 0 + vertex -149.934 110.124 -3 + endloop + endfacet + facet normal -0.98425 0.176783 0 + outer loop + vertex -149.971 109.918 -3 + vertex -149.971 109.918 0 + vertex -149.934 110.124 0 + endloop + endfacet + facet normal -0.994505 0.104685 0 + outer loop + vertex -149.993 109.709 -3 + vertex -149.971 109.918 0 + vertex -149.971 109.918 -3 + endloop + endfacet + facet normal -0.994505 0.104685 0 + outer loop + vertex -149.993 109.709 -3 + vertex -149.993 109.709 0 + vertex -149.971 109.918 0 + endloop + endfacet + facet normal -0.99944 0.0334741 0 + outer loop + vertex -150 109.5 -3 + vertex -149.993 109.709 0 + vertex -149.993 109.709 -3 + endloop + endfacet + facet normal -0.99944 0.0334741 0 + outer loop + vertex -150 109.5 -3 + vertex -150 109.5 0 + vertex -149.993 109.709 0 + endloop + endfacet + facet normal -1 0 0 + outer loop + vertex -150 -109.5 -3 + vertex -150 109.5 0 + vertex -150 109.5 -3 + endloop + endfacet + facet normal -1 0 0 + outer loop + vertex -150 -109.5 -3 + vertex -150 -109.5 0 + vertex -150 109.5 0 + endloop + endfacet + facet normal -0.99944 -0.0334741 0 + outer loop + vertex -149.993 -109.709 -3 + vertex -150 -109.5 0 + vertex -150 -109.5 -3 + endloop + endfacet + facet normal -0.99944 -0.0334741 0 + outer loop + vertex -149.993 -109.709 -3 + vertex -149.993 -109.709 0 + vertex -150 -109.5 0 + endloop + endfacet + facet normal -0.994505 -0.104685 0 + outer loop + vertex -149.971 -109.918 -3 + vertex -149.993 -109.709 0 + vertex -149.993 -109.709 -3 + endloop + endfacet + facet normal -0.994505 -0.104685 0 + outer loop + vertex -149.971 -109.918 -3 + vertex -149.971 -109.918 0 + vertex -149.993 -109.709 0 + endloop + endfacet + facet normal -0.98425 -0.176783 0 + outer loop + vertex -149.934 -110.124 -3 + vertex -149.971 -109.918 0 + vertex -149.971 -109.918 -3 + endloop + endfacet + facet normal -0.98425 -0.176783 0 + outer loop + vertex -149.934 -110.124 -3 + vertex -149.934 -110.124 0 + vertex -149.971 -109.918 0 + endloop + endfacet + facet normal -0.970981 -0.239158 0 + outer loop + vertex -149.884 -110.327 -3 + vertex -149.934 -110.124 0 + vertex -149.934 -110.124 -3 + endloop + endfacet + facet normal -0.970981 -0.239158 0 + outer loop + vertex -149.884 -110.327 -3 + vertex -149.884 -110.327 0 + vertex -149.934 -110.124 0 + endloop + endfacet + facet normal -0.950577 -0.31049 0 + outer loop + vertex -149.819 -110.526 -3 + vertex -149.884 -110.327 0 + vertex -149.884 -110.327 -3 + endloop + endfacet + facet normal -0.950577 -0.31049 0 + outer loop + vertex -149.819 -110.526 -3 + vertex -149.819 -110.526 0 + vertex -149.884 -110.327 0 + endloop + endfacet + facet normal -0.927816 -0.373039 0 + outer loop + vertex -149.741 -110.72 -3 + vertex -149.819 -110.526 0 + vertex -149.819 -110.526 -3 + endloop + endfacet + facet normal -0.927816 -0.373039 0 + outer loop + vertex -149.741 -110.72 -3 + vertex -149.741 -110.72 0 + vertex -149.819 -110.526 0 + endloop + endfacet + facet normal -0.898217 -0.439553 0 + outer loop + vertex -149.649 -110.908 -3 + vertex -149.741 -110.72 0 + vertex -149.741 -110.72 -3 + endloop + endfacet + facet normal -0.898217 -0.439553 0 + outer loop + vertex -149.649 -110.908 -3 + vertex -149.649 -110.908 0 + vertex -149.741 -110.72 0 + endloop + endfacet + facet normal -0.866186 -0.499722 0 + outer loop + vertex -149.544 -111.09 -3 + vertex -149.649 -110.908 0 + vertex -149.649 -110.908 -3 + endloop + endfacet + facet normal -0.866186 -0.499722 0 + outer loop + vertex -149.544 -111.09 -3 + vertex -149.544 -111.09 0 + vertex -149.649 -110.908 0 + endloop + endfacet + facet normal -0.828349 -0.560213 0 + outer loop + vertex -149.427 -111.263 -3 + vertex -149.544 -111.09 0 + vertex -149.544 -111.09 -3 + endloop + endfacet + facet normal -0.828349 -0.560213 0 + outer loop + vertex -149.427 -111.263 -3 + vertex -149.427 -111.263 0 + vertex -149.544 -111.09 0 + endloop + endfacet + facet normal -0.787807 -0.615922 0 + outer loop + vertex -149.298 -111.428 -3 + vertex -149.427 -111.263 0 + vertex -149.427 -111.263 -3 + endloop + endfacet + facet normal -0.787807 -0.615922 0 + outer loop + vertex -149.298 -111.428 -3 + vertex -149.298 -111.428 0 + vertex -149.427 -111.263 0 + endloop + endfacet + facet normal -0.744242 -0.66791 0 + outer loop + vertex -149.158 -111.584 -3 + vertex -149.298 -111.428 0 + vertex -149.298 -111.428 -3 + endloop + endfacet + facet normal -0.744242 -0.66791 0 + outer loop + vertex -149.158 -111.584 -3 + vertex -149.158 -111.584 0 + vertex -149.298 -111.428 0 + endloop + endfacet + facet normal -0.692631 -0.721292 0 + outer loop + vertex -149.007 -111.729 -3 + vertex -149.158 -111.584 0 + vertex -149.158 -111.584 -3 + endloop + endfacet + facet normal -0.692631 -0.721292 0 + outer loop + vertex -149.007 -111.729 -3 + vertex -149.007 -111.729 0 + vertex -149.158 -111.584 0 + endloop + endfacet + facet normal -0.644871 -0.764291 0 + outer loop + vertex -148.847 -111.864 -3 + vertex -149.007 -111.729 0 + vertex -149.007 -111.729 -3 + endloop + endfacet + facet normal -0.644871 -0.764291 0 + outer loop + vertex -148.847 -111.864 -3 + vertex -148.847 -111.864 0 + vertex -149.007 -111.729 0 + endloop + endfacet + facet normal -0.588456 -0.808529 0 + outer loop + vertex -148.678 -111.987 -3 + vertex -148.847 -111.864 0 + vertex -148.847 -111.864 -3 + endloop + endfacet + facet normal -0.588456 -0.808529 0 + outer loop + vertex -148.678 -111.987 -3 + vertex -148.678 -111.987 0 + vertex -148.847 -111.864 0 + endloop + endfacet + facet normal -0.529142 -0.848533 0 + outer loop + vertex -148.5 -112.098 -3 + vertex -148.678 -111.987 0 + vertex -148.678 -111.987 -3 + endloop + endfacet + facet normal -0.529142 -0.848533 0 + outer loop + vertex -148.5 -112.098 -3 + vertex -148.5 -112.098 0 + vertex -148.678 -111.987 0 + endloop + endfacet + facet normal -0.468107 -0.883672 0 + outer loop + vertex -148.315 -112.196 -3 + vertex -148.5 -112.098 0 + vertex -148.5 -112.098 -3 + endloop + endfacet + facet normal -0.468107 -0.883672 0 + outer loop + vertex -148.315 -112.196 -3 + vertex -148.315 -112.196 0 + vertex -148.5 -112.098 0 + endloop + endfacet + facet normal -0.410563 -0.911832 0 + outer loop + vertex -148.124 -112.282 -3 + vertex -148.315 -112.196 0 + vertex -148.315 -112.196 -3 + endloop + endfacet + facet normal -0.410563 -0.911832 0 + outer loop + vertex -148.124 -112.282 -3 + vertex -148.124 -112.282 0 + vertex -148.315 -112.196 0 + endloop + endfacet + facet normal -0.339058 -0.940766 0 + outer loop + vertex -147.927 -112.353 -3 + vertex -148.124 -112.282 0 + vertex -148.124 -112.282 -3 + endloop + endfacet + facet normal -0.339058 -0.940766 0 + outer loop + vertex -147.927 -112.353 -3 + vertex -147.927 -112.353 0 + vertex -148.124 -112.282 0 + endloop + endfacet + facet normal -0.277246 -0.960799 0 + outer loop + vertex -147.726 -112.411 -3 + vertex -147.927 -112.353 0 + vertex -147.927 -112.353 -3 + endloop + endfacet + facet normal -0.277246 -0.960799 0 + outer loop + vertex -147.726 -112.411 -3 + vertex -147.726 -112.411 0 + vertex -147.927 -112.353 0 + endloop + endfacet + facet normal -0.205289 -0.978701 0 + outer loop + vertex -147.521 -112.454 -3 + vertex -147.726 -112.411 0 + vertex -147.726 -112.411 -3 + endloop + endfacet + facet normal -0.205289 -0.978701 0 + outer loop + vertex -147.521 -112.454 -3 + vertex -147.521 -112.454 0 + vertex -147.726 -112.411 0 + endloop + endfacet + facet normal -0.143429 -0.989661 0 + outer loop + vertex -147.314 -112.484 -3 + vertex -147.521 -112.454 0 + vertex -147.521 -112.454 -3 + endloop + endfacet + facet normal -0.143429 -0.989661 0 + outer loop + vertex -147.314 -112.484 -3 + vertex -147.314 -112.484 0 + vertex -147.521 -112.454 0 + endloop + endfacet + facet normal -0.0668359 -0.997764 0 + outer loop + vertex -147.105 -112.498 -3 + vertex -147.314 -112.484 0 + vertex -147.314 -112.484 -3 + endloop + endfacet + facet normal -0.0668359 -0.997764 0 + outer loop + vertex -147.105 -112.498 -3 + vertex -147.105 -112.498 0 + vertex -147.314 -112.484 0 + endloop + endfacet + facet normal 0 -1 0 + outer loop + vertex -147.002 -112.498 -3 + vertex -147.105 -112.498 0 + vertex -147.105 -112.498 -3 + endloop + endfacet + facet normal 0 -1 0 + outer loop + vertex -147.002 -112.498 -3 + vertex -147.002 -112.498 0 + vertex -147.105 -112.498 0 + endloop + endfacet + facet normal -0.707107 -0.707107 0 + outer loop + vertex -147 -112.5 -3 + vertex -147.002 -112.498 0 + vertex -147.002 -112.498 -3 + endloop + endfacet + facet normal -0.707107 -0.707107 0 + outer loop + vertex -147 -112.5 -3 + vertex -147 -112.5 0 + vertex -147.002 -112.498 0 + endloop + endfacet + facet normal 0 -1 0 + outer loop + vertex 147 -112.5 -3 + vertex -147 -112.5 0 + vertex -147 -112.5 -3 + endloop + endfacet + facet normal 0 -1 0 + outer loop + vertex 147 -112.5 -3 + vertex 147 -112.5 0 + vertex -147 -112.5 0 + endloop + endfacet + facet normal 0.707107 -0.707107 0 + outer loop + vertex 147.002 -112.498 -3 + vertex 147 -112.5 0 + vertex 147 -112.5 -3 + endloop + endfacet + facet normal 0.707107 -0.707107 0 + outer loop + vertex 147.002 -112.498 -3 + vertex 147.002 -112.498 0 + vertex 147 -112.5 0 + endloop + endfacet +endsolid OpenSCAD_Model diff --git a/resources/profiles/INAT.idx b/resources/profiles/INAT.idx index ff2873c12b..47632c29a0 100644 --- a/resources/profiles/INAT.idx +++ b/resources/profiles/INAT.idx @@ -1,2 +1,3 @@ -min_slic3r_version = 2.3.0 +min_slic3r_version = 2.3.1-beta 0.0.1 Initial version +0.0.2 Improved start gcode, changed filename format diff --git a/resources/profiles/INAT.ini b/resources/profiles/INAT.ini index 0c4cbe5f4d..81a4f90ef4 100644 --- a/resources/profiles/INAT.ini +++ b/resources/profiles/INAT.ini @@ -2,9 +2,9 @@ [vendor] # Vendor name will be shown by the Config Wizard. -name = INAT s.r.o. -config_version = 0.0.1 -config_update_url = http://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/INAT/ +name = INAT +config_version = 0.0.2 +config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/INAT/ ### ### PRINTER LIST @@ -133,7 +133,7 @@ extruder_clearance_radius = 85 extruder_clearance_height = 34 gcode_comments = 0 gcode_label_objects = 0 -output_filename_format = {input_filename_base}_{filament_type[0]}_{print_time}.gcode +output_filename_format = {input_filename_base}_{filament_type[0]}.gcode [print:0.2mm Standard @PROTON_X] @@ -193,7 +193,7 @@ use_firmware_retraction = 0 use_volumetric_e = 0 variable_layer_height = 1 #gcodes -start_gcode = G28 ;Home\nG0 Z0.3 F200 ;Move nozzle down\nM192 S50 ; Wait for probe temperature to settle\nG29\nG0 X0 Y0 Z30 F6000\nM84 E\nM0\nG1 Z15.0 F6000 ;Move the platform down 15mm\n;Prime the extruder\nG92 E0\nG1 F200 E3\nG92 E0\n +start_gcode = G28 ;Home\nG0 X-2 Y150 F6000 ;Move to the side\nG0 Z0.3 F200 ;Move nozzle down\nM192 ; Wait for probe temperature to settle\nG28 Z\nG29\nG0 X0 Y0 Z30 F6000\nM84 E\nM0\nG1 Z15.0 F6000 ;Move the platform down 15mm\n end_gcode = M400\nM104 S0\nM140 S0\nM107\n;Retract the filament\nG92 E1\nG1 E-1 F300\nG28 X R5\nG0 Y300 F2000\nM84\n color_change_gcode = M600 #limits diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index 8c14026ce1..dc3c79cd55 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,159 +1,161 @@ -min_slic3r_version = 2.3.0-rc1 -1.2.4 Updated cost/density values in filament settings. Various changes in print settings. -1.2.3 Updated firmware version. Updated end g-code in MMU2 printer profiles. -1.2.2 Added Prusament PVB filament profile. Added 0.8mm nozzle profiles. -1.2.1 Updated FW version for MK2.5 family printers. -1.2.0 Added full_fan_speed_layer value for PETG. Increased support interface spacing for 0.6mm nozzle profiles. Updated firmware version. -min_slic3r_version = 2.3.0-beta2 -1.2.0-beta1 Updated end g-code. Added full_fan_speed_layer values. -min_slic3r_version = 2.3.0-beta0 -1.2.0-beta0 Adjusted infill anchor limits. Added filament spool weights. -min_slic3r_version = 2.3.0-alpha4 -1.2.0-alpha1 Renamed MK3S and MINI printer profiles. Updated end g-code (MINI). Added new SLA materials and filament profiles. -1.2.0-alpha0 Added filament spool weights -min_slic3r_version = 2.2.0-alpha3 -1.1.13 Updated firmware version. Updated end g-code in MMU2 printer profiles. -1.1.12 Added Prusament PVB filament profile. Added 0.8mm nozzle profiles. -1.1.11 Renamed MK3S and MINI printer profiles. Updated end g-code (MINI). Added new SLA materials and filament profiles. -1.1.10 Updated firmware version. -1.1.9 Updated K values in filament profiles (linear advance). Added new filament profiles and SLA materials. -1.1.8 Updated start/end g-code scripts for MK3 family printer profiles (reduced extruder motor current for some print profiles). Added new filament and SLA material profiles. -1.1.7 Updated end g-code for MMU2 Single printer profiles. Added/updated filament and SLA material profiles. -1.1.6 Updated firmware version for MK2.5/S and MK3/S. -1.1.5 Updated MMU1 specific retraction settings for Prusament PC Blend -1.1.4 Added Prusament PC Blend filament profile. -1.1.3 Added SLA material and filament profile -1.1.2 Added renamed_from fields for PETG filaments to indicate that they were renamed from PET. -1.1.1 Added Verbatim and Fiberlogy PETG filament profiles. Updated auto cooling settings for ABS. -1.1.1-beta Updated for PrusaSlicer 2.2.0-beta -1.1.1-alpha4 Extended list of default filaments to be installed, top/bottom_solid_min_thickness defined, infill_acceleration changed etc -1.1.1-alpha3 Print bed textures are now configurable from the Preset Bundle. Requires PrusaSlicer 2.2.0-alpha3 and newer. -# The following line (max_slic3r_version) forces the users of PrusaSlicer 2.2.0-alpha3 and newer to update the profiles to 1.1.1-alpha3 and newer, -# so they will see the print bed. -max_slic3r_version = 2.2.0-alpha2 -min_slic3r_version = 2.2.0-alpha0 -1.1.1-alpha2 Bumped up config version, so our in house customer will get updated profiles. -1.1.0 Filament aliases, Creality profiles and other goodies for PrusaSlicer 2.2.0-alpha0 -min_slic3r_version = 2.1.1-beta0 -1.0.11 Updated firmware version. -1.0.10 Updated firmware version for MK2.5/S and MK3/S. -1.0.9 Updated firmware version for MK2.5/S and MK3/S. -1.0.8 Various changes in FFF profiles, new filaments/materials added. See changelog. -1.0.7 Updated layer height limits for MINI -1.0.6 Added Prusa MINI profiles -min_slic3r_version = 2.1.0-alpha0 -1.0.5 Added SLA materials -1.0.4 Updated firmware version and 0.25mm nozzle profiles -1.0.3 Added filament profiles -1.0.2 Added SLA materials -1.0.1 Updated MK3 firmware version check to 3.8.0, new soluble support profiles for 0.6mm nozzle diameter MMU2S printers. -1.0.0 Updated end G-code for the MMU2 profiles to lift the extruder at the end of print. Wipe tower bridging distance was made smaller for soluble supports. -1.0.0-beta1 Updated color for the ASA filaments to differ from the other filaments. Single extruder printers now have no extruder color assigned, obects and toolpaths will be colored with the color of the active filament. -1.0.0-beta0 Printer model checks in start G-codes, ASA filament profiles, limits on min / max SL1 exposition times -1.0.0-alpha2 Printer model and nozzle diameter check -1.0.0-alpha1 Added Prusament ASA profile -1.0.0-alpha0 Filament specific retract for PET and similar copolymers, and for FLEX -min_slic3r_version = 1.42.0-alpha6 -0.8.10 Updated firmware version. -0.8.9 Updated firmware version for MK2.5/S and MK3/S. -0.8.8 Updated firmware version for MK2.5/S and MK3/S. -0.8.7 Updated firmware version -0.8.6 Updated firmware version for MK2.5/S and MK3/S -0.8.5 Updated SL1 printer and material settings -0.8.4 Added Prusament ASA profile -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 -0.8.0-rc Updated for the PrusaSlicer 2.0.0-rc release -0.8.0-beta4 Updated SLA profiles -0.8.0-beta3 Updated SLA profiles -0.8.0-beta2 Updated SLA profiles -0.8.0-beta1 Updated SLA profiles -0.8.0-beta Updated SLA profiles -0.8.0-alpha9 Updated SLA and FFF profiles -0.8.0-alpha8 Updated SLA profiles -0.8.0-alpha7 Updated SLA profiles -0.8.0-alpha6 Updated SLA profiles -min_slic3r_version = 1.42.0-alpha -0.8.0-alpha Updated SLA profiles -0.4.0-alpha4 Updated SLA profiles -0.4.0-alpha3 Update of SLA profiles -0.4.0-alpha2 First SLA profiles -min_slic3r_version = 1.41.3-alpha -0.4.12 Updated firmware version for MK2.5/S and MK3/S. -0.4.11 Updated firmware version for MK2.5/S and MK3/S. -0.4.10 Updated firmware version -0.4.9 Updated firmware version for MK2.5/S and MK3/S -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 -0.4.3 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt -0.4.2 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt -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.11 Updated firmware version for MK2.5/S and MK3/S. -0.3.10 Updated firmware version -0.3.9 Updated firmware version for MK2.5/S and MK3/S -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 -0.3.3 Prusament PETG released -0.3.2 New MK2.5 and MK3 FW versions -0.3.1 New MK2.5 and MK3 FW versions -0.3.0 New MK2.5 and MK3 FW version -min_slic3r_version = 1.41.0-alpha -0.2.9 New MK2.5 and MK3 FW versions -0.2.8 New MK2.5 and MK3 FW version -min_slic3r_version = 1.41.1 -0.2.7 New MK2.5 and MK3 FW version -0.2.6 Added MMU2 MK2.5 settings -min_slic3r_version = 1.41.0-alpha -0.2.5 Prusament is out - added prusament settings -0.2.4 Added soluble support profiles for MMU2 -0.2.3 Added materials for MMU2 single mode, edited MK3 xy stealth feedrate limit -0.2.2 Edited MMU2 Single mode purge line -0.2.1 Added PET and BVOH settings for MMU2 -0.2.0-beta5 Fixed MMU1 ramming parameters -0.2.0-beta4 Added filament loading speed at start, increased minimal purge on wipe tower -0.2.0-beta3 Edited ramming parameters and filament cooling moves for MMU2 -0.2.0-beta2 Edited first layer speed and wipe tower position -0.2.0-beta Removed limit on the MK3MMU2 height, added legacy M204 S T format to the MK2 profiles -0.2.0-alpha8 Added filament_load/unload_time for the PLA/ABS MMU2 filament presets. -0.2.0-alpha7 Vojtech's fix the incorrect *MK3* references -0.2.0-alpha6 Jindra's way to fix the 0.2.0-alpha5 version -0.2.0-alpha5 Bumped up firmware versions for MK2.5/MK3 to 3.3.1, disabled priming areas for MK3MMU2 -0.2.0-alpha4 Extended the custom start/end G-codes of the MMU2.0 printers for no priming towers. -0.2.0-alpha3 Adjusted machine limits for time estimates, added filament density and cost -0.2.0-alpha2 Renamed the key MK3SMMU to MK3MMU2, added a generic PLA MMU2 material -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.18 Updated firmware version -0.1.17 Updated firmware version for MK2.5/S and MK3/S -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 -0.1.11 fw version changed to 3.3.1 -0.1.10 MK3 jerk and acceleration update -0.1.9 edited support extrusion width for 0.25 and 0.6 nozzles -0.1.8 extrusion width for 0,25, 0.6 and variable layer height fixes -0.1.7 Fixed errors in 0.25mm and 0.6mm profiles -0.1.6 Split the MK2.5 profile from the MK2S -min_slic3r_version = 1.40.0-beta -0.1.5 fixed printer_variant fields for the i3 MK3 0.25 and 0.6mm nozzles -0.1.4 edited fw version, added z-raise after print -min_slic3r_version = 1.40.0-alpha -0.1.3 Fixed an incorrect position of the max_print_height parameter -0.1.2 Wipe tower changes -0.1.1 Minor print speed adjustments -0.1.0 Initial +min_slic3r_version = 2.4.0-alpha0 +1.3.0-alpha0 Disabled thick bridges, updated support settings. +min_slic3r_version = 2.3.0-rc1 +1.2.4 Updated cost/density values in filament settings. Various changes in print settings. +1.2.3 Updated firmware version. Updated end g-code in MMU2 printer profiles. +1.2.2 Added Prusament PVB filament profile. Added 0.8mm nozzle profiles. +1.2.1 Updated FW version for MK2.5 family printers. +1.2.0 Added full_fan_speed_layer value for PETG. Increased support interface spacing for 0.6mm nozzle profiles. Updated firmware version. +min_slic3r_version = 2.3.0-beta2 +1.2.0-beta1 Updated end g-code. Added full_fan_speed_layer values. +min_slic3r_version = 2.3.0-beta0 +1.2.0-beta0 Adjusted infill anchor limits. Added filament spool weights. +min_slic3r_version = 2.3.0-alpha4 +1.2.0-alpha1 Renamed MK3S and MINI printer profiles. Updated end g-code (MINI). Added new SLA materials and filament profiles. +1.2.0-alpha0 Added filament spool weights +min_slic3r_version = 2.2.0-alpha3 +1.1.13 Updated firmware version. Updated end g-code in MMU2 printer profiles. +1.1.12 Added Prusament PVB filament profile. Added 0.8mm nozzle profiles. +1.1.11 Renamed MK3S and MINI printer profiles. Updated end g-code (MINI). Added new SLA materials and filament profiles. +1.1.10 Updated firmware version. +1.1.9 Updated K values in filament profiles (linear advance). Added new filament profiles and SLA materials. +1.1.8 Updated start/end g-code scripts for MK3 family printer profiles (reduced extruder motor current for some print profiles). Added new filament and SLA material profiles. +1.1.7 Updated end g-code for MMU2 Single printer profiles. Added/updated filament and SLA material profiles. +1.1.6 Updated firmware version for MK2.5/S and MK3/S. +1.1.5 Updated MMU1 specific retraction settings for Prusament PC Blend +1.1.4 Added Prusament PC Blend filament profile. +1.1.3 Added SLA material and filament profile +1.1.2 Added renamed_from fields for PETG filaments to indicate that they were renamed from PET. +1.1.1 Added Verbatim and Fiberlogy PETG filament profiles. Updated auto cooling settings for ABS. +1.1.1-beta Updated for PrusaSlicer 2.2.0-beta +1.1.1-alpha4 Extended list of default filaments to be installed, top/bottom_solid_min_thickness defined, infill_acceleration changed etc +1.1.1-alpha3 Print bed textures are now configurable from the Preset Bundle. Requires PrusaSlicer 2.2.0-alpha3 and newer. +# The following line (max_slic3r_version) forces the users of PrusaSlicer 2.2.0-alpha3 and newer to update the profiles to 1.1.1-alpha3 and newer, +# so they will see the print bed. +max_slic3r_version = 2.2.0-alpha2 +min_slic3r_version = 2.2.0-alpha0 +1.1.1-alpha2 Bumped up config version, so our in house customer will get updated profiles. +1.1.0 Filament aliases, Creality profiles and other goodies for PrusaSlicer 2.2.0-alpha0 +min_slic3r_version = 2.1.1-beta0 +1.0.11 Updated firmware version. +1.0.10 Updated firmware version for MK2.5/S and MK3/S. +1.0.9 Updated firmware version for MK2.5/S and MK3/S. +1.0.8 Various changes in FFF profiles, new filaments/materials added. See changelog. +1.0.7 Updated layer height limits for MINI +1.0.6 Added Prusa MINI profiles +min_slic3r_version = 2.1.0-alpha0 +1.0.5 Added SLA materials +1.0.4 Updated firmware version and 0.25mm nozzle profiles +1.0.3 Added filament profiles +1.0.2 Added SLA materials +1.0.1 Updated MK3 firmware version check to 3.8.0, new soluble support profiles for 0.6mm nozzle diameter MMU2S printers. +1.0.0 Updated end G-code for the MMU2 profiles to lift the extruder at the end of print. Wipe tower bridging distance was made smaller for soluble supports. +1.0.0-beta1 Updated color for the ASA filaments to differ from the other filaments. Single extruder printers now have no extruder color assigned, obects and toolpaths will be colored with the color of the active filament. +1.0.0-beta0 Printer model checks in start G-codes, ASA filament profiles, limits on min / max SL1 exposition times +1.0.0-alpha2 Printer model and nozzle diameter check +1.0.0-alpha1 Added Prusament ASA profile +1.0.0-alpha0 Filament specific retract for PET and similar copolymers, and for FLEX +min_slic3r_version = 1.42.0-alpha6 +0.8.10 Updated firmware version. +0.8.9 Updated firmware version for MK2.5/S and MK3/S. +0.8.8 Updated firmware version for MK2.5/S and MK3/S. +0.8.7 Updated firmware version +0.8.6 Updated firmware version for MK2.5/S and MK3/S +0.8.5 Updated SL1 printer and material settings +0.8.4 Added Prusament ASA profile +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 +0.8.0-rc Updated for the PrusaSlicer 2.0.0-rc release +0.8.0-beta4 Updated SLA profiles +0.8.0-beta3 Updated SLA profiles +0.8.0-beta2 Updated SLA profiles +0.8.0-beta1 Updated SLA profiles +0.8.0-beta Updated SLA profiles +0.8.0-alpha9 Updated SLA and FFF profiles +0.8.0-alpha8 Updated SLA profiles +0.8.0-alpha7 Updated SLA profiles +0.8.0-alpha6 Updated SLA profiles +min_slic3r_version = 1.42.0-alpha +0.8.0-alpha Updated SLA profiles +0.4.0-alpha4 Updated SLA profiles +0.4.0-alpha3 Update of SLA profiles +0.4.0-alpha2 First SLA profiles +min_slic3r_version = 1.41.3-alpha +0.4.12 Updated firmware version for MK2.5/S and MK3/S. +0.4.11 Updated firmware version for MK2.5/S and MK3/S. +0.4.10 Updated firmware version +0.4.9 Updated firmware version for MK2.5/S and MK3/S +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 +0.4.3 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt +0.4.2 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt +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.11 Updated firmware version for MK2.5/S and MK3/S. +0.3.10 Updated firmware version +0.3.9 Updated firmware version for MK2.5/S and MK3/S +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 +0.3.3 Prusament PETG released +0.3.2 New MK2.5 and MK3 FW versions +0.3.1 New MK2.5 and MK3 FW versions +0.3.0 New MK2.5 and MK3 FW version +min_slic3r_version = 1.41.0-alpha +0.2.9 New MK2.5 and MK3 FW versions +0.2.8 New MK2.5 and MK3 FW version +min_slic3r_version = 1.41.1 +0.2.7 New MK2.5 and MK3 FW version +0.2.6 Added MMU2 MK2.5 settings +min_slic3r_version = 1.41.0-alpha +0.2.5 Prusament is out - added prusament settings +0.2.4 Added soluble support profiles for MMU2 +0.2.3 Added materials for MMU2 single mode, edited MK3 xy stealth feedrate limit +0.2.2 Edited MMU2 Single mode purge line +0.2.1 Added PET and BVOH settings for MMU2 +0.2.0-beta5 Fixed MMU1 ramming parameters +0.2.0-beta4 Added filament loading speed at start, increased minimal purge on wipe tower +0.2.0-beta3 Edited ramming parameters and filament cooling moves for MMU2 +0.2.0-beta2 Edited first layer speed and wipe tower position +0.2.0-beta Removed limit on the MK3MMU2 height, added legacy M204 S T format to the MK2 profiles +0.2.0-alpha8 Added filament_load/unload_time for the PLA/ABS MMU2 filament presets. +0.2.0-alpha7 Vojtech's fix the incorrect *MK3* references +0.2.0-alpha6 Jindra's way to fix the 0.2.0-alpha5 version +0.2.0-alpha5 Bumped up firmware versions for MK2.5/MK3 to 3.3.1, disabled priming areas for MK3MMU2 +0.2.0-alpha4 Extended the custom start/end G-codes of the MMU2.0 printers for no priming towers. +0.2.0-alpha3 Adjusted machine limits for time estimates, added filament density and cost +0.2.0-alpha2 Renamed the key MK3SMMU to MK3MMU2, added a generic PLA MMU2 material +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.18 Updated firmware version +0.1.17 Updated firmware version for MK2.5/S and MK3/S +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 +0.1.11 fw version changed to 3.3.1 +0.1.10 MK3 jerk and acceleration update +0.1.9 edited support extrusion width for 0.25 and 0.6 nozzles +0.1.8 extrusion width for 0,25, 0.6 and variable layer height fixes +0.1.7 Fixed errors in 0.25mm and 0.6mm profiles +0.1.6 Split the MK2.5 profile from the MK2S +min_slic3r_version = 1.40.0-beta +0.1.5 fixed printer_variant fields for the i3 MK3 0.25 and 0.6mm nozzles +0.1.4 edited fw version, added z-raise after print +min_slic3r_version = 1.40.0-alpha +0.1.3 Fixed an incorrect position of the max_print_height parameter +0.1.2 Wipe tower changes +0.1.1 Minor print speed adjustments +0.1.0 Initial diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 796d578226..16737d1563 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -5,18 +5,15 @@ 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 PrusaSlicer configuration to be downgraded. -config_version = 1.2.4 +config_version = 1.3.0-alpha0 # Where to get the updates from? config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaResearch/ changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% # The printer models will be shown by the Configuration Wizard in this order, # also the first model installed & the first nozzle installed will be activated after install. -#TODO: One day we may differentiate variants of the nozzles / hot ends, -#for example by the melt zone size, or whether the nozzle is hardened. # Printer model name will be shown by the installation wizard. - [printer_model:MINI] name = Original Prusa MINI && MINI+ variants = 0.4; 0.25; 0.6; 0.8 @@ -128,20 +125,21 @@ default_materials = Prusa Orange Tough @0.05 # All presets starting with asterisk, for example *common*, are intermediate and they will # not make it into the user interface. -# Common print preset, mostly derived from MK2 single material with a 0.4mm nozzle. -# All other print presets will derive from the *common* print preset. +# Common print presets + [print:*common*] avoid_crossing_perimeters = 0 +thick_bridges = 0 bridge_acceleration = 1000 bridge_angle = 0 -bridge_flow_ratio = 0.8 -bridge_speed = 20 +bridge_flow_ratio = 1 +bridge_speed = 25 brim_width = 0 clip_multipart_objects = 1 compatible_printers = complete_objects = 0 default_acceleration = 1000 -dont_support_bridges = 1 +dont_support_bridges = 0 elefant_foot_compensation = 0.2 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear @@ -154,7 +152,7 @@ extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic -first_layer_acceleration = 1000 +first_layer_acceleration = 800 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 20 @@ -183,6 +181,7 @@ perimeter_extrusion_width = 0.45 post_process = print_settings_id = raft_layers = 0 +raft_first_layer_density = 90% resolution = 0 seam_position = nearest single_extruder_multi_material_priming = 1 @@ -203,7 +202,7 @@ support_material_interface_extruder = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_enforce_layers = 0 -support_material_contact_distance = 0.1 +support_material_contact_distance = 0.2 support_material_interface_contact_loops = 0 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 @@ -212,9 +211,10 @@ support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 -support_material_threshold = 55 +support_material_threshold = 50 support_material_with_sheath = 0 -support_material_xy_spacing = 50% +support_material_xy_spacing = 60% +support_material_bottom_interface_layers = 0 thin_walls = 0 top_infill_extrusion_width = 0.45 top_solid_infill_speed = 40 @@ -240,20 +240,15 @@ wipe_tower_x = 170 wipe_tower_y = 125 [print:*MK306*] +inherits = *MK3* fill_pattern = gyroid fill_density = 15% -single_extruder_multi_material_priming = 0 -travel_speed = 180 -wipe_tower_x = 170 -wipe_tower_y = 125 - -## MINI [print:*MINI*] fill_pattern = grid travel_speed = 150 wipe_tower = 0 -default_acceleration = 1250 +default_acceleration = 1000 first_layer_acceleration = 800 infill_acceleration = 1000 bridge_acceleration = 1000 @@ -262,7 +257,6 @@ max_print_speed = 150 extruder_clearance_height = 20 extruder_clearance_radius = 35 -# Print parameters common to a 0.25mm diameter nozzle. [print:*0.25nozzle*] elefant_foot_compensation = 0 external_perimeter_extrusion_width = 0.25 @@ -277,7 +271,11 @@ support_material_interface_layers = 0 support_material_interface_spacing = 0.15 support_material_spacing = 1 support_material_xy_spacing = 150% +support_material_contact_distance = 0.1 output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{filament_type[0]}_{printer_model}_{print_time}.gcode +thick_bridges = 0 +bridge_flow_ratio = 1 +bridge_speed = 20 [print:*0.25nozzleMK3*] inherits = *0.25nozzle* @@ -288,7 +286,6 @@ infill_speed = 45 solid_infill_speed = 45 top_solid_infill_speed = 30 support_material_speed = 40 -bridge_speed = 20 gap_fill_speed = 30 perimeter_acceleration = 500 infill_acceleration = 1000 @@ -307,7 +304,6 @@ solid_infill_speed = 40 infill_acceleration = 800 first_layer_acceleration = 500 -# Print parameters common to a 0.6mm diameter nozzle. [print:*0.6nozzle*] external_perimeter_extrusion_width = 0.61 extrusion_width = 0.67 @@ -324,6 +320,31 @@ output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_heig infill_anchor_max = 15 top_solid_min_thickness = 0.9 bottom_solid_min_thickness = 0.6 +thick_bridges = 1 +bridge_flow_ratio = 0.95 +bridge_speed = 25 + +[print:*0.6nozzleMK3*] +inherits = *0.6nozzle* +external_perimeter_extrusion_width = 0.65 +extrusion_width = 0.65 +infill_extrusion_width = 0.65 +thick_bridges = 0 + +[print:*0.6nozzleMINI*] +inherits = *0.6nozzleMK3* +infill_extrusion_width = 0.68 +solid_infill_extrusion_width = 0.68 +fill_pattern = gyroid +fill_density = 15% +travel_speed = 150 +perimeter_acceleration = 800 +infill_acceleration = 1000 +bridge_acceleration = 1000 +first_layer_acceleration = 800 +default_acceleration = 1250 +support_material_speed = 40 +support_material_interface_speed = 100% [print:*0.8nozzle*] external_perimeter_extrusion_width = 0.9 @@ -356,34 +377,12 @@ bridge_flow_ratio = 0.9 perimeter_acceleration = 800 infill_acceleration = 1000 bridge_acceleration = 1000 -first_layer_acceleration = 1000 +first_layer_acceleration = 800 default_acceleration = 1000 top_solid_min_thickness = 1.2 bottom_solid_min_thickness = 0.8 single_extruder_multi_material_priming = 0 - -[print:*0.6nozzleMK3*] -inherits = *0.6nozzle* -external_perimeter_extrusion_width = 0.65 -extrusion_width = 0.65 -infill_extrusion_width = 0.65 -bridge_flow_ratio = 0.95 -bridge_speed = 25 - -[print:*0.6nozzleMINI*] -inherits = *0.6nozzleMK3* -infill_extrusion_width = 0.68 -solid_infill_extrusion_width = 0.68 -fill_pattern = gyroid -fill_density = 15% -travel_speed = 150 -perimeter_acceleration = 800 -infill_acceleration = 1000 -bridge_acceleration = 1000 -first_layer_acceleration = 1000 -default_acceleration = 1250 -support_material_speed = 40 -support_material_interface_speed = 100% +thick_bridges = 1 [print:*soluble_support*] overhangs = 1 @@ -399,29 +398,29 @@ support_material_threshold = 80 support_material_with_sheath = 1 wipe_tower_bridging = 6 support_material_interface_speed = 80% - -# XXXXXXXXXXXXXXXXXXXX -# XXX--- 0.05mm ---XXX -# XXXXXXXXXXXXXXXXXXXX +support_material_bottom_interface_layers = -1 +thick_bridges = 1 [print:*0.05mm*] inherits = *common* +layer_height = 0.05 bottom_solid_layers = 10 bridge_acceleration = 300 -bridge_flow_ratio = 0.7 +bridge_flow_ratio = 1.15 +bridge_speed = 15 default_acceleration = 1000 external_perimeter_speed = 20 fill_density = 20% -first_layer_acceleration = 500 +first_layer_acceleration = 800 gap_fill_speed = 20 infill_acceleration = 800 infill_speed = 30 max_print_speed = 80 small_perimeter_speed = 20 solid_infill_speed = 30 -support_material_extrusion_width = 0.3 +support_material_extrusion_width = 0.33 support_material_spacing = 1.5 -layer_height = 0.05 +support_material_contact_distance = 0.15 perimeter_acceleration = 300 perimeter_speed = 30 perimeters = 3 @@ -429,151 +428,28 @@ support_material_speed = 30 top_solid_infill_speed = 20 top_solid_layers = 15 -[print:0.05mm ULTRADETAIL] -inherits = *0.05mm* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders==1 -infill_extrusion_width = 0.5 - -# MK3 # -[print:0.05mm ULTRADETAIL @MK3] -inherits = *0.05mm*; *MK3* -fill_pattern = gyroid -fill_density = 15% -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and ! single_extruder_multi_material -top_infill_extrusion_width = 0.4 - -# MK2 # -[print:0.05mm ULTRADETAIL @0.25 nozzle] -inherits = *0.05mm*; *0.25nozzle* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 and num_extruders==1 -fill_density = 20% -infill_speed = 20 -max_print_speed = 100 -perimeter_speed = 20 -small_perimeter_speed = 15 -solid_infill_speed = 20 -support_material_speed = 20 - -# MK3 # -[print:0.05mm ULTRADETAIL @0.25 nozzle MK3] -inherits = *0.05mm*; *0.25nozzle*; *MK3* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 and num_extruders==1 -fill_pattern = grid -fill_density = 20% - -# XXXXXXXXXXXXXXXXXXXX -# XXX--- 0.07mm ---XXX -# XXXXXXXXXXXXXXXXXXXX - [print:*0.07mm*] -inherits = *common* -bottom_solid_layers = 8 -bridge_acceleration = 300 -bridge_flow_ratio = 0.7 -bridge_speed = 20 -default_acceleration = 1000 -external_perimeter_speed = 20 -fill_density = 15% -first_layer_acceleration = 500 -gap_fill_speed = 20 -infill_acceleration = 800 -infill_speed = 40 -max_print_speed = 80 -small_perimeter_speed = 20 -solid_infill_speed = 40 -support_material_extrusion_width = 0.3 -support_material_spacing = 1.5 +inherits = *0.05mm* layer_height = 0.07 -perimeter_acceleration = 300 -perimeter_speed = 30 -perimeters = 3 +bottom_solid_layers = 8 +bridge_flow_ratio = 1 +fill_density = 15% +infill_speed = 40 +solid_infill_speed = 40 support_material_speed = 40 top_solid_infill_speed = 30 top_solid_layers = 11 -# MK3 # -[print:0.07mm ULTRADETAIL @MK3] -inherits = *0.07mm*; *MK3* -fill_pattern = gyroid -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and ! single_extruder_multi_material -top_infill_extrusion_width = 0.4 - -[print:0.07mm ULTRADETAIL @0.25 nozzle MK3] -inherits = *0.07mm*; *0.25nozzle*; *MK3* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 and num_extruders==1 -infill_speed = 30 -solid_infill_speed = 30 -support_material_speed = 30 -top_solid_infill_speed = 20 -fill_pattern = grid -fill_density = 20% - -# XXXXXXXXXXXXXXXXXXXX -# XXX--- 0.10mm ---XXX -# XXXXXXXXXXXXXXXXXXXX - -# MK2 # [print:*0.10mm*] inherits = *common* bottom_solid_layers = 7 -bridge_flow_ratio = 0.7 +bridge_flow_ratio = 1 +bridge_speed = 20 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders==1 layer_height = 0.1 perimeter_acceleration = 800 top_solid_layers = 9 - -# MK2 # -[print:0.10mm DETAIL] -inherits = *0.10mm* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders==1 -external_perimeter_speed = 40 -infill_acceleration = 2000 -infill_speed = 60 -perimeter_speed = 50 -solid_infill_speed = 50 -perimeters = 3 - -# MK3 # -[print:0.10mm DETAIL @MK3] -inherits = *0.10mm*; *MK3* -bridge_speed = 30 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 -external_perimeter_speed = 25 -infill_acceleration = 1000 -infill_speed = 80 -max_print_speed = 200 -perimeter_speed = 45 -solid_infill_speed = 80 -top_infill_extrusion_width = 0.4 -top_solid_infill_speed = 40 -fill_pattern = gyroid -fill_density = 15% -perimeters = 3 - -# MK2 # -[print:0.10mm DETAIL @0.25 nozzle] -inherits = *0.10mm*; *0.25nozzle* -bridge_acceleration = 600 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 -external_perimeter_speed = 20 -infill_acceleration = 1000 -infill_speed = 40 -perimeter_acceleration = 600 -perimeter_speed = 25 -small_perimeter_speed = 15 -solid_infill_speed = 40 -top_solid_infill_speed = 30 - -# MK3 # -[print:0.10mm DETAIL @0.25 nozzle MK3] -inherits = *0.10mm*; *0.25nozzleMK3*; *MK3* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 -fill_pattern = grid -fill_density = 20% - -# XXXXXXXXXXXXXXXXXXXX -# XXX--- 0.15mm ---XXX -# XXXXXXXXXXXXXXXXXXXX +support_material_contact_distance = 0.17 [print:*0.15mm*] inherits = *common* @@ -587,138 +463,8 @@ perimeter_speed = 50 solid_infill_speed = 50 top_infill_extrusion_width = 0.4 top_solid_layers = 7 - -# MK2 # -[print:0.15mm 100mms Linear Advance] -inherits = *0.15mm* -bridge_flow_ratio = 0.95 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 -external_perimeter_speed = 50 -infill_speed = 100 -max_print_speed = 150 -perimeter_speed = 60 -small_perimeter_speed = 30 -solid_infill_speed = 100 -support_material_speed = 60 -top_solid_infill_speed = 70 - -# MK2 # -[print:0.15mm OPTIMAL] -inherits = *0.15mm* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 -top_infill_extrusion_width = 0.45 - -# MK2 # -[print:0.15mm OPTIMAL @0.25 nozzle] -inherits = *0.15mm*; *0.25nozzle* -bridge_acceleration = 600 -bridge_flow_ratio = 0.7 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 -external_perimeter_speed = 20 -infill_acceleration = 1000 -infill_speed = 40 -perimeter_acceleration = 600 -perimeter_speed = 25 -small_perimeter_speed = 15 -solid_infill_speed = 40 -top_solid_infill_speed = 30 - -# MK2 # -[print:0.15mm OPTIMAL @0.6 nozzle] -inherits = *0.15mm*; *0.6nozzle* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 - -# MK3 # -[print:0.15mm QUALITY @MK3] -inherits = *0.15mm*; *MK3* -bridge_speed = 30 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 -external_perimeter_speed = 25 -infill_acceleration = 1000 -infill_speed = 80 -max_print_speed = 200 -perimeter_speed = 45 -solid_infill_speed = 80 -top_solid_infill_speed = 40 -fill_pattern = gyroid -fill_density = 15% - -[print:0.15mm SPEED @MK3] -inherits = *0.15mm*; *MK3* -bridge_speed = 30 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 -external_perimeter_speed = 35 -infill_acceleration = 1000 -infill_speed = 200 -max_print_speed = 200 -perimeter_speed = 60 -solid_infill_speed = 200 -top_solid_infill_speed = 50 - -# MK3 MMU # -[print:0.15mm SOLUBLE FULL @MK3] -inherits = 0.15mm SPEED @MK3; *soluble_support* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 -notes = Set your soluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder -support_material_extruder = 5 -support_material_interface_extruder = 5 -perimeter_speed = 40 -solid_infill_speed = 40 -infill_speed = 80 -top_infill_extrusion_width = 0.45 -top_solid_infill_speed = 30 -support_material_speed = 45 - -# MK3 MMU # -[print:0.15mm SOLUBLE INTERFACE @MK3] -inherits = 0.15mm SOLUBLE FULL @MK3 -notes = Set your soluble extruder in Multiple Extruders > Support material/raft interface extruder -support_material_extruder = 0 -support_material_interface_layers = 3 -support_material_with_sheath = 0 - -# MK2 MMU # -[print:0.15mm OPTIMAL SOLUBLE FULL] -inherits = *0.15mm*; *soluble_support* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 and num_extruders>1 -external_perimeter_speed = 25 -notes = Set your soluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder -perimeter_speed = 40 -solid_infill_speed = 40 -top_infill_extrusion_width = 0.45 -top_solid_infill_speed = 30 - -# MK2 MMU # -[print:0.15mm OPTIMAL SOLUBLE INTERFACE] -inherits = 0.15mm OPTIMAL SOLUBLE FULL -notes = Set your soluble extruder in Multiple Extruders > Support material/raft interface extruder -support_material_extruder = 0 -support_material_interface_layers = 3 -support_material_with_sheath = 0 -support_material_xy_spacing = 80% - -# MK3 # -[print:0.15mm QUALITY @0.25 nozzle MK3] -inherits = *0.15mm*; *0.25nozzleMK3*; *MK3* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 -fill_pattern = grid -fill_density = 20% - -# MK3 # -[print:0.15mm DETAIL @0.6 nozzle MK3] -inherits = *0.15mm*; *0.6nozzleMK3*; *MK306* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 -external_perimeter_speed = 35 -infill_acceleration = 1000 -infill_speed = 70 -max_print_speed = 100 -perimeter_speed = 45 -solid_infill_speed = 70 -top_solid_infill_speed = 45 - -# XXXXXXXXXXXXXXXXXXXX -# XXX--- 0.20mm ---XXX -# XXXXXXXXXXXXXXXXXXXX +bridge_flow_ratio = 1 +bridge_speed = 25 [print:*0.20mm*] inherits = *common* @@ -734,114 +480,6 @@ solid_infill_speed = 50 top_infill_extrusion_width = 0.4 top_solid_layers = 5 -# MK2 # -[print:0.20mm 100mms Linear Advance] -inherits = *0.20mm* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 -external_perimeter_speed = 50 -infill_speed = 100 -max_print_speed = 150 -perimeter_speed = 60 -small_perimeter_speed = 30 -solid_infill_speed = 100 -support_material_speed = 60 -top_solid_infill_speed = 70 - -# MK3 # -[print:0.20mm QUALITY @MK3] -inherits = *0.20mm*; *MK3* -bridge_speed = 30 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 -external_perimeter_speed = 25 -infill_acceleration = 1000 -infill_speed = 80 -max_print_speed = 200 -perimeter_speed = 45 -solid_infill_speed = 80 -top_solid_infill_speed = 40 -fill_pattern = gyroid -fill_density = 15% - -[print:0.20mm SPEED @MK3] -inherits = *0.20mm*; *MK3* -bridge_speed = 30 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 -external_perimeter_speed = 35 -infill_acceleration = 1000 -infill_speed = 200 -max_print_speed = 200 -perimeter_speed = 60 -solid_infill_speed = 200 -top_solid_infill_speed = 50 - -# MK3 MMU # -[print:0.20mm SOLUBLE FULL @MK3] -inherits = 0.20mm SPEED @MK3; *soluble_support* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 -notes = Set your soluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder -support_material_extruder = 5 -support_material_interface_extruder = 5 -perimeter_speed = 40 -solid_infill_speed = 40 -infill_speed = 80 -top_infill_extrusion_width = 0.45 -top_solid_infill_speed = 30 -support_material_speed = 45 - -# MK3 MMU # -[print:0.20mm SOLUBLE INTERFACE @MK3] -inherits = 0.20mm SOLUBLE FULL @MK3 -notes = Set your soluble extruder in Multiple Extruders > Support material/raft interface extruder -support_material_extruder = 0 -support_material_interface_layers = 3 -support_material_with_sheath = 0 - -# MK2 # -[print:0.20mm NORMAL] -inherits = *0.20mm* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 - -# MK2 # -[print:0.20mm NORMAL @0.6 nozzle] -inherits = *0.20mm*; *0.6nozzle* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 - -# MK2 MMU # -[print:0.20mm NORMAL SOLUBLE FULL] -inherits = *0.20mm*; *soluble_support* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 and num_extruders>1 -external_perimeter_speed = 30 -notes = Set your soluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder -perimeter_speed = 40 -solid_infill_speed = 40 -top_solid_infill_speed = 30 - -# MK2 MMU # -[print:0.20mm NORMAL SOLUBLE INTERFACE] -inherits = 0.20mm NORMAL SOLUBLE FULL -notes = Set your soluble extruder in Multiple Extruders > Support material/raft interface extruder -support_material_extruder = 0 -support_material_interface_layers = 3 -support_material_with_sheath = 0 -support_material_xy_spacing = 80% - -# MK3 # -[print:0.20mm DETAIL @0.6 nozzle MK3] -inherits = *0.20mm*; *0.6nozzleMK3*; *MK306* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 -external_perimeter_speed = 35 -infill_acceleration = 1000 -infill_speed = 70 -max_print_speed = 100 -perimeter_speed = 45 -solid_infill_speed = 70 -top_solid_infill_speed = 45 - - -# XXXXXXXXXXXXXXXXXXXX -# XXX--- 0.25mm ---XXX -# XXXXXXXXXXXXXXXXXXXX - [print:*0.25mm*] inherits = *common* bottom_solid_layers = 4 @@ -852,10 +490,6 @@ layer_height = 0.25 perimeter_speed = 50 top_solid_layers = 4 -# XXXXXXXXXXXXXXXXXXXX -# XXX--- 0.30mm ---XXX -# XXXXXXXXXXXXXXXXXXXX - [print:*0.30mm*] inherits = *common* bottom_solid_layers = 4 @@ -869,65 +503,7 @@ perimeter_speed = 50 solid_infill_speed = 50 top_infill_extrusion_width = 0.4 top_solid_layers = 4 - -[print:0.30mm QUALITY @0.6 nozzle MK3] -inherits = *0.30mm*; *0.6nozzleMK3*; *MK306* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 -external_perimeter_speed = 35 -infill_acceleration = 1000 -infill_speed = 70 -max_print_speed = 100 -perimeter_speed = 45 -solid_infill_speed = 70 -top_solid_infill_speed = 45 - -[print:0.30mm SOLUBLE FULL @0.6 nozzle MK3] -inherits = 0.30mm QUALITY @0.6 nozzle MK3; *soluble_support* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 and num_extruders>1 -notes = Set your soluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder -support_material_extruder = 5 -support_material_interface_extruder = 5 -support_material_speed = 40 -perimeter_speed = 40 -solid_infill_speed = 40 -top_infill_extrusion_width = 0.6 -support_material_extrusion_width = 0.6 -top_solid_infill_speed = 30 -support_material_xy_spacing = 80% - -[print:0.30mm SOLUBLE INTERFACE @0.6 nozzle MK3] -inherits = 0.30mm SOLUBLE FULL @0.6 nozzle MK3 -notes = Set your soluble extruder in Multiple Extruders > Support material/raft interface extruder -support_material_extruder = 0 -support_material_interface_layers = 3 -support_material_with_sheath = 0 - -[print:0.30mm DRAFT @MK3] -inherits = *0.30mm*; *MK3* -bottom_solid_layers = 3 -bridge_speed = 30 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 -external_perimeter_speed = 35 -infill_acceleration = 1000 -infill_speed = 85 -max_print_speed = 200 -perimeter_speed = 50 -small_perimeter_speed = 30 -solid_infill_speed = 80 -top_solid_infill_speed = 40 -support_material_speed = 45 -external_perimeter_extrusion_width = 0.6 -extrusion_width = 0.5 -first_layer_extrusion_width = 0.42 -infill_extrusion_width = 0.5 -perimeter_extrusion_width = 0.5 -solid_infill_extrusion_width = 0.5 -top_infill_extrusion_width = 0.45 -support_material_extrusion_width = 0.38 - -# XXXXXXXXXXXXXXXXXXXX -# XXX--- 0.35mm ---XXX -# XXXXXXXXXXXXXXXXXXXX +support_material_contact_distance = 0.3 [print:*0.35mm*] inherits = *common* @@ -946,65 +522,6 @@ solid_infill_speed = 60 top_solid_infill_speed = 50 top_solid_layers = 4 -# MK2 # -[print:0.35mm FAST] -inherits = *0.35mm* -bridge_flow_ratio = 0.95 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 -first_layer_extrusion_width = 0.42 -perimeter_extrusion_width = 0.43 -solid_infill_extrusion_width = 0.7 -top_infill_extrusion_width = 0.43 -support_material_extrusion_width = 0.37 - -# MK2 # -[print:0.35mm FAST @0.6 nozzle] -inherits = *0.35mm*; *0.6nozzle* -# alias = 0.35mm FAST -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 - -# 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_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 soluble extruder in Multiple Extruders > Support material/raft interface extruder -perimeter_speed = 40 -support_material_speed = 40 -support_material_interface_layers = 2 -support_material_xy_spacing = 120% -top_infill_extrusion_width = 0.6 -support_material_extrusion_width = 0.6 - -# MK2 MMU # -[print:0.35mm FAST sol int @0.6 nozzle] -inherits = 0.35mm FAST sol full @0.6 nozzle -support_material_extruder = 0 -support_material_interface_layers = 3 -support_material_with_sheath = 0 -support_material_xy_spacing = 150% - -# MK3 # -[print:0.35mm SPEED @0.6 nozzle MK3] -inherits = *0.35mm*; *0.6nozzleMK3*; *MK306* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 -external_perimeter_speed = 35 -infill_acceleration = 1000 -infill_speed = 70 -max_print_speed = 100 -perimeter_speed = 45 -solid_infill_speed = 70 -top_solid_infill_speed = 45 -external_perimeter_extrusion_width = 0.68 -perimeter_extrusion_width = 0.68 -infill_extrusion_width = 0.68 -solid_infill_extrusion_width = 0.68 - -# XXXXXXXXXXXXXXXXXXXX -# XXX--- 0.40mm ---XXX -# XXXXXXXXXXXXXXXXXXXX - [print:*0.40mm*] inherits = *common* bottom_solid_layers = 3 @@ -1022,71 +539,236 @@ solid_infill_speed = 60 top_solid_infill_speed = 40 top_solid_layers = 4 -# MK3 # -[print:0.40mm DRAFT @0.6 nozzle MK3] -inherits = *0.40mm*; *0.6nozzleMK3*; *MK306* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 -external_perimeter_speed = 35 -infill_acceleration = 1000 -infill_speed = 70 +## MK2 family ## + +## MK2 - 0.4mm nozzle +[print:0.05mm ULTRADETAIL] +inherits = *0.05mm* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders==1 +infill_extrusion_width = 0.5 + +[print:0.10mm DETAIL] +inherits = *0.10mm* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders==1 +external_perimeter_speed = 40 +infill_acceleration = 2000 +infill_speed = 60 +perimeter_speed = 50 +solid_infill_speed = 50 +perimeters = 3 +bridge_acceleration = 800 + +[print:0.15mm 100mms Linear Advance] +inherits = *0.15mm* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 +external_perimeter_speed = 50 +infill_speed = 100 +max_print_speed = 150 +perimeter_speed = 60 +small_perimeter_speed = 30 +solid_infill_speed = 100 +support_material_speed = 60 +top_solid_infill_speed = 70 + +[print:0.15mm OPTIMAL] +inherits = *0.15mm* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 +top_infill_extrusion_width = 0.45 + +[print:0.20mm 100mms Linear Advance] +inherits = *0.20mm* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 +external_perimeter_speed = 50 +infill_speed = 100 +max_print_speed = 150 +perimeter_speed = 60 +small_perimeter_speed = 30 +solid_infill_speed = 100 +support_material_speed = 60 +top_solid_infill_speed = 70 + +[print:0.20mm NORMAL] +inherits = *0.20mm* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 + +[print:0.35mm FAST] +inherits = *0.35mm* +bridge_flow_ratio = 0.95 +bridge_speed = 30 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 +first_layer_extrusion_width = 0.42 +perimeter_extrusion_width = 0.43 +solid_infill_extrusion_width = 0.7 +top_infill_extrusion_width = 0.45 +support_material_extrusion_width = 0.37 +support_material_contact_distance = 0.1 +top_solid_infill_speed = 40 +thick_bridges = 1 + +## MMU1 specific +[print:0.15mm OPTIMAL SOLUBLE FULL] +inherits = *0.15mm*; *soluble_support* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 and num_extruders>1 +external_perimeter_speed = 25 +notes = Set your soluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder +perimeter_speed = 40 +solid_infill_speed = 40 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 30 +bridge_flow_ratio = 0.8 +bridge_speed = 30 + +[print:0.15mm OPTIMAL SOLUBLE INTERFACE] +inherits = 0.15mm OPTIMAL SOLUBLE FULL +notes = Set your soluble extruder in Multiple Extruders > Support material/raft interface extruder +support_material_extruder = 0 +support_material_interface_layers = 3 +support_material_with_sheath = 0 +support_material_xy_spacing = 80% + +[print:0.20mm NORMAL SOLUBLE FULL] +inherits = *0.20mm*; *soluble_support* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 and num_extruders>1 +external_perimeter_speed = 30 +notes = Set your soluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder +perimeter_speed = 40 +solid_infill_speed = 40 +top_solid_infill_speed = 30 +bridge_flow_ratio = 0.95 +bridge_speed = 30 + +[print:0.20mm NORMAL SOLUBLE INTERFACE] +inherits = 0.20mm NORMAL SOLUBLE FULL +notes = Set your soluble extruder in Multiple Extruders > Support material/raft interface extruder +support_material_extruder = 0 +support_material_interface_layers = 3 +support_material_with_sheath = 0 +support_material_xy_spacing = 80% + +## MK2 - 0.25mm nozzle + +[print:0.05mm ULTRADETAIL @0.25 nozzle] +inherits = *0.05mm*; *0.25nozzle* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 and num_extruders==1 +fill_density = 20% +infill_speed = 20 max_print_speed = 100 -perimeter_speed = 45 -solid_infill_speed = 70 -top_solid_infill_speed = 45 -external_perimeter_extrusion_width = 0.68 -perimeter_extrusion_width = 0.68 -infill_extrusion_width = 0.68 -solid_infill_extrusion_width = 0.68 +perimeter_speed = 20 +small_perimeter_speed = 15 +solid_infill_speed = 20 +support_material_speed = 20 +support_material_contact_distance = 0.07 -# XXXXXXXXXXXXXXXXXXXXXX -# XXX----- MK2.5 ----XXX -# XXXXXXXXXXXXXXXXXXXXXX +[print:0.10mm DETAIL @0.25 nozzle] +inherits = *0.10mm*; *0.25nozzle* +bridge_acceleration = 600 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 +external_perimeter_speed = 20 +infill_acceleration = 1000 +infill_speed = 40 +perimeter_acceleration = 600 +perimeter_speed = 25 +small_perimeter_speed = 15 +solid_infill_speed = 40 +top_solid_infill_speed = 30 +support_material_contact_distance = 0.07 -# MK2.5 # -[print:0.15mm 100mms Linear Advance @MK2.5] -inherits = 0.15mm 100mms Linear Advance -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 -single_extruder_multi_material_priming = 0 +[print:0.15mm OPTIMAL @0.25 nozzle] +inherits = *0.15mm*; *0.25nozzle* +bridge_acceleration = 600 +bridge_flow_ratio = 0.8 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 +external_perimeter_speed = 20 +infill_acceleration = 1000 +infill_speed = 40 +perimeter_acceleration = 600 +perimeter_speed = 25 +small_perimeter_speed = 15 +solid_infill_speed = 40 +top_solid_infill_speed = 30 +support_material_contact_distance = 0.08 -# MK2.5 # -[print:0.15mm OPTIMAL @MK2.5] -inherits = 0.15mm OPTIMAL -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 -single_extruder_multi_material_priming = 0 +## MK2 - 0.6mm nozzle + +[print:0.15mm OPTIMAL @0.6 nozzle] +inherits = *0.15mm*; *0.6nozzle* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 + +[print:0.20mm NORMAL @0.6 nozzle] +inherits = *0.20mm*; *0.6nozzle* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 + +[print:0.35mm FAST @0.6 nozzle] +inherits = *0.35mm*; *0.6nozzle* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 + +## MMU1 specific +[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_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 soluble extruder in Multiple Extruders > Support material/raft interface extruder +perimeter_speed = 40 +support_material_speed = 40 +support_material_interface_layers = 2 +support_material_xy_spacing = 120% +top_infill_extrusion_width = 0.6 +support_material_extrusion_width = 0.6 + +[print:0.35mm FAST sol int @0.6 nozzle] +inherits = 0.35mm FAST sol full @0.6 nozzle +support_material_extruder = 0 +support_material_interface_layers = 3 +support_material_with_sheath = 0 +support_material_xy_spacing = 150% + +## MK2.5 -# MK2.5 MMU2 # [print:0.10mm DETAIL @MK2.5] inherits = 0.10mm DETAIL compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 single_extruder_multi_material_priming = 0 -# MK2.5 MMU2 # +[print:0.15mm 100mms Linear Advance @MK2.5] +inherits = 0.15mm 100mms Linear Advance +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 +single_extruder_multi_material_priming = 0 + +[print:0.15mm OPTIMAL @MK2.5] +inherits = 0.15mm OPTIMAL +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 +single_extruder_multi_material_priming = 0 + +[print:0.20mm 100mms Linear Advance @MK2.5] +inherits = 0.20mm 100mms Linear Advance +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 +single_extruder_multi_material_priming = 0 + +[print:0.20mm NORMAL @MK2.5] +inherits = 0.20mm NORMAL +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 +single_extruder_multi_material_priming = 0 + +[print:0.35mm FAST @MK2.5] +inherits = 0.35mm FAST +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 +single_extruder_multi_material_priming = 0 + +## MK2.5 - MMU2 specific + [print:0.15mm OPTIMAL SOLUBLE FULL @MK2.5] inherits = 0.15mm OPTIMAL SOLUBLE FULL support_material_extruder = 5 support_material_interface_extruder = 5 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 -# MK2.5 MMU2 # [print:0.15mm OPTIMAL SOLUBLE INTERFACE @MK2.5] inherits = 0.15mm OPTIMAL SOLUBLE INTERFACE support_material_extruder = 0 support_material_interface_extruder = 5 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 -# MK2.5 # -[print:0.20mm 100mms Linear Advance @MK2.5] -inherits = 0.20mm 100mms Linear Advance -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 -single_extruder_multi_material_priming = 0 - -# MK2.5 # -[print:0.20mm NORMAL @MK2.5] -inherits = 0.20mm NORMAL -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 -single_extruder_multi_material_priming = 0 - -# MK2.5 MMU2 # [print:0.20mm NORMAL SOLUBLE FULL @MK2.5] inherits = 0.20mm NORMAL SOLUBLE FULL support_material_extruder = 5 @@ -1094,7 +776,6 @@ support_material_interface_extruder = 5 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 single_extruder_multi_material_priming = 0 -# MK2.5 MMU2 # [print:0.20mm NORMAL SOLUBLE INTERFACE @MK2.5] inherits = 0.20mm NORMAL SOLUBLE INTERFACE support_material_extruder = 0 @@ -1102,14 +783,7 @@ support_material_interface_extruder = 5 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 single_extruder_multi_material_priming = 0 -# MK2.5 # -[print:0.35mm FAST @MK2.5] -inherits = 0.35mm FAST -# alias = 0.35mm FAST -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 -single_extruder_multi_material_priming = 0 - -# MK2.5 MMU2 0.6 nozzle # +# MK2.5 MMU2 0.6 nozzle [print:0.35mm SOLUBLE FULL @0.6 nozzle MK2.5] inherits = *0.35mm*; *0.6nozzle*; *soluble_support* compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and printer_model!="MK2SMM" and nozzle_diameter[0]==0.6 and num_extruders>1 @@ -1132,12 +806,296 @@ support_material_interface_layers = 3 support_material_with_sheath = 0 support_material_xy_spacing = 80% -## 0.8mm nozzle print profiles +## MK3 family ## + +## MK3 - 0.4mm nozzle + +[print:0.05mm ULTRADETAIL @MK3] +inherits = *0.05mm*; *MK3* +fill_pattern = gyroid +fill_density = 15% +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and ! single_extruder_multi_material +top_infill_extrusion_width = 0.4 + +[print:0.07mm ULTRADETAIL @MK3] +inherits = *0.07mm*; *MK3* +fill_pattern = gyroid +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and ! single_extruder_multi_material +top_infill_extrusion_width = 0.4 + +[print:0.10mm DETAIL @MK3] +inherits = *0.10mm*; *MK3* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 +external_perimeter_speed = 25 +infill_acceleration = 1000 +bridge_acceleration = 800 +infill_speed = 80 +max_print_speed = 200 +perimeter_speed = 45 +solid_infill_speed = 80 +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 40 +fill_pattern = gyroid +fill_density = 15% +perimeters = 3 + +[print:0.15mm QUALITY @MK3] +inherits = *0.15mm*; *MK3* +bridge_speed = 25 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 +external_perimeter_speed = 25 +infill_acceleration = 1000 +infill_speed = 80 +max_print_speed = 200 +perimeter_speed = 45 +solid_infill_speed = 80 +top_solid_infill_speed = 40 +fill_pattern = gyroid +fill_density = 15% + +[print:0.15mm SPEED @MK3] +inherits = *0.15mm*; *MK3* +bridge_speed = 25 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 +external_perimeter_speed = 35 +infill_acceleration = 1000 +infill_speed = 200 +max_print_speed = 200 +perimeter_speed = 60 +solid_infill_speed = 200 +top_solid_infill_speed = 50 + +[print:0.20mm QUALITY @MK3] +inherits = *0.20mm*; *MK3* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 +external_perimeter_speed = 25 +infill_acceleration = 1000 +infill_speed = 80 +max_print_speed = 200 +perimeter_speed = 45 +solid_infill_speed = 80 +top_solid_infill_speed = 40 +fill_pattern = gyroid +fill_density = 15% + +[print:0.20mm SPEED @MK3] +inherits = *0.20mm*; *MK3* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 +external_perimeter_speed = 35 +infill_acceleration = 1000 +infill_speed = 200 +max_print_speed = 200 +perimeter_speed = 60 +solid_infill_speed = 200 +top_solid_infill_speed = 50 + +[print:0.30mm DRAFT @MK3] +inherits = *0.30mm*; *MK3* +bottom_solid_layers = 3 +bridge_speed = 25 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 +external_perimeter_speed = 35 +infill_acceleration = 1000 +infill_speed = 85 +max_print_speed = 200 +perimeter_speed = 50 +small_perimeter_speed = 30 +solid_infill_speed = 80 +top_solid_infill_speed = 40 +support_material_speed = 45 +external_perimeter_extrusion_width = 0.6 +extrusion_width = 0.5 +first_layer_extrusion_width = 0.42 +infill_extrusion_width = 0.5 +perimeter_extrusion_width = 0.5 +solid_infill_extrusion_width = 0.5 +top_infill_extrusion_width = 0.45 +support_material_extrusion_width = 0.38 +support_material_contact_distance = 0.2 + +## MK3 - MMU2 specific +[print:0.15mm SOLUBLE FULL @MK3] +inherits = 0.15mm SPEED @MK3; *soluble_support* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 +notes = Set your soluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder +support_material_extruder = 5 +support_material_interface_extruder = 5 +perimeter_speed = 40 +solid_infill_speed = 40 +infill_speed = 80 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 30 +support_material_speed = 45 +bridge_flow_ratio = 0.8 +bridge_speed = 30 + +[print:0.15mm SOLUBLE INTERFACE @MK3] +inherits = 0.15mm SOLUBLE FULL @MK3 +notes = Set your soluble extruder in Multiple Extruders > Support material/raft interface extruder +support_material_extruder = 0 +support_material_interface_layers = 3 +support_material_with_sheath = 0 + +[print:0.20mm SOLUBLE FULL @MK3] +inherits = 0.20mm SPEED @MK3; *soluble_support* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 +notes = Set your soluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder +support_material_extruder = 5 +support_material_interface_extruder = 5 +perimeter_speed = 40 +solid_infill_speed = 40 +infill_speed = 80 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 30 +support_material_speed = 45 +bridge_flow_ratio = 0.95 +bridge_speed = 30 + +[print:0.20mm SOLUBLE INTERFACE @MK3] +inherits = 0.20mm SOLUBLE FULL @MK3 +notes = Set your soluble extruder in Multiple Extruders > Support material/raft interface extruder +support_material_extruder = 0 +support_material_interface_layers = 3 +support_material_with_sheath = 0 + +## MK3 - 0.25mm nozzle + +[print:0.05mm ULTRADETAIL @0.25 nozzle MK3] +inherits = *0.05mm*; *0.25nozzle*; *MK3* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 and num_extruders==1 +fill_pattern = grid +fill_density = 20% +support_material_contact_distance = 0.07 + +[print:0.07mm ULTRADETAIL @0.25 nozzle MK3] +inherits = *0.07mm*; *0.25nozzle*; *MK3* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 and num_extruders==1 +infill_speed = 30 +solid_infill_speed = 30 +support_material_speed = 30 +top_solid_infill_speed = 20 +fill_pattern = grid +fill_density = 20% +support_material_contact_distance = 0.07 + +[print:0.10mm DETAIL @0.25 nozzle MK3] +inherits = *0.10mm*; *0.25nozzleMK3*; *MK3* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 +fill_pattern = grid +fill_density = 20% +support_material_contact_distance = 0.07 + +[print:0.15mm QUALITY @0.25 nozzle MK3] +inherits = *0.15mm*; *0.25nozzleMK3*; *MK3* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 +fill_pattern = grid +fill_density = 20% +support_material_contact_distance = 0.08 + +## MK3 - 0.6mm nozzle + +[print:0.15mm DETAIL @0.6 nozzle MK3] +inherits = *0.15mm*; *0.6nozzleMK3*; *MK306* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 +external_perimeter_speed = 35 +infill_acceleration = 1000 +infill_speed = 70 +max_print_speed = 100 +perimeter_speed = 45 +solid_infill_speed = 70 +top_solid_infill_speed = 45 +support_material_contact_distance = 0.22 +bridge_flow_ratio = 1 + +[print:0.20mm DETAIL @0.6 nozzle MK3] +inherits = *0.20mm*; *0.6nozzleMK3*; *MK306* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 +external_perimeter_speed = 35 +infill_acceleration = 1000 +infill_speed = 70 +max_print_speed = 100 +perimeter_speed = 45 +solid_infill_speed = 70 +top_solid_infill_speed = 45 +support_material_contact_distance = 0.22 +bridge_flow_ratio = 1 + +[print:0.30mm QUALITY @0.6 nozzle MK3] +inherits = *0.30mm*; *0.6nozzleMK3*; *MK306* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 +external_perimeter_speed = 35 +infill_acceleration = 1000 +infill_speed = 70 +max_print_speed = 100 +perimeter_speed = 45 +solid_infill_speed = 70 +top_solid_infill_speed = 45 +support_material_contact_distance = 0.25 +bridge_flow_ratio = 1 + +[print:0.35mm SPEED @0.6 nozzle MK3] +inherits = *0.35mm*; *0.6nozzleMK3*; *MK306* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 +external_perimeter_speed = 35 +infill_acceleration = 1000 +infill_speed = 70 +max_print_speed = 100 +perimeter_speed = 45 +solid_infill_speed = 70 +top_solid_infill_speed = 45 +external_perimeter_extrusion_width = 0.68 +perimeter_extrusion_width = 0.68 +infill_extrusion_width = 0.68 +solid_infill_extrusion_width = 0.68 +support_material_contact_distance = 0.25 +bridge_flow_ratio = 0.95 + +[print:0.40mm DRAFT @0.6 nozzle MK3] +inherits = *0.40mm*; *0.6nozzleMK3*; *MK306* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 +external_perimeter_speed = 35 +infill_acceleration = 1000 +infill_speed = 70 +max_print_speed = 100 +perimeter_speed = 45 +solid_infill_speed = 70 +top_solid_infill_speed = 45 +external_perimeter_extrusion_width = 0.68 +perimeter_extrusion_width = 0.68 +infill_extrusion_width = 0.68 +solid_infill_extrusion_width = 0.68 +support_material_contact_distance = 0.25 +bridge_flow_ratio = 0.95 + +## MK3 - MMU2 specific + +[print:0.30mm SOLUBLE FULL @0.6 nozzle MK3] +inherits = 0.30mm QUALITY @0.6 nozzle MK3; *soluble_support* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 and num_extruders>1 +notes = Set your soluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder +support_material_extruder = 5 +support_material_interface_extruder = 5 +support_material_speed = 40 +perimeter_speed = 40 +solid_infill_speed = 40 +top_infill_extrusion_width = 0.6 +support_material_extrusion_width = 0.6 +top_solid_infill_speed = 30 +support_material_xy_spacing = 80% + +[print:0.30mm SOLUBLE INTERFACE @0.6 nozzle MK3] +inherits = 0.30mm SOLUBLE FULL @0.6 nozzle MK3 +notes = Set your soluble extruder in Multiple Extruders > Support material/raft interface extruder +support_material_extruder = 0 +support_material_interface_layers = 3 +support_material_with_sheath = 0 + +## 0.8mm nozzle - MK2.5 and MK3 +## Only for MMU2 Single mode at the moment [print:0.30mm DETAIL @0.8 nozzle] inherits = *common*; *0.8nozzle* layer_height = 0.30 -## Only for MMU2 Single mode at the moment compatible_printers_condition = printer_model=~/(MK3|MK2.5).*/ and nozzle_diameter[0]==0.8 and num_extruders==1 perimeter_speed = 35 external_perimeter_speed = 25 @@ -1151,7 +1109,6 @@ support_material_speed = 40 [print:0.40mm QUALITY @0.8 nozzle] inherits = *common*; *0.8nozzle* layer_height = 0.4 -## Only for MMU2 Single mode at the moment compatible_printers_condition = printer_model=~/(MK3|MK2.5).*/ and nozzle_diameter[0]==0.8 and num_extruders==1 perimeter_speed = 35 external_perimeter_speed = 25 @@ -1165,7 +1122,6 @@ support_material_speed = 40 [print:0.55mm DRAFT @0.8 nozzle] inherits = *common*; *0.8nozzle* layer_height = 0.55 -## Only for MMU2 Single mode at the moment compatible_printers_condition = printer_model=~/(MK3|MK2.5).*/ and nozzle_diameter[0]==0.8 and num_extruders==1 perimeter_speed = 30 external_perimeter_speed = 25 @@ -1179,9 +1135,9 @@ top_solid_infill_speed = 30 external_perimeter_extrusion_width = 1 perimeter_extrusion_width = 1 -## MINI print profiles +## MINI ## -# 0.4mm nozzle +# MINI - 0.4mm nozzle [print:0.05mm ULTRADETAIL @MINI] inherits = *0.05mm*; *MINI* @@ -1194,6 +1150,8 @@ perimeter_extrusion_width = 0.4 external_perimeter_extrusion_width = 0.4 support_material_xy_spacing = 60% support_material_speed = 30 +support_material_extrusion_width = 0.35 +bridge_acceleration = 300 [print:0.07mm ULTRADETAIL @MINI] inherits = *0.07mm*; *MINI* @@ -1205,10 +1163,13 @@ small_perimeter_speed = 15 perimeter_extrusion_width = 0.4 external_perimeter_extrusion_width = 0.4 support_material_xy_spacing = 60% +support_material_extrusion_width = 0.35 +bridge_acceleration = 300 [print:0.10mm DETAIL @MINI] inherits = *0.10mm*; *MINI* -bridge_speed = 30 +bridge_speed = 20 +bridge_acceleration = 700 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.4 perimeter_speed = 40 external_perimeter_speed = 30 @@ -1219,12 +1180,10 @@ top_solid_infill_speed = 40 fill_pattern = gyroid fill_density = 15% perimeters = 3 -bridge_acceleration = 1000 support_material_xy_spacing = 60% [print:0.15mm QUALITY @MINI] inherits = *0.15mm*; *MINI* -bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.4 perimeter_speed = 40 external_perimeter_speed = 30 @@ -1233,24 +1192,20 @@ solid_infill_speed = 80 top_solid_infill_speed = 40 fill_pattern = gyroid fill_density = 15% -bridge_flow_ratio = 0.85 support_material_xy_spacing = 60% [print:0.15mm SPEED @MINI] inherits = *0.15mm*; *MINI* -bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.4 perimeter_speed = 50 external_perimeter_speed = 40 infill_speed = 140 solid_infill_speed = 140 top_solid_infill_speed = 40 -bridge_flow_ratio = 0.85 support_material_xy_spacing = 60% [print:0.20mm QUALITY @MINI] inherits = *0.20mm*; *MINI* -bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.4 perimeter_speed = 40 external_perimeter_speed = 30 @@ -1263,7 +1218,6 @@ support_material_xy_spacing = 60% [print:0.20mm SPEED @MINI] inherits = *0.20mm*; *MINI* -bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.4 perimeter_speed = 50 external_perimeter_speed = 40 @@ -1275,7 +1229,8 @@ support_material_xy_spacing = 60% [print:0.25mm DRAFT @MINI] inherits = *0.25mm*; *MINI* -bridge_speed = 30 +bridge_speed = 25 +bridge_flow_ratio = 0.95 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.4 external_perimeter_speed = 40 infill_speed = 110 @@ -1288,8 +1243,9 @@ infill_extrusion_width = 0.45 solid_infill_extrusion_width = 0.45 top_infill_extrusion_width = 0.4 support_material_xy_spacing = 60% +support_material_contact_distance = 0.2 -# 0.25mm nozzle +# MINI - 0.25mm nozzle [print:0.05mm ULTRADETAIL @0.25 nozzle MINI] inherits = *0.05mm*; *0.25nozzle*; *MINI* @@ -1297,6 +1253,7 @@ compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and fill_pattern = grid fill_density = 20% support_material_speed = 30 +support_material_contact_distance = 0.07 [print:0.07mm ULTRADETAIL @0.25 nozzle MINI] inherits = *0.07mm*; *0.25nozzle*; *MINI* @@ -1307,20 +1264,23 @@ support_material_speed = 30 top_solid_infill_speed = 20 fill_pattern = grid fill_density = 20% +support_material_contact_distance = 0.07 [print:0.10mm DETAIL @0.25 nozzle MINI] inherits = *0.10mm*; *0.25nozzleMINI*; *MINI* compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.25 fill_pattern = grid fill_density = 20% +support_material_contact_distance = 0.07 [print:0.15mm QUALITY @0.25 nozzle MINI] inherits = *0.15mm*; *0.25nozzleMINI*; *MINI* compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.25 fill_pattern = grid fill_density = 20% +support_material_contact_distance = 0.08 -# 0.6mm nozzle MINI +# MINI - 0.6mm nozzle [print:0.15mm DETAIL @0.6 nozzle MINI] inherits = *0.15mm*; *0.6nozzleMINI* @@ -1333,6 +1293,8 @@ solid_infill_speed = 70 top_solid_infill_speed = 45 infill_extrusion_width = 0.65 solid_infill_extrusion_width = 0.65 +support_material_contact_distance = 0.22 +bridge_flow_ratio = 1 [print:0.20mm DETAIL @0.6 nozzle MINI] inherits = *0.20mm*; *0.6nozzleMINI* @@ -1345,6 +1307,8 @@ solid_infill_speed = 70 top_solid_infill_speed = 45 infill_extrusion_width = 0.65 solid_infill_extrusion_width = 0.65 +support_material_contact_distance = 0.22 +bridge_flow_ratio = 1 [print:0.30mm QUALITY @0.6 nozzle MINI] inherits = *0.30mm*; *0.6nozzleMINI* @@ -1357,6 +1321,8 @@ solid_infill_speed = 65 top_solid_infill_speed = 45 external_perimeter_extrusion_width = 0.68 perimeter_extrusion_width = 0.68 +support_material_contact_distance = 0.25 +bridge_flow_ratio = 1 [print:0.35mm SPEED @0.6 nozzle MINI] inherits = *0.35mm*; *0.6nozzleMINI* @@ -1369,6 +1335,8 @@ solid_infill_speed = 60 top_solid_infill_speed = 45 external_perimeter_extrusion_width = 0.68 perimeter_extrusion_width = 0.68 +support_material_contact_distance = 0.25 +bridge_flow_ratio = 0.95 [print:0.40mm DRAFT @0.6 nozzle MINI] inherits = *0.40mm*; *0.6nozzleMINI* @@ -1383,8 +1351,10 @@ external_perimeter_extrusion_width = 0.68 perimeter_extrusion_width = 0.68 infill_extrusion_width = 0.68 solid_infill_extrusion_width = 0.68 +support_material_contact_distance = 0.25 +bridge_flow_ratio = 0.95 -# 0.8mm nozzle MINI +# MINI - 0.8mm nozzle [print:0.30mm DETAIL @0.8 nozzle MINI] inherits = 0.30mm DETAIL @0.8 nozzle @@ -1490,7 +1460,7 @@ max_fan_speed = 50 min_fan_speed = 30 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.6}0.12{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.8}0.06{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/}0.2{elsif nozzle_diameter[0]==0.8}0.02{elsif nozzle_diameter[0]==0.6}0.04{else}0.08{endif} ; Filament gcode LA 1.5\n{if printer_notes=~/.*PRINTER_MODEL_MINI.*/};{elsif printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}M900 K200{elsif nozzle_diameter[0]==0.6}M900 K24{elsif nozzle_diameter[0]==0.8};{else}M900 K45{endif} ; Filament gcode LA 1.0" temperature = 240 -filament_retract_length = 1.4 +filament_retract_length = 1 filament_retract_lift = 0.2 compatible_printers_condition = printer_model!="MK2SMM" and printer_model!="MINI" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) @@ -1500,14 +1470,14 @@ compatible_printers_condition = nozzle_diameter[0]==0.6 and printer_model!="MK2S filament_max_volumetric_speed = 15 [filament:*PETMMU1*] -inherits = *PET* +; inherits = *PET* filament_retract_length = nil filament_retract_speed = nil filament_retract_lift = 0.2 compatible_printers_condition = printer_model=="MK2SMM" [filament:*PETMINI*] -inherits = *PET* +; inherits = *PET* filament_retract_length = nil filament_retract_speed = 40 filament_deretract_speed = 25 @@ -1518,7 +1488,7 @@ compatible_printers_condition = printer_model=="MINI" start_filament_gcode = "M900 K{if nozzle_diameter[0]==0.6}0.12{elsif nozzle_diameter[0]==0.8}0.06{else}0.2{endif} ; Filament gcode" [filament:*PETMINI06*] -inherits = *PET* +; inherits = *PET* filament_retract_length = nil filament_retract_speed = 40 filament_deretract_speed = 25 @@ -1529,7 +1499,7 @@ start_filament_gcode = "M900 K0.12 ; Filament gcode" filament_max_volumetric_speed = 13 [filament:*ABSMINI*] -inherits = *ABS* +; inherits = *ABS* bed_temperature = 100 filament_retract_length = 2.7 filament_retract_speed = nil @@ -1861,8 +1831,8 @@ min_fan_speed = 20 max_fan_speed = 20 min_print_speed = 15 slowdown_below_layer_time = 15 -first_layer_temperature = 265 -temperature = 265 +first_layer_temperature = 260 +temperature = 260 filament_type = ASA [filament:Prusament ASA] @@ -2015,9 +1985,10 @@ filament_cost = 27.82 filament_density = 1.04 filament_spool_weight = 245 -[filament:Plasty Mladec ABS] +[filament:Filament PM ABS] inherits = *ABSC* -filament_vendor = Plasty Mladec +renamed_from = "Plasty Mladec ABS" +filament_vendor = Filament PM filament_cost = 27.82 filament_density = 1.08 filament_spool_weight = 230 @@ -2038,9 +2009,21 @@ filament_cost = 27.82 filament_density = 1.27 compatible_printers_condition = nozzle_diameter[0]!=0.8 and printer_model!="MK2SMM" and printer_model!="MINI" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) -[filament:Plasty Mladec PETG] +[filament:Extrudr PETG] inherits = *PET* -filament_vendor = Plasty Mladec +filament_vendor = Extrudr +filament_cost = 35.45 +filament_density = 1.29 +temperature = 220 +bed_temperature = 70 +first_layer_temperature = 220 +first_layer_bed_temperature = 70 +slowdown_below_layer_time = 20 + +[filament:Filament PM PETG] +inherits = *PET* +renamed_from = "Plasty Mladec PETG" +filament_vendor = Filament PM filament_cost = 27.82 filament_density = 1.27 filament_spool_weight = 230 @@ -2301,9 +2284,10 @@ filament_vendor = Made for Prusa filament_cost = 27.82 filament_spool_weight = 230 -[filament:Plasty Mladec ABS @MMU2] +[filament:Filament PM ABS @MMU2] inherits = *ABS MMU2* -filament_vendor = Plasty Mladec +renamed_from = "Plasty Mladec ABS @MMU2" +filament_vendor = Filament PM filament_density = 1.08 filament_cost = 27.82 filament_spool_weight = 230 @@ -2402,9 +2386,10 @@ filament_density = 1.27 filament_spool_weight = 201 filament_type = PETG -[filament:Plasty Mladec PETG @0.6 nozzle] +[filament:Filament PM PETG @0.6 nozzle] inherits = *PET06* -filament_vendor = Plasty Mladec +renamed_from = "Plasty Mladec PETG @0.6 nozzle" +filament_vendor = Filament PM first_layer_temperature = 230 temperature = 240 filament_cost = 27.82 @@ -2458,7 +2443,7 @@ 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_length = 1 filament_retract_lift = 0.2 [filament:*PET MMU2 06*] @@ -2471,9 +2456,10 @@ inherits = *PET MMU2* renamed_from = "Generic PET MMU2"; "Generic PETG MMU2" filament_vendor = Generic -[filament:Plasty Mladec PETG @MMU2] +[filament:Filament PM PETG @MMU2] inherits = *PET MMU2* -filament_vendor = Plasty Mladec +renamed_from = "Plasty Mladec PETG @MMU2" +filament_vendor = Filament PM filament_spool_weight = 230 [filament:Prusa PETG @MMU2] @@ -2510,10 +2496,11 @@ filament_cost = 36.29 filament_density = 1.27 filament_spool_weight = 201 -[filament:Plasty Mladec PETG @MMU2 0.6 nozzle] +[filament:Filament PM PETG @MMU2 0.6 nozzle] inherits = *PET MMU2 06* +renamed_from = "Plasty Mladec PETG @MMU2 0.6 nozzle" filament_type = PETG -filament_vendor = Plasty Mladec +filament_vendor = Filament PM filament_spool_weight = 230 [filament:Prusa PLA] @@ -2530,9 +2517,10 @@ filament_vendor = Fiberlogy filament_cost = 25.4 filament_density = 1.24 -[filament:Plasty Mladec PLA] +[filament:Filament PM PLA] inherits = *PLA* -filament_vendor = Plasty Mladec +renamed_from = "Plasty Mladec PLA" +filament_vendor = Filament PM filament_cost = 27.82 filament_density = 1.24 filament_spool_weight = 230 @@ -2576,6 +2564,26 @@ filament_vendor = EUMAKERS filament_cost = 25.4 filament_density = 1.24 +[filament:Extrudr PLA NX1] +inherits = *PLA* +filament_vendor = Extrudr +filament_cost = 22.76 +filament_density = 1.24 +temperature = 205 +bed_temperature = 60 +first_layer_temperature = 205 +first_layer_bed_temperature = 60 +full_fan_speed_layer = 3 +max_fan_speed = 90 +min_fan_speed = 30 +slowdown_below_layer_time = 20 + +[filament:Extrudr PLA NX2] +inherits = Extrudr PLA NX1 +filament_vendor = Extrudr +filament_cost = 23.63 +filament_density = 1.3 + [filament:Floreon3D PLA] inherits = *PLA* filament_vendor = Floreon3D @@ -2930,204 +2938,75 @@ temperature = 220 ## Filaments MMU1 [filament:ColorFabb HT @MMU1] -inherits = *PETMMU1* -filament_vendor = ColorFabb -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 -filament_spool_weight = 236 -first_layer_bed_temperature = 105 -first_layer_temperature = 270 -max_fan_speed = 20 -min_fan_speed = 10 +inherits = ColorFabb HT; *PETMMU1* 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_vendor = ColorFabb -filament_type = PETG -filament_cost = 62.90 -filament_density = 1.27 -filament_spool_weight = 236 -first_layer_bed_temperature = 90 -first_layer_temperature = 260 -temperature = 270 +inherits = ColorFabb XT; *PETMMU1* [filament:ColorFabb XT-CF20 @MMU1] -inherits = *PETMMU1* -filament_vendor = ColorFabb -compatible_printers_condition = nozzle_diameter[0]>=0.4 and printer_model=="MK2SMM" -extrusion_multiplier = 1.05 -filament_cost = 80.65 -filament_density = 1.35 -filament_spool_weight = 236 -filament_colour = #804040 -filament_max_volumetric_speed = 2 -first_layer_bed_temperature = 90 -first_layer_temperature = 260 +inherits = ColorFabb XT-CF20; *PETMMU1* 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_vendor = ColorFabb -filament_cost = 21.2 -filament_density = 1.2 -filament_spool_weight = 236 -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 +inherits = ColorFabb nGen; *PETMMU1* [filament:E3D Edge @MMU1] -inherits = *PETMMU1* -filament_vendor = E3D -filament_cost = 56.9 -filament_density = 1.26 -filament_type = EDGE +inherits = E3D Edge; *PETMMU1* [filament:Fillamentum CPE @MMU1] -inherits = *PETMMU1* -filament_vendor = Fillamentum -filament_cost = 56.45 -filament_density = 1.25 -filament_spool_weight = 230 -filament_type = CPE -first_layer_bed_temperature = 90 -first_layer_temperature = 275 -max_fan_speed = 50 -min_fan_speed = 50 -disable_fan_first_layers = 3 -full_fan_speed_layer = 5 -temperature = 275 +inherits = Fillamentum CPE; *PETMMU1* [filament:Generic PETG @MMU1] -inherits = *PETMMU1* +inherits = Generic PETG; *PETMMU1* renamed_from = "Generic PET MMU1"; "Generic PETG MMU1" -filament_vendor = Generic -filament_cost = 27.82 -filament_density = 1.27 [filament:Devil Design PETG @MMU1] -inherits = *PETMMU1* -filament_vendor = Devil Design -filament_cost = 20.99 -filament_density = 1.23 -filament_spool_weight = 250 -first_layer_temperature = 230 -first_layer_bed_temperature = 85 -temperature = 230 -bed_temperature = 90 +inherits = Devil Design PETG; *PETMMU1* -[filament:Plasty Mladec PETG @MMU1] -inherits = *PETMMU1* -filament_vendor = Plasty Mladec -filament_cost = 27.82 -filament_density = 1.27 -filament_spool_weight = 230 +[filament:Filament PM PETG @MMU1] +inherits = Filament PM PETG; *PETMMU1* +renamed_from = "Plasty Mladec PETG @MMU1" [filament:Verbatim PETG @MMU1] -inherits = *PETMMU1* -filament_vendor = Verbatim -filament_cost = 27.90 -filament_density = 1.27 -filament_spool_weight = 235 +inherits = Verbatim PETG; *PETMMU1* [filament:Fiberlogy PETG @MMU1] -inherits = *PETMMU1* -filament_vendor = Fiberlogy -filament_cost = 21.50 -filament_density = 1.27 +inherits = Fiberlogy PETG; *PETMMU1* [filament:Prusa PETG @MMU1] -inherits = *PETMMU1* +inherits = Prusa PETG; *PETMMU1* renamed_from = "Prusa PET MMU1"; "Prusa PETG MMU1" -filament_vendor = Made for Prusa -filament_cost = 27.82 -filament_density = 1.27 -filament_spool_weight = 230 [filament:Prusament PETG @MMU1] -inherits = *PETMMU1* -filament_vendor = Prusa Polymers -first_layer_temperature = 240 -temperature = 250 -filament_cost = 36.29 -filament_density = 1.27 -filament_spool_weight = 201 -filament_type = PETG +inherits = Prusament PETG; *PETMMU1* + +[filament:Extrudr PETG @MMU1] +inherits = Extrudr PETG; *PETMMU1* +filament_vendor = Extrudr [filament:Taulman T-Glase @MMU1] -inherits = *PETMMU1* -filament_vendor = Taulman -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 +inherits = Taulman T-Glase; *PETMMU1* start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" [filament:Fiberthree F3 PA Pure Pro @MMU1] -inherits = *common* -filament_vendor = Fiberthree -filament_cost = 200.84 -filament_density = 1.2 -bed_temperature = 70 -first_layer_bed_temperature = 75 -first_layer_temperature = 270 -temperature = 270 -bridge_fan_speed = 30 -cooling = 1 -disable_fan_first_layers = 3 -fan_always_on = 1 -fan_below_layer_time = 20 -min_print_speed = 15 -slowdown_below_layer_time = 10 -filament_colour = #DEE0E6 +inherits = Fiberthree F3 PA Pure Pro filament_max_volumetric_speed = 4 -filament_soluble = 0 -filament_type = NYLON -max_fan_speed = 20 -min_fan_speed = 20 -start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.6}0.12{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.8}0.06{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/}0.2{elsif nozzle_diameter[0]==0.8}0.01{elsif nozzle_diameter[0]==0.6}0.04{else}0.05{endif} ; Filament gcode LA 1.5\n{if printer_notes=~/.*PRINTER_MODEL_MINI.*/};{elsif printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}M900 K200{elsif nozzle_diameter[0]==0.6}M900 K18{elsif nozzle_diameter[0]==0.8};{else}M900 K30{endif} ; Filament gcode LA 1.0" +filament_retract_length = nil +filament_retract_speed = nil +filament_retract_lift = nil +filament_retract_before_travel = nil +filament_wipe = nil compatible_printers_condition = printer_model=="MK2SMM" [filament:Fiberthree F3 PA-CF Pro @MMU1] -inherits = *common* -filament_vendor = Fiberthree -filament_cost = 208.1 -filament_density = 1.25 -bed_temperature = 70 -first_layer_bed_temperature = 75 -first_layer_temperature = 275 -temperature = 275 -bridge_fan_speed = 30 -cooling = 1 -disable_fan_first_layers = 3 -fan_always_on = 0 -fan_below_layer_time = 20 -min_print_speed = 15 -slowdown_below_layer_time = 10 -filament_colour = #DEE0E6 +inherits = Fiberthree F3 PA-CF Pro filament_max_volumetric_speed = 4 -filament_soluble = 0 -filament_type = NYLON -max_fan_speed = 0 -min_fan_speed = 0 -start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.6}0.12{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.8}0.06{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/}0.2{elsif nozzle_diameter[0]==0.8}0.01{elsif nozzle_diameter[0]==0.6}0.04{else}0.05{endif} ; Filament gcode LA 1.5\n{if printer_notes=~/.*PRINTER_MODEL_MINI.*/};{elsif printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}M900 K200{elsif nozzle_diameter[0]==0.6}M900 K18{elsif nozzle_diameter[0]==0.8};{else}M900 K30{endif} ; Filament gcode LA 1.0" +filament_retract_length = nil +filament_retract_speed = nil +filament_retract_lift = nil +filament_retract_before_travel = nil +filament_wipe = nil compatible_printers_condition = nozzle_diameter[0]>=0.4 and printer_model=="MK2SMM" [filament:Fiberthree F3 PA-GF Pro @MMU1] @@ -3166,108 +3045,47 @@ compatible_printers_condition = printer_model=="MK2SMM" [filament:Generic PETG @MINI] inherits = Generic PETG; *PETMINI* renamed_from = "Generic PET MINI"; "Generic PETG MINI" -filament_vendor = Generic -filament_cost = 27.82 -filament_density = 1.27 compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 [filament:Devil Design PETG @MINI] -inherits = Generic PETG; *PETMINI* -filament_vendor = Devil Design -filament_cost = 20.99 -filament_density = 1.23 -filament_spool_weight = 250 -first_layer_temperature = 230 -first_layer_bed_temperature = 85 -temperature = 230 -bed_temperature = 90 +inherits = Devil Design PETG; *PETMINI* compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]!=0.6 -[filament:Plasty Mladec PETG @MINI] -inherits = Generic PETG; *PETMINI* -filament_vendor = Plasty Mladec -filament_cost = 27.82 -filament_density = 1.27 -filament_spool_weight = 230 +[filament:Filament PM PETG @MINI] +inherits = Filament PM PETG; *PETMINI* +renamed_from = "Plasty Mladec PETG @MINI" compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]!=0.6 [filament:Verbatim PETG @MINI] -inherits = Generic PETG; *PETMINI* -filament_vendor = Verbatim -filament_cost = 27.90 -filament_density = 1.27 -filament_spool_weight = 235 +inherits = Verbatim PETG; *PETMINI* compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]!=0.6 [filament:Fiberlogy PETG @MINI] -inherits = Generic PETG; *PETMINI* -filament_vendor = Fiberlogy -filament_cost = 21.50 -filament_density = 1.27 +inherits = Fiberlogy PETG; *PETMINI* compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]!=0.6 [filament:Generic ABS @MINI] inherits = Generic ABS; *ABSMINI* -filament_vendor = Generic -filament_cost = 27.82 -filament_density = 1.08 -fan_always_on = 0 -cooling = 1 -min_fan_speed = 15 -max_fan_speed = 15 -disable_fan_first_layers = 4 -fan_below_layer_time = 30 -bridge_fan_speed = 25 compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]!=0.8 [filament:Fiberthree F3 PA Pure Pro @MINI] -inherits = *common* -filament_vendor = Fiberthree -filament_cost = 200.84 -filament_density = 1.2 -bed_temperature = 70 -first_layer_bed_temperature = 75 -first_layer_temperature = 270 -temperature = 270 -bridge_fan_speed = 30 -cooling = 1 -disable_fan_first_layers = 3 -fan_always_on = 1 -fan_below_layer_time = 20 -min_print_speed = 15 -slowdown_below_layer_time = 10 -filament_colour = #DEE0E6 +inherits = Fiberthree F3 PA Pure Pro filament_max_volumetric_speed = 4 -filament_soluble = 0 -filament_type = NYLON -max_fan_speed = 20 -min_fan_speed = 20 -start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.6}0.12{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.8}0.06{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/}0.2{elsif nozzle_diameter[0]==0.8}0.01{elsif nozzle_diameter[0]==0.6}0.04{else}0.05{endif} ; Filament gcode LA 1.5\n{if printer_notes=~/.*PRINTER_MODEL_MINI.*/};{elsif printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}M900 K200{elsif nozzle_diameter[0]==0.6}M900 K18{elsif nozzle_diameter[0]==0.8};{else}M900 K30{endif} ; Filament gcode LA 1.0" +filament_retract_length = nil +filament_retract_speed = nil +filament_retract_lift = nil +filament_retract_before_travel = nil +filament_wipe = nil compatible_printers_condition = printer_model=="MINI" [filament:Fiberthree F3 PA-CF Pro @MINI] -inherits = *common* -filament_vendor = Fiberthree -filament_cost = 208.1 -filament_density = 1.25 -bed_temperature = 70 -first_layer_bed_temperature = 75 -first_layer_temperature = 275 -temperature = 275 -bridge_fan_speed = 30 -cooling = 1 -disable_fan_first_layers = 3 -fan_always_on = 0 -fan_below_layer_time = 20 -min_print_speed = 15 -slowdown_below_layer_time = 10 -filament_colour = #DEE0E6 +inherits = Fiberthree F3 PA-CF Pro filament_max_volumetric_speed = 4 -filament_soluble = 0 -filament_type = NYLON -max_fan_speed = 0 -min_fan_speed = 0 -start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.6}0.12{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.8}0.06{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/}0.2{elsif nozzle_diameter[0]==0.8}0.01{elsif nozzle_diameter[0]==0.6}0.04{else}0.05{endif} ; Filament gcode LA 1.5\n{if printer_notes=~/.*PRINTER_MODEL_MINI.*/};{elsif printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}M900 K200{elsif nozzle_diameter[0]==0.6}M900 K18{elsif nozzle_diameter[0]==0.8};{else}M900 K30{endif} ; Filament gcode LA 1.0" +filament_retract_length = nil +filament_retract_speed = nil +filament_retract_lift = nil +filament_retract_before_travel = nil +filament_wipe = nil compatible_printers_condition = nozzle_diameter[0]>=0.4 and printer_model=="MINI" [filament:Fiberthree F3 PA-GF Pro @MINI] @@ -3280,14 +3098,8 @@ max_fan_speed = 15 min_fan_speed = 15 [filament:Kimya ABS Carbon @MINI] -inherits = *ABSMINI* -filament_vendor = Kimya -filament_cost = 140.4 -filament_density = 1.032 -filament_colour = #804040 +inherits = Kimya ABS Carbon; *ABSMINI* filament_max_volumetric_speed = 6 -first_layer_temperature = 260 -temperature = 260 compatible_printers_condition = nozzle_diameter[0]>=0.4 and printer_model=="MINI" [filament:Kimya ABS Kevlar @MINI] @@ -3296,146 +3108,57 @@ filament_vendor = Kimya filament_density = 1.037 [filament:Esun ABS @MINI] -inherits = Generic ABS; *ABSMINI* -filament_vendor = Esun -filament_cost = 27.82 -filament_density = 1.01 -filament_spool_weight = 265 -fan_always_on = 0 -cooling = 1 -min_fan_speed = 15 -max_fan_speed = 15 -disable_fan_first_layers = 4 -fan_below_layer_time = 30 -bridge_fan_speed = 25 +inherits = Esun ABS; *ABSMINI* [filament:Hatchbox ABS @MINI] -inherits = Generic ABS; *ABSMINI* -filament_vendor = Hatchbox -filament_cost = 27.82 -filament_density = 1.08 -filament_spool_weight = 245 -fan_always_on = 0 -cooling = 1 -min_fan_speed = 15 -max_fan_speed = 15 -disable_fan_first_layers = 4 -fan_below_layer_time = 30 -bridge_fan_speed = 25 +inherits = Hatchbox ABS; *ABSMINI* -[filament:Plasty Mladec ABS @MINI] -inherits = Generic ABS; *ABSMINI* -filament_vendor = Plasty Mladec -filament_cost = 27.82 -filament_density = 1.08 -filament_spool_weight = 230 -fan_always_on = 0 -cooling = 1 -min_fan_speed = 15 -max_fan_speed = 15 -disable_fan_first_layers = 4 -fan_below_layer_time = 30 -bridge_fan_speed = 25 +[filament:Filament PM ABS @MINI] +inherits = Filament PM ABS; *ABSMINI* +renamed_from = "Plasty Mladec ABS @MINI" [filament:Verbatim ABS @MINI] -inherits = Generic ABS; *ABSMINI* -filament_vendor = Verbatim -filament_cost = 25.87 -filament_density = 1.05 -filament_spool_weight = 235 -fan_always_on = 0 -cooling = 1 -min_fan_speed = 15 -max_fan_speed = 15 -disable_fan_first_layers = 4 -fan_below_layer_time = 30 -bridge_fan_speed = 25 +inherits = Verbatim ABS; *ABSMINI* [filament:Prusament PETG @MINI] inherits = Prusament PETG; *PETMINI* -filament_vendor = Prusa Polymers -first_layer_temperature = 240 -temperature = 250 -filament_density = 1.27 -filament_spool_weight = 201 -filament_cost = 36.29 compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 +[filament:Extrudr PETG @MINI] +inherits = Extrudr PETG; *PETMINI* +filament_vendor = Extrudr + [filament:Kimya PETG Carbon @MINI] -inherits = *PETMINI* -filament_vendor = Kimya -extrusion_multiplier = 1.05 -filament_cost = 150.02 -filament_density = 1.317 -filament_colour = #804040 +inherits = Kimya PETG Carbon; *PETMINI* filament_max_volumetric_speed = 6 -first_layer_bed_temperature = 85 -first_layer_temperature = 240 -temperature = 240 filament_retract_length = nil filament_retract_lift = 0.3 compatible_printers_condition = nozzle_diameter[0]>=0.4 and printer_model=="MINI" [filament:Prusament PETG @0.6 nozzle MINI] inherits = Prusament PETG; *PETMINI06* -first_layer_temperature = 240 -temperature = 250 -filament_density = 1.27 -filament_spool_weight = 201 -filament_cost = 36.29 [filament:Generic PETG @0.6 nozzle MINI] inherits = Generic PETG; *PETMINI06* renamed_from = "Generic PET 0.6 nozzle MINI"; "Generic PETG 0.6 nozzle MINI" -filament_cost = 27.82 -filament_density = 1.27 [filament:Devil Design PETG @0.6 nozzle MINI] -inherits = Generic PETG; *PETMINI06* -filament_vendor = Devil Design -first_layer_temperature = 230 -first_layer_bed_temperature = 85 -temperature = 230 -bed_temperature = 90 -filament_cost = 20.99 -filament_density = 1.23 -filament_spool_weight = 250 +inherits = Devil Design PETG; *PETMINI06* -[filament:Plasty Mladec PETG @0.6 nozzle MINI] -inherits = Generic PETG; *PETMINI06* -filament_vendor = Plasty Mladec -filament_cost = 27.82 -filament_density = 1.27 -filament_spool_weight = 230 +[filament:Filament PM PETG @0.6 nozzle MINI] +inherits = Filament PM PETG; *PETMINI06* +renamed_from = "Plasty Mladec PETG @0.6 nozzle MINI" [filament:Verbatim PETG @0.6 nozzle MINI] -inherits = Generic PETG; *PETMINI06* -filament_vendor = Verbatim -filament_spool_weight = 235 +inherits = Verbatim PETG; *PETMINI06* [filament:Fiberlogy PETG @0.6 nozzle MINI] -inherits = Generic PETG; *PETMINI06* -filament_vendor = Fiberlogy +inherits = Fiberlogy PETG; *PETMINI06* [filament:Prusament ASA @MINI] inherits = Prusament ASA; *ABSMINI* -first_layer_temperature = 260 first_layer_bed_temperature = 100 -temperature = 260 bed_temperature = 100 -fan_always_on = 1 -cooling = 1 -min_fan_speed = 20 -max_fan_speed = 20 -bridge_fan_speed = 30 -min_print_speed = 15 -slowdown_below_layer_time = 15 -disable_fan_first_layers = 4 -filament_type = ASA -filament_colour = #FFF2EC -filament_cost = 42.69 -filament_density = 1.07 -filament_spool_weight = 201 compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]!=0.8 [filament:Fillamentum Flexfill 98A @MINI] @@ -3541,89 +3264,43 @@ inherits = Fillamentum CPE; *PETMINI* first_layer_temperature = 265 first_layer_bed_temperature = 90 temperature = 265 -filament_type = CPE -filament_cost = 56.45 -filament_density = 1.25 -filament_spool_weight = 230 disable_fan_first_layers = 3 full_fan_speed_layer = 5 [filament:ColorFabb nGen @MINI] inherits = ColorFabb nGen; *PETMINI* -filament_cost = 52.46 -filament_density = 1.2 -filament_spool_weight = 236 [filament:E3D PC-ABS @MINI] inherits = E3D PC-ABS; *ABSMINI* -filament_density = 1.05 -filament_cost = 28.80 +filament_retract_length = nil +filament_retract_before_travel = nil +filament_wipe = nil [filament:Fillamentum ABS @MINI] inherits = Fillamentum ABS; *ABSMINI* -filament_cost = 32.4 -filament_density = 1.04 -filament_spool_weight = 230 -fan_always_on = 0 -cooling = 1 -min_fan_speed = 15 -max_fan_speed = 15 -disable_fan_first_layers = 4 -fan_below_layer_time = 30 -bridge_fan_speed = 25 [filament:Fillamentum ASA @MINI] inherits = Fillamentum ASA; *ABSMINI* -first_layer_temperature = 255 first_layer_bed_temperature = 100 -temperature = 255 bed_temperature = 100 -fan_always_on = 1 -cooling = 1 -min_fan_speed = 20 -max_fan_speed = 20 -min_print_speed = 15 -slowdown_below_layer_time = 15 -disable_fan_first_layers = 4 -filament_type = ASA -filament_colour = #FFF2EC -filament_cost = 38.7 -filament_density = 1.07 -filament_spool_weight = 230 [filament:Polymaker PC-Max @MINI] inherits = Polymaker PC-Max; *ABSMINI* -filament_type = PC filament_max_volumetric_speed = 7 bed_temperature = 100 -filament_colour = #FFF2EC first_layer_bed_temperature = 100 first_layer_temperature = 270 temperature = 270 -bridge_fan_speed = 0 -filament_cost = 77.3 -filament_density = 1.20 +filament_retract_length = nil +filament_retract_before_travel = nil +filament_wipe = nil [filament:Prusament PC Blend @MINI] -inherits = *ABSMINI* -filament_vendor = Prusa Polymers -filament_cost = 60.49 -filament_density = 1.22 -filament_spool_weight = 201 -fan_always_on = 0 +inherits = Prusament PC Blend; *ABSMINI* first_layer_temperature = 275 first_layer_bed_temperature = 100 temperature = 275 bed_temperature = 100 -cooling = 1 -min_fan_speed = 20 -max_fan_speed = 20 -bridge_fan_speed = 30 -min_print_speed = 15 -disable_fan_first_layers = 4 -fan_below_layer_time = 30 -filament_type = PC -filament_colour = #DEE0E6 filament_max_volumetric_speed = 7 filament_retract_length = nil filament_retract_speed = nil @@ -3634,117 +3311,43 @@ filament_wipe = nil compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]!=0.8 [filament:Prusa ABS @MINI] -inherits = *ABSMINI* -filament_vendor = Made for Prusa -filament_cost = 27.82 -filament_density = 1.08 -filament_spool_weight = 230 -fan_always_on = 0 -cooling = 1 -min_fan_speed = 15 -max_fan_speed = 15 -disable_fan_first_layers = 4 -fan_below_layer_time = 30 -bridge_fan_speed = 25 +inherits = Prusa ABS; *ABSMINI* compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]!=0.8 [filament:Generic HIPS @MINI] -inherits = *ABSMINI* -filament_vendor = Generic -filament_cost = 27.3 -filament_density = 1.04 -bridge_fan_speed = 50 -cooling = 1 -extrusion_multiplier = 1 -fan_always_on = 1 -fan_below_layer_time = 10 -filament_colour = #FFFFD7 -filament_soluble = 1 -filament_type = HIPS -first_layer_temperature = 230 -max_fan_speed = 20 -min_fan_speed = 20 -temperature = 230 +inherits = Generic HIPS; *ABSMINI* [filament:ColorFabb HT @MINI] -inherits = *PETMINI* -filament_vendor = ColorFabb +inherits = ColorFabb HT; *PETMINI* bed_temperature = 100 -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 -filament_spool_weight = 236 first_layer_bed_temperature = 100 -first_layer_temperature = 270 -max_fan_speed = 20 -min_fan_speed = 10 -temperature = 270 +min_fan_speed = 15 [filament:ColorFabb XT @MINI] -inherits = *PETMINI* -filament_vendor = ColorFabb -filament_type = PETG -filament_cost = 62.90 -filament_density = 1.27 -filament_spool_weight = 236 +inherits = ColorFabb XT; *PETMINI* first_layer_bed_temperature = 90 -first_layer_temperature = 260 -temperature = 270 [filament:ColorFabb XT-CF20 @MINI] -inherits = *PETMINI* -filament_vendor = ColorFabb +inherits = ColorFabb XT-CF20; *PETMINI* compatible_printers_condition = nozzle_diameter[0]>=0.4 and printer_model=="MINI" -extrusion_multiplier = 1.05 -filament_cost = 80.65 -filament_density = 1.35 -filament_spool_weight = 236 -filament_colour = #804040 -filament_max_volumetric_speed = 2 first_layer_bed_temperature = 90 first_layer_temperature = 260 temperature = 260 [filament:Taulman T-Glase @MINI] -inherits = *PETMINI* -filament_vendor = Taulman -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 +inherits = Taulman T-Glase; *PETMINI* [filament:E3D Edge @MINI] -inherits = *PETMINI* -filament_vendor = E3D -filament_cost = 56.9 -filament_density = 1.26 -filament_type = EDGE +inherits = E3D Edge; *PETMINI* [filament:Prusa PETG @MINI] -inherits = *PETMINI* +inherits = Prusa PETG; *PETMINI* renamed_from = "Prusa PET MINI"; "Prusa PETG MINI" -filament_vendor = Made for Prusa -filament_cost = 27.82 -filament_density = 1.27 -filament_spool_weight = 230 compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 [filament:Prusa PETG @0.6 nozzle MINI] -inherits = *PETMINI06* +inherits = Prusa PETG; *PETMINI06* renamed_from = "Prusa PET 0.6 nozzle MINI"; "Prusa PETG 0.6 nozzle MINI" -filament_vendor = Made for Prusa -filament_cost = 27.82 -filament_density = 1.27 -filament_spool_weight = 230 ## Filaments 0.8 nozzle @@ -5321,7 +4924,7 @@ retract_speed = 35 serial_port = serial_speed = 250000 single_extruder_multi_material = 0 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 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 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 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 Z0.2 F720\nG1 Y-2 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\nG92 E0 toolchange_gcode = use_firmware_retraction = 0 use_relative_e_distances = 1 @@ -5356,19 +4959,19 @@ printer_model = MK2SMM [printer:*mm-single*] 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 max_layer_z < max_print_height}G1 Z{z_offset+min(max_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\nM900 K0 ; reset LA\nM84 ; disable motors\n\n +end_gcode = G1 E-4 F2100\nG91\nG1 Z1 F7200\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 F5000\nG1 E-50 F5400\nG1 E-15 F3000\nG1 E-12 F2000\nG1 F1600\nG1 X0 Y1 E3\nG1 X50 Y1 E-5\nG1 F2000\nG1 X0 Y1 E5\nG1 X50 Y1 E-5\nG1 F2400\nG1 X0 Y1 E5\nG1 X50 Y1 E-5\nG1 F2400\nG1 X0 Y1 E5\nG1 X50 Y1 E-3\nG4 S0\nM107 ; turn off fan\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_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\nM900 K0 ; reset LA\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 = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 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]\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 = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 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]\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0\nM203 E100\nM92 E140\nG1 Z0.25 F7200\nG1 X50 E80 F1000\nG1 X160 E20 F1000\nG1 Z0.2 F7200\nG1 X220 E13 F1000\nG1 X240 E0 F1000\nG92 E0 default_print_profile = 0.15mm OPTIMAL [printer:*mm-multi*] inherits = *multimaterial* high_current_on_filament_swap = 1 -end_gcode = {if not has_wipe_tower}\n; Pull the filament into the cooling tubes.\nG1 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\n{endif}\nM107 ; turn off fan\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_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\nM900 K0 ; reset LA\nM84 ; disable motors +end_gcode = {if not has_wipe_tower}\n; Pull the filament into the cooling tubes.\nG1 E-4 F2100\nG91\nG1 Z1 F7200\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 F5000\nG1 E-50 F5400\nG1 E-15 F3000\nG1 E-12 F2000\nG1 F1600\nG1 X0 Y1 E3\nG1 X50 Y1 E-5\nG1 F2000\nG1 X0 Y1 E5\nG1 X50 Y1 E-5\nG1 F2400\nG1 X0 Y1 E5\nG1 X50 Y1 E-5\nG1 F2400\nG1 X0 Y1 E5\nG1 X50 Y1 E-3\nG4 S0\n{endif}\nM107 ; turn off fan\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_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\nM900 K0 ; reset LA\nM84 ; disable motors 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 = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 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\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 = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 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\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0\nM203 E100 ; set max feedrate\nM92 E140 ; E-steps per filament milimeter\n{if not has_single_extruder_multi_material_priming}\nG1 Z0.25 F7200\nG1 X50 E80 F1000\nG1 X160 E20 F1000\nG1 Z0.2 F7200\nG1 X220 E13 F1000\nG1 X240 E0 F1000\n{endif}\nG92 E0 default_print_profile = 0.15mm OPTIMAL # XXXXXXXXXXXXXXXXX @@ -5438,21 +5041,21 @@ inherits = Original Prusa i3 MK2S printer_model = MK2.5 remaining_times = 1 machine_max_jerk_e = 4.5 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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 = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\nG92 E0 [printer:Original Prusa i3 MK2.5 0.25 nozzle] inherits = Original Prusa i3 MK2S 0.25 nozzle printer_model = MK2.5 remaining_times = 1 machine_max_jerk_e = 4.5 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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 = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\nG92 E0 [printer:Original Prusa i3 MK2.5 0.6 nozzle] inherits = Original Prusa i3 MK2S 0.6 nozzle printer_model = MK2.5 remaining_times = 1 machine_max_jerk_e = 4.5 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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 = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\nG92 E0 [printer:Original Prusa i3 MK2.5 0.8 nozzle] inherits = Original Prusa i3 MK2S 0.6 nozzle @@ -5464,39 +5067,20 @@ min_layer_height = 0.2 retract_length = 1 remaining_times = 1 machine_max_jerk_e = 4.5 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM115 U3.9.3 ; 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 = M862.3 P \"[printer_model]\" ; printer model check\nM115 U3.9.3 ; 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 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\nG92 E0 default_print_profile = 0.40mm QUALITY @0.8 nozzle default_filament_profile = Prusament PLA @0.8 nozzle [printer:Original Prusa i3 MK2.5 MMU2 Single] -inherits = Original Prusa i3 MK2.5; *mm2* +inherits = *25mm2* printer_model = MK2.5MMU2 single_extruder_multi_material = 0 max_print_height = 200 -remaining_times = 1 -silent_mode = 0 -retract_lift_below = 199 -machine_max_acceleration_e = 10000 -machine_max_acceleration_extruding = 2000 -machine_max_acceleration_retracting = 1500 -machine_max_acceleration_x = 9000 -machine_max_acceleration_y = 9000 -machine_max_acceleration_z = 500 -machine_max_feedrate_e = 120 -machine_max_feedrate_x = 500 -machine_max_feedrate_y = 500 -machine_max_feedrate_z = 12 -machine_max_jerk_e = 4.5 -machine_max_jerk_x = 10 -machine_max_jerk_y = 10 -machine_max_jerk_z = 0.2 -machine_min_extruding_rate = 0 -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 = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\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 = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+10, max_print_height)}{endif} F720 ; Move print head up\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\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM702 C\nG4 ; wait\nM104 S0 ; turn off temperature\nM900 K0 ; reset LA\nM84 ; disable motors +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; load to nozzle\nTc\n; purge line\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.20 F1000\nG1 X5 E4 F1000\nG92 E0\n +end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+10, max_print_height)}{endif} F720 ; Move print head up\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15 F5800\nG1 E-20 F5500\nG1 E10 F3000\nG1 E-10 F3100\nG1 E10 F3150\nG1 E-10 F3250\nG1 E10 F3300\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM702 C\nG4 ; wait\nM104 S0 ; turn off temperature\nM900 K0 ; reset LA\nM84 ; disable motors [printer:Original Prusa i3 MK2.5 MMU2 Single 0.8 nozzle] inherits = Original Prusa i3 MK2.5S MMU2S Single 0.8 nozzle @@ -5511,87 +5095,43 @@ inherits = Original Prusa i3 MK2.5S MMU2S Single 0.25 nozzle printer_model = MK2.5MMU2 [printer:Original Prusa i3 MK2.5 MMU2] -inherits = Original Prusa i3 MK2.5; *mm2* +inherits = *25mm2* printer_model = MK2.5MMU2 max_print_height = 200 -remaining_times = 1 -silent_mode = 0 -retract_lift_below = 199 -machine_max_acceleration_e = 10000 -machine_max_acceleration_extruding = 2000 -machine_max_acceleration_retracting = 1500 -machine_max_acceleration_x = 9000 -machine_max_acceleration_y = 9000 -machine_max_acceleration_z = 500 -machine_max_feedrate_e = 120 -machine_max_feedrate_x = 500 -machine_max_feedrate_y = 500 -machine_max_feedrate_z = 12 -machine_max_jerk_e = 4.5 -machine_max_jerk_x = 10 -machine_max_jerk_y = 10 -machine_max_jerk_z = 0.2 -machine_min_extruding_rate = 0 -machine_min_travel_rate = 0 default_print_profile = 0.15mm OPTIMAL @MK2.5 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 single_extruder_multi_material = 1 -# The 5x nozzle diameter defines the number of extruders. Other extruder parameters -# (for example the retract values) are duplicaed from the first value, so they do not need -# 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 = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\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 = ; Lift print head a bit\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} ; Move print head up\n{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\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nG1 X0 Y210 F3000 ; home X axis\nM900 K0 ; reset LA\nM84 ; disable motors\n +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\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 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55 E32 F1073\nG1 X5 E32 F1800\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n{endif}\nG92 E0\n +end_gcode = ; Lift print head a bit\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} ; Move print head up\n{if has_wipe_tower}\nG1 E-15 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15 F5800\nG1 E-20 F5500\nG1 E10 F3000\nG1 E-10 F3100\nG1 E10 F3150\nG1 E-10 F3250\nG1 E10 F3300\n{endif}\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nG1 X0 Y210 F3000 ; home X axis\nM900 K0 ; reset LA\nM84 ; disable motors\n [printer:Original Prusa i3 MK2.5S] inherits = Original Prusa i3 MK2.5 printer_model = MK2.5S -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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 = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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 = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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.8 nozzle] inherits = Original Prusa i3 MK2.5 0.8 nozzle printer_model = MK2.5S [printer:Original Prusa i3 MK2.5S MMU2S Single] -inherits = Original Prusa i3 MK2.5; *mm2s* +inherits = *25mm2s* printer_model = MK2.5SMMU2S single_extruder_multi_material = 0 max_print_height = 200 -remaining_times = 1 -silent_mode = 0 -retract_lift_below = 199 -machine_max_acceleration_e = 10000 -machine_max_acceleration_extruding = 2000 -machine_max_acceleration_retracting = 1500 -machine_max_acceleration_x = 9000 -machine_max_acceleration_y = 9000 -machine_max_acceleration_z = 500 -machine_max_feedrate_e = 120 -machine_max_feedrate_x = 500 -machine_max_feedrate_y = 500 -machine_max_feedrate_z = 12 -machine_max_jerk_e = 4.5 -machine_max_jerk_x = 10 -machine_max_jerk_y = 10 -machine_max_jerk_z = 0.2 -machine_min_extruding_rate = 0 -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 = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\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 = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+10, max_print_height)}{endif} F720 ; Move print head up\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\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM702 C\nG4 ; wait\nM104 S0 ; turn off temperature\nM900 K0 ; reset LA\nM84 ; disable motors +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n +end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+10, max_print_height)}{endif} F720 ; Move print head up\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15 F5800\nG1 E-20 F5500\nG1 E10 F3000\nG1 E-10 F3100\nG1 E10 F3150\nG1 E-10 F3250\nG1 E10 F3300\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM702 C\nG4 ; wait\nM104 S0 ; turn off temperature\nM900 K0 ; reset LA\nM84 ; disable motors [printer:Original Prusa i3 MK2.5S MMU2S Single 0.8 nozzle] inherits = Original Prusa i3 MK2.5S MMU2S Single @@ -5601,7 +5141,7 @@ min_layer_height = 0.2 nozzle_diameter = 0.8 printer_variant = 0.8 retract_length = 1 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM115 U3.9.3 ; 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\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 = M862.3 P \"[printer_model]\" ; printer model check\nM115 U3.9.3 ; 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\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n default_print_profile = 0.40mm QUALITY @0.8 nozzle default_filament_profile = Prusament PLA @0.8 nozzle @@ -5623,41 +5163,19 @@ nozzle_diameter = 0.25 printer_variant = 0.25 retract_lift = 0.15 default_print_profile = 0.10mm DETAIL 0.25 nozzle -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\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 = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F1400\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n [printer:Original Prusa i3 MK2.5S MMU2S] -inherits = Original Prusa i3 MK2.5; *mm2s* +inherits = *25mm2s* printer_model = MK2.5SMMU2S max_print_height = 200 -remaining_times = 1 -silent_mode = 0 -retract_lift_below = 199 -machine_max_acceleration_e = 10000 -machine_max_acceleration_extruding = 2000 -machine_max_acceleration_retracting = 1500 -machine_max_acceleration_x = 9000 -machine_max_acceleration_y = 9000 -machine_max_acceleration_z = 500 -machine_max_feedrate_e = 120 -machine_max_feedrate_x = 500 -machine_max_feedrate_y = 500 -machine_max_feedrate_z = 12 -machine_max_jerk_e = 4.5 -machine_max_jerk_x = 10 -machine_max_jerk_y = 10 -machine_max_jerk_z = 0.2 -machine_min_extruding_rate = 0 -machine_min_travel_rate = 0 default_print_profile = 0.15mm OPTIMAL @MK2.5 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 single_extruder_multi_material = 1 -# The 5x nozzle diameter defines the number of extruders. Other extruder parameters -# (for example the retract values) are duplicaed from the first value, so they do not need -# 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 = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\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 = ; Lift print head a bit\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} ; Move print head up\n{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\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nG1 X0 Y210 F3000 ; home X axis\nM900 K0 ; reset LA\nM84 ; disable motors\n +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\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 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55 E29 F1073\nG1 X5 E29 F1800\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n{endif}\nG92 E0\n +end_gcode = ; Lift print head a bit\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} ; Move print head up\n{if has_wipe_tower}\nG1 E-15 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15 F5800\nG1 E-20 F5500\nG1 E10 F3000\nG1 E-10 F3100\nG1 E10 F3150\nG1 E-10 F3250\nG1 E10 F3300\n{endif}\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nG1 X0 Y210 F3000 ; home X axis\nM900 K0 ; reset LA\nM84 ; disable motors\n [printer:Original Prusa i3 MK2.5S MMU2S 0.6 nozzle] inherits = Original Prusa i3 MK2.5S MMU2S @@ -5675,7 +5193,7 @@ min_layer_height = 0.15 printer_variant = 0.6 default_print_profile = 0.20mm NORMAL @0.6 nozzle -## For later use. 0.8mm nozzle profiles are only available for MMU2 Single mode at the moment. +## 0.8mm nozzle profiles are only available for MMU2 Single mode at the moment. ## [printer:Original Prusa i3 MK2.5S MMU2S 0.8 nozzle] ## inherits = Original Prusa i3 MK2.5S MMU2S @@ -5725,7 +5243,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 = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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}\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\nG92 E0\nM221 S{if layer_height<0.075}100{else}95{endif}\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} printer_model = MK3 default_print_profile = 0.15mm QUALITY @MK3 @@ -5736,7 +5254,7 @@ max_layer_height = 0.15 min_layer_height = 0.05 printer_variant = 0.25 retract_lift = 0.15 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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}\n\n; Don't change E value below. Excessive value can damage the printer.\n{if print_settings_id=~/.*@0.25 nozzle MK3.*/}M907 E430 ; set extruder motor current{endif} +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E8 F700 ; intro line\nG1 X100 E12.5 F700 ; intro line\nG92 E0\nM221 S{if layer_height<0.075}100{else}95{endif}\n\n; Don't change E value below. Excessive value can damage the printer.\n{if print_settings_id=~/.*@0.25 nozzle MK3.*/}M907 E430 ; set extruder motor current{endif} default_print_profile = 0.10mm DETAIL @0.25 nozzle MK3 [printer:Original Prusa i3 MK3 0.6 nozzle] @@ -5745,7 +5263,7 @@ nozzle_diameter = 0.6 max_layer_height = 0.40 min_layer_height = 0.15 printer_variant = 0.6 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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 = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\nG92 E0\nM221 S{if layer_height<0.075}100{else}95{endif} default_print_profile = 0.30mm QUALITY @0.6 nozzle MK3 [printer:Original Prusa i3 MK3 0.8 nozzle] @@ -5755,7 +5273,7 @@ max_layer_height = 0.6 min_layer_height = 0.2 printer_variant = 0.8 retract_length = 1 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM115 U3.9.3 ; 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 = M862.3 P \"[printer_model]\" ; printer model check\nM115 U3.9.3 ; 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 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\nG92 E0\nM221 S95 default_print_profile = 0.40mm QUALITY @0.8 nozzle default_filament_profile = Prusament PLA @0.8 nozzle @@ -5763,13 +5281,11 @@ default_filament_profile = Prusament PLA @0.8 nozzle inherits = Original Prusa i3 MK3 renamed_from = "Original Prusa i3 MK3S" printer_model = MK3S -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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}\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} [printer:Original Prusa i3 MK3S & MK3S+ 0.25 nozzle] inherits = Original Prusa i3 MK3 0.25 nozzle renamed_from = "Original Prusa i3 MK3S 0.25 nozzle" printer_model = MK3S -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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}\n\n; Don't change E value below. Excessive value can damage the printer.\n{if print_settings_id=~/.*@0.25 nozzle MK3.*/}M907 E430 ; set extruder motor current{endif} [printer:Original Prusa i3 MK3S & MK3S+ 0.6 nozzle] inherits = Original Prusa i3 MK3 0.6 nozzle @@ -5804,12 +5320,32 @@ printer_model = MK3SMMU2S default_print_profile = 0.15mm QUALITY @MK3 default_filament_profile = Prusament PLA @MMU2 +[printer:*25mm2*] +inherits = Original Prusa i3 MK2.5 +single_extruder_multi_material = 1 +cooling_tube_length = 10 +cooling_tube_retraction = 30 +parking_pos_retraction = 85 +retract_length_toolchange = 3 +extra_loading_move = -13 +default_filament_profile = Prusament PLA @MMU2 + +[printer:*25mm2s*] +inherits = Original Prusa i3 MK2.5S +single_extruder_multi_material = 1 +cooling_tube_length = 20 +cooling_tube_retraction = 40 +parking_pos_retraction = 85 +retract_length_toolchange = 3 +extra_loading_move = -25 +default_filament_profile = Prusament PLA @MMU2 + [printer:Original Prusa i3 MK3 MMU2 Single] inherits = *mm2* single_extruder_multi_material = 0 default_filament_profile = Prusament PLA -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\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\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} -end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+10, max_print_height)}{endif} F720 ; Move print head up\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\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM702 C\nG4 ; wait\nM221 S100 ; reset flow\nM900 K0 ; reset LA\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|@0.25 nozzle MK3).*/}M907 E538 ; reset extruder motor current{endif}\nM104 S0 ; turn off temperature\nM84 ; disable motors +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} +end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+10, max_print_height)}{endif} F720 ; Move print head up\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15 F5800\nG1 E-20 F5500\nG1 E10 F3000\nG1 E-10 F3100\nG1 E10 F3150\nG1 E-10 F3250\nG1 E10 F3300\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM702 C\nG4 ; wait\nM221 S100 ; reset flow\nM900 K0 ; reset LA\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|@0.25 nozzle MK3).*/}M907 E538 ; reset extruder motor current{endif}\nM104 S0 ; turn off temperature\nM84 ; disable motors [printer:Original Prusa i3 MK3 MMU2 Single 0.6 nozzle] inherits = Original Prusa i3 MK3 MMU2 Single @@ -5818,7 +5354,7 @@ nozzle_diameter = 0.6 max_layer_height = 0.40 min_layer_height = 0.15 printer_variant = 0.6 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\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 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0 default_print_profile = 0.30mm QUALITY @0.6 nozzle MK3 [printer:Original Prusa i3 MK3 MMU2 Single 0.8 nozzle] @@ -5829,7 +5365,7 @@ max_layer_height = 0.6 min_layer_height = 0.2 printer_variant = 0.8 retract_length = 1 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM115 U3.9.3 ; 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\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 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM115 U3.9.3 ; 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\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0 default_print_profile = 0.40mm QUALITY @0.8 nozzle default_filament_profile = Prusament PLA @0.8 nozzle @@ -5841,27 +5377,24 @@ max_layer_height = 0.15 min_layer_height = 0.05 printer_variant = 0.25 retract_lift = 0.15 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\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\n; Don't change E value below. Excessive value can damage the printer.\n{if print_settings_id=~/.*@0.25 nozzle MK3.*/}M907 E430 ; set extruder motor current{endif} +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 E8 F1000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F1400\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0\n\n; Don't change E value below. Excessive value can damage the printer.\n{if print_settings_id=~/.*@0.25 nozzle MK3.*/}M907 E430 ; set extruder motor current{endif} default_print_profile = 0.10mm DETAIL @0.25 nozzle MK3 [printer:Original Prusa i3 MK3 MMU2] inherits = *mm2* -# The 5x nozzle diameter defines the number of extruders. Other extruder parameters -# (for example the retract values) are duplicaed from the first value, so they do not need -# to be defined explicitely. 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 = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\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\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|SOLUBLE).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} -end_gcode = ; Lift print head a bit\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} ; Move print head up\n{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\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM221 S100 ; reset flow\nM900 K0 ; reset LA\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|SOLUBLE|@0.25 nozzle MK3).*/}M907 E538 ; reset extruder motor current{endif}\nM104 S0 ; turn off temperature\nG1 X0 Y210 F3000 ; home X axis\nM84 ; disable motors\n +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\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 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55 E32 F1073\nG1 X5 E32 F1800\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|SOLUBLE).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} +end_gcode = ; Lift print head a bit\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} ; Move print head up\n{if has_wipe_tower}\nG1 E-15 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15 F5800\nG1 E-20 F5500\nG1 E10 F3000\nG1 E-10 F3100\nG1 E10 F3150\nG1 E-10 F3250\nG1 E10 F3300\n{endif}\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM221 S100 ; reset flow\nM900 K0 ; reset LA\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|SOLUBLE|@0.25 nozzle MK3).*/}M907 E538 ; reset extruder motor current{endif}\nM104 S0 ; turn off temperature\nG1 X0 Y210 F3000 ; home X axis\nM84 ; disable motors\n [printer:Original Prusa i3 MK3S & MK3S+ MMU2S Single] inherits = *mm2s* renamed_from = "Original Prusa i3 MK3S MMU2S Single" single_extruder_multi_material = 0 default_filament_profile = Prusament PLA -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\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\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} -end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+10, max_print_height)}{endif} F720 ; Move print head up\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\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM702 C\nG4 ; wait\nM221 S100 ; reset flow\nM900 K0 ; reset LA\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|@0.25 nozzle MK3).*/}M907 E538 ; reset extruder motor current{endif}\nM104 S0 ; turn off temperature\nM84 ; disable motors +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} +end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+10, max_print_height)}{endif} F720 ; Move print head up\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15 F5800\nG1 E-20 F5500\nG1 E10 F3000\nG1 E-10 F3100\nG1 E10 F3150\nG1 E-10 F3250\nG1 E10 F3300\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM702 C\nG4 ; wait\nM221 S100 ; reset flow\nM900 K0 ; reset LA\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|@0.25 nozzle MK3).*/}M907 E538 ; reset extruder motor current{endif}\nM104 S0 ; turn off temperature\nM84 ; disable motors [printer:Original Prusa i3 MK3S & MK3S+ MMU2S Single 0.6 nozzle] inherits = Original Prusa i3 MK3S & MK3S+ MMU2S Single @@ -5871,7 +5404,7 @@ nozzle_diameter = 0.6 max_layer_height = 0.40 min_layer_height = 0.15 printer_variant = 0.6 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\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 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0 default_print_profile = 0.30mm QUALITY @0.6 nozzle MK3 [printer:Original Prusa i3 MK3S & MK3S+ MMU2S Single 0.8 nozzle] @@ -5882,7 +5415,7 @@ max_layer_height = 0.6 min_layer_height = 0.2 printer_variant = 0.8 retract_length = 1 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM115 U3.9.3 ; 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\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 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM115 U3.9.3 ; 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\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0 default_print_profile = 0.40mm QUALITY @0.8 nozzle default_filament_profile = Prusament PLA @0.8 nozzle @@ -5895,7 +5428,7 @@ max_layer_height = 0.15 min_layer_height = 0.05 printer_variant = 0.25 retract_lift = 0.15 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\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\n; Don't change E value below. Excessive value can damage the printer.\n{if print_settings_id=~/.*@0.25 nozzle MK3.*/}M907 E430 ; set extruder motor current{endif} +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F1400\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0\n\n; Don't change E value below. Excessive value can damage the printer.\n{if print_settings_id=~/.*@0.25 nozzle MK3.*/}M907 E430 ; set extruder motor current{endif} default_print_profile = 0.10mm DETAIL @0.25 nozzle MK3 [printer:Original Prusa i3 MK3S & MK3S+ MMU2S] @@ -5904,8 +5437,8 @@ renamed_from = "Original Prusa i3 MK3S MMU2S" 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 = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\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\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|SOLUBLE).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} -end_gcode = ; Lift print head a bit\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} ; Move print head up\n{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\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM221 S100 ; reset flow\nM900 K0 ; reset LA\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|SOLUBLE|@0.25 nozzle MK3).*/}M907 E538 ; reset extruder motor current{endif}\nM104 S0 ; turn off temperature\nG1 X0 Y210 F3000 ; home X axis\nM84 ; disable motors\n +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\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 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55 E29 F1073\nG1 X5 E29 F1800\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|SOLUBLE).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} +end_gcode = ; Lift print head a bit\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} ; Move print head up\n{if has_wipe_tower}\nG1 E-15 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15 F5800\nG1 E-20 F5500\nG1 E10 F3000\nG1 E-10 F3100\nG1 E10 F3150\nG1 E-10 F3250\nG1 E10 F3300\n{endif}\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM221 S100 ; reset flow\nM900 K0 ; reset LA\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|SOLUBLE|@0.25 nozzle MK3).*/}M907 E538 ; reset extruder motor current{endif}\nM104 S0 ; turn off temperature\nG1 X0 Y210 F3000 ; home X axis\nM84 ; disable motors\n ## 0.6mm nozzle MMU2/S printer profiles @@ -5916,7 +5449,7 @@ 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 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\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 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\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 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55 E29 F1073\nG1 X5 E29 F1800\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0 default_print_profile = 0.30mm QUALITY @0.6 nozzle MK3 [printer:Original Prusa i3 MK3 MMU2 0.6 nozzle] @@ -5925,7 +5458,7 @@ 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 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\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 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; 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\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 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55 E32 F1073\nG1 X5 E32 F1800\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0 default_print_profile = 0.30mm QUALITY @0.6 nozzle MK3 ## 0.8mm nozzle MMU2/S printer profiles @@ -5938,7 +5471,7 @@ default_print_profile = 0.30mm QUALITY @0.6 nozzle MK3 ## max_layer_height = 0.6 ## min_layer_height = 0.2 ## printer_variant = 0.8 -## start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM115 U3.9.3 ; 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\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 +## start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM115 U3.9.3 ; 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\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\nG1 Z0.4 F1000\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\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 ## default_print_profile = 0.40mm QUALITY @0.8 nozzle ## [printer:Original Prusa i3 MK3S & MK3S+ MMU2S 0.8 nozzle] @@ -5963,10 +5496,11 @@ thumbnails = 16x16,220x124 bed_shape = 0x0,180x0,180x180,0x180 default_filament_profile = "Prusament PLA" default_print_profile = 0.15mm QUALITY @MINI -gcode_flavor = marlin +gcode_flavor = marlinfirmware machine_max_acceleration_e = 5000 machine_max_acceleration_extruding = 1250 machine_max_acceleration_retracting = 1250 +machine_max_acceleration_travel = 1250 machine_max_acceleration_x = 1250 machine_max_acceleration_y = 1250 machine_max_acceleration_z = 400 @@ -5996,7 +5530,7 @@ retract_lift_below = 179 retract_layer_change = 1 silent_mode = 0 remaining_times = 1 -start_gcode = G90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S170 ; set extruder temp for bed leveling\nM140 S[first_layer_bed_temperature] ; set bed temp\nM109 R170 ; wait for bed leveling temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nG28 ; home all without mesh bed level\nG29 ; mesh bed leveling \nM104 S[first_layer_temperature] ; set extruder temp\nG92 E0.0\nG1 Y-2.0 X179 F2400\nG1 Z3 F720\nM109 S[first_layer_temperature] ; wait for extruder temp\n\n; intro line\nG1 X170 F1000\nG1 Z0.2 F720\nG1 X110.0 E8.0 F900\nG1 X40.0 E10.0 F700\nG92 E0.0\n\nM221 S95 ; set flow +start_gcode = G90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S170 ; set extruder temp for bed leveling\nM140 S[first_layer_bed_temperature] ; set bed temp\nM109 R170 ; wait for bed leveling temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nG28 ; home all without mesh bed level\nG29 ; mesh bed leveling \nM104 S[first_layer_temperature] ; set extruder temp\nG92 E0\nG1 Y-2 X179 F2400\nG1 Z3 F720\nM109 S[first_layer_temperature] ; wait for extruder temp\n\n; intro line\nG1 X170 F1000\nG1 Z0.2 F720\nG1 X110 E8 F900\nG1 X40 E10 F700\nG92 E0\n\nM221 S95 ; set flow end_gcode = G1 E-1 F2100 ; retract\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+2, max_print_height)}{endif} F720 ; Move print head up\nG1 X178 Y178 F4200 ; park print head\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} F720 ; Move print head further up\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM221 S100 ; reset flow\nM900 K0 ; reset LA\nM84 ; disable motors 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_MINI\n extruder_colour = @@ -6012,7 +5546,7 @@ default_print_profile = 0.10mm DETAIL @0.25 nozzle MINI retract_length = 3 retract_lift = 0.15 retract_before_travel = 1 -start_gcode = G90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S170 ; set extruder temp for bed leveling\nM140 S[first_layer_bed_temperature] ; set bed temp\nM109 R170 ; wait for bed leveling temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nG28 ; home all without mesh bed level\nG29 ; mesh bed leveling \nM104 S[first_layer_temperature] ; set extruder temp\nG92 E0.0\nG1 Y-2.0 X179 F2400\nG1 Z3 F720\nM109 S[first_layer_temperature] ; wait for extruder temp\n\n; intro line\nG1 X170 F1000\nG1 Z0.2 F720\nG1 X110.0 E8.0 F600\nG1 X40.0 E10.0 F400\nG92 E0.0\n\nM221 S95 ; set flow +start_gcode = G90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S170 ; set extruder temp for bed leveling\nM140 S[first_layer_bed_temperature] ; set bed temp\nM109 R170 ; wait for bed leveling temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nG28 ; home all without mesh bed level\nG29 ; mesh bed leveling \nM104 S[first_layer_temperature] ; set extruder temp\nG92 E0\nG1 Y-2 X179 F2400\nG1 Z3 F720\nM109 S[first_layer_temperature] ; wait for extruder temp\n\n; intro line\nG1 X170 F1000\nG1 Z0.2 F720\nG1 X110 E8 F600\nG1 X40 E10 F400\nG92 E0\n\nM221 S95 ; set flow [printer:Original Prusa MINI & MINI+ 0.6 nozzle] inherits = Original Prusa MINI & MINI+ diff --git a/resources/profiles/TriLAB.idx b/resources/profiles/TriLAB.idx index 4a097ed6ae..65f47d59a6 100644 --- a/resources/profiles/TriLAB.idx +++ b/resources/profiles/TriLAB.idx @@ -1,8 +1,9 @@ min_slic3r_version = 2.3.0-alpha3 +0.0.7 Added PLA, PETG profiles for 0.25 nozzle, fixed supports on 0.8 nozzle profile, fixed max volumetric speed, disabled elefant foot compensation 0.0.6 Added material TPU 93A (SMARTFIL) 0.0.5 Removed obsolete host keys. 0.0.4 Added PLA, PETG profiles for 0.8 nozzle, update print materials 0.0.3 Added DeltiQ 2, DeltiQ 2 Plus printers, 0.10mm, 0.20mm FLEX print profiles, updated print materials, flexprint extension support min_slic3r_version = 2.3.0-alpha0 0.0.2 Added 0.15mm print profile -0.0.1 Initial TriLAB bundle +0.0.1 Initial TriLAB bundle \ No newline at end of file diff --git a/resources/profiles/TriLAB.ini b/resources/profiles/TriLAB.ini index d31461510d..d6824badeb 100644 --- a/resources/profiles/TriLAB.ini +++ b/resources/profiles/TriLAB.ini @@ -6,7 +6,7 @@ name = TriLAB # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the PrusaSlicer configuration to be downgraded. -config_version = 0.0.6 +config_version = 0.0.7 # Where to get the updates from? config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/TriLAB/ # changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% @@ -17,16 +17,16 @@ config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/Prus [printer_model:DQ2] name = DeltiQ 2 -variants = 0.4; 0.8 +variants = 0.4; 0.25; 0.8 technology = FFF family = DeltiQ 2 bed_model = dq2_bed.stl bed_texture = dq2_bed_texture.svg -default_materials = DeltiQ - PLA - Generic; DeltiQ - PETG - Generic; DeltiQ - ABS - Generic; DeltiQ - PLA - ExtraFill (Fillamentum); DeltiQ - PETG (Devil Design); DeltiQ - ABS - ExtraFill (Fillamentum); DeltiQ - ASA - ExtraFill (Fillamentum); DeltiQ - CPE - HG100 (Fillamentum); DeltiQ FP2 - PLA - Generic; DeltiQ FP2 - PETG - Generic; DeltiQ FP2 - ABS - Generic; DeltiQ FP2 - PLA - ExtraFill (Fillamentum); DeltiQ FP2 - PETG (Devil Design); DeltiQ FP2 - ABS - ExtraFill (Fillamentum); DeltiQ FP2 - ASA - ExtraFill (Fillamentum); DeltiQ FP2 - CPE - HG100 (Fillamentum); DeltiQ FP2 - FLEX - Generic; DeltiQ FP2 - TPU 92A - FlexFill (Fillamentum); DeltiQ FP2 - TPU 98A - FlexFill (Fillamentum); DeltiQ FP2 - TPU 93A (SMARTFIL); DeltiQ - PLA - ExtraFill (Fillamentum) @0.8 nozzle +default_materials = DeltiQ - PLA - Generic; DeltiQ - PETG - Generic; DeltiQ - ABS - Generic; DeltiQ - PLA - ExtraFill (Fillamentum); DeltiQ - PETG (Devil Design); DeltiQ - ABS - ExtraFill (Fillamentum); DeltiQ - ASA - ExtraFill (Fillamentum); DeltiQ - CPE - HG100 (Fillamentum); DeltiQ FP2 - PLA - Generic; DeltiQ FP2 - PETG - Generic; DeltiQ FP2 - ABS - Generic; DeltiQ FP2 - PLA - ExtraFill (Fillamentum); DeltiQ FP2 - PETG (Devil Design); DeltiQ FP2 - ABS - ExtraFill (Fillamentum); DeltiQ FP2 - ASA - ExtraFill (Fillamentum); DeltiQ FP2 - CPE - HG100 (Fillamentum); DeltiQ FP2 - FLEX - Generic; DeltiQ FP2 - TPU 92A - FlexFill (Fillamentum); DeltiQ FP2 - TPU 98A - FlexFill (Fillamentum); DeltiQ FP2 - TPU 93A (SMARTFIL); DeltiQ - PLA - ExtraFill (Fillamentum) @0.8 nozzle; DeltiQ - PLA - ExtraFill (Fillamentum) @0.25 nozzle [printer_model:DQ2P] name = DeltiQ 2 Plus -variants = 0.4; 0.8 +variants = 0.4; 0.25; 0.8 technology = FFF family = DeltiQ 2 bed_model = dq2_bed.stl @@ -110,7 +110,7 @@ complete_objects = 0 default_acceleration = 2000 dont_support_bridges = 0 draft_shield = 0 -elefant_foot_compensation = 0.2 +elefant_foot_compensation = 0.0 ensure_vertical_shell_thickness = 0 external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 30 @@ -150,7 +150,7 @@ min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 1 ooze_prevention = 0 -output_filename_format = {input_filename_base}_{printer_model}_{filament_type[0]}_{layer_height}mm_{print_time}.gcode +output_filename_format = {input_filename_base}_{printer_model}_{filament_type[0]}_{layer_height}mm_{print_time}_{timestamp}.gcode overhangs = 1 perimeter_acceleration = 1500 perimeter_extruder = 1 @@ -283,6 +283,37 @@ travel_speed = 200 max_print_speed = 40 complete_objects = 1 +[print:DeltiQ 0.07mm Quality @0.25 nozzle] +inherits = DeltiQ 0.20mm Normal +bottom_solid_layers = 6 +bottom_solid_min_thickness = 0.5 +bridge_speed = 60 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_TRILAB.*/ and nozzle_diameter[0]==0.25 +elefant_foot_compensation = 0.0 +external_perimeter_extrusion_width = 0.27 +extrusion_width = 0.25 +first_layer_extrusion_width = 0.26 +first_layer_height = 0.1 +infill_extrusion_width = 0.27 +infill_overlap = 50% +layer_height = 0.07 +overhangs = 1 +perimeter_extrusion_width = 0.27 +perimeters = 5 +solid_infill_extrusion_width = 0.27 +skirts = 2 +support_material_extrusion_width = 0.27 +support_material_contact_distance = 0.1 +top_infill_extrusion_width = 0.27 +top_solid_layers = 6 +top_solid_min_thickness = 0.5 +thin_walls = 1 + +[print:DeltiQ 0.20mm Normal @0.25 nozzle] +inherits = DeltiQ 0.07mm Quality @0.25 nozzle +first_layer_height = 0.2 +layer_height = 0.2 + [print:DeltiQ 0.40mm Normal @0.8 nozzle] inherits = DeltiQ 0.20mm Normal bottom_solid_layers = 3 @@ -290,7 +321,7 @@ bottom_solid_min_thickness = 1.2 bridge_flow_ratio = 0.90 bridge_speed = 20 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_TRILAB.*/ and nozzle_diameter[0]==0.8 -elefant_foot_compensation = 0.2 +elefant_foot_compensation = 0.0 external_perimeter_extrusion_width = 0.80 external_perimeter_speed = 30 extrusion_width = 0.80 @@ -311,6 +342,8 @@ perimeters = 2 small_perimeter_speed = 20 solid_infill_extrusion_width = 0.8 solid_infill_speed = 60 +support_material_extrusion_width = 0.8 +support_material_contact_distance = 0.2 top_infill_extrusion_width = 0.8 top_solid_infill_speed = 40 top_solid_layers = 4 @@ -412,7 +445,7 @@ fan_below_layer_time = 20 filament_vendor = Fillamentum filament_cost = 774 filament_density = 1.08 -filament_max_volumetric_speed = 4 +filament_max_volumetric_speed = 8 filament_retract_before_travel = 3 filament_retract_before_wipe = 70% filament_retract_layer_change = 1 @@ -474,6 +507,19 @@ min_print_speed = 10 slowdown_below_layer_time = 5 temperature = 260 +[filament:DeltiQ - PLA - ExtraFill (Fillamentum) @0.25 nozzle] +inherits = DeltiQ - PLA - ExtraFill (Fillamentum) +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_TRILAB.*/ and printer_notes=~/.*PRINTER_FAMILY_DQ.*/ and !(printer_notes=~/.*FLEXPRINT.*/) and nozzle_diameter[0]==0.25 + +[filament:DeltiQ - PETG (Devil Design) @0.25 nozzle] +inherits = DeltiQ - PETG (Devil Design) +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_TRILAB.*/ and printer_notes=~/.*PRINTER_FAMILY_DQ.*/ and !(printer_notes=~/.*FLEXPRINT.*/) and nozzle_diameter[0]==0.25 +first_layer_temperature = 225 +temperature = 220 +max_fan_speed = 65 +min_fan_speed = 40 +bridge_fan_speed = 100 + [filament:DeltiQ - PLA - ExtraFill (Fillamentum) @0.8 nozzle] inherits = DeltiQ - PLA - ExtraFill (Fillamentum) compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_TRILAB.*/ and printer_notes=~/.*PRINTER_FAMILY_DQ.*/ and !(printer_notes=~/.*FLEXPRINT.*/) and nozzle_diameter[0]==0.8 @@ -536,7 +582,7 @@ filament_vendor = Generic filament_cost = 1870 filament_density = 1.22 filament_deretract_speed = nil -filament_max_volumetric_speed = 0.7 +filament_max_volumetric_speed = 3.0 filament_retract_before_travel = 2 filament_retract_before_wipe = 70% filament_retract_layer_change = 0 @@ -634,7 +680,7 @@ filament_vendor = Fillamentum filament_cost = 1870 filament_density = 1.22 filament_deretract_speed = nil -filament_max_volumetric_speed = 2.9 +filament_max_volumetric_speed = 3.0 filament_retract_before_travel = 2 filament_retract_before_wipe = 70% filament_retract_layer_change = 0 @@ -665,7 +711,7 @@ filament_vendor = Fillamentum filament_cost = 1870 filament_density = 1.22 filament_deretract_speed = nil -filament_max_volumetric_speed = 2.9 +filament_max_volumetric_speed = 3.0 filament_retract_before_travel = 2 filament_retract_before_wipe = 70% filament_retract_layer_change = 0 @@ -689,7 +735,7 @@ extrusion_multiplier = 1.10 filament_cost = 1870 filament_density = 1.23 filament_deretract_speed = nil -filament_max_volumetric_speed = 2.9 +filament_max_volumetric_speed = 3.0 filament_retract_before_wipe = 70% filament_retract_length = 2.5 filament_retract_speed = 20 @@ -822,6 +868,15 @@ printer_model = DQ2 printer_variant = 0.4 max_print_height = 320 +[printer:DeltiQ 2 - 0.25 nozzle] +inherits = DeltiQ 2 +printer_variant = 0.25 +max_layer_height = 0.15 +min_layer_height = 0.07 +nozzle_diameter = 0.25 +default_filament_profile = "DeltiQ - PLA - ExtraFill (Fillamentum) @0.25 nozzle" +default_print_profile = DeltiQ 0.07mm Quality @0.25 nozzle + [printer:DeltiQ 2 - 0.8 nozzle] inherits = DeltiQ 2 printer_variant = 0.8 @@ -837,6 +892,15 @@ printer_model = DQ2P printer_variant = 0.4 max_print_height = 500 +[printer:DeltiQ 2 Plus - 0.25 nozzle] +inherits = DeltiQ 2 Plus +printer_variant = 0.25 +max_layer_height = 0.15 +min_layer_height = 0.07 +nozzle_diameter = 0.25 +default_filament_profile = "DeltiQ - PLA - ExtraFill (Fillamentum) @0.25 nozzle" +default_print_profile = DeltiQ 0.07mm Quality @0.25 nozzle + [printer:DeltiQ 2 Plus - 0.8 nozzle] inherits = DeltiQ 2 Plus printer_variant = 0.8 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e25399911b..c036af38dd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -58,9 +58,27 @@ if (SLIC3R_GUI) include(${wxWidgets_USE_FILE}) string(REGEX MATCH "wxpng" WX_PNG_BUILTIN ${wxWidgets_LIBRARIES}) - if (PNG_FOUND AND NOT WX_PNG_BUILTIN) - list(FILTER wxWidgets_LIBRARIES EXCLUDE REGEX png) - list(APPEND wxWidgets_LIBRARIES ${PNG_LIBRARIES}) + if (NOT WX_PNG_BUILTIN) + find_package(JPEG QUIET) + if (PNG_FOUND) + list(FILTER wxWidgets_LIBRARIES EXCLUDE REGEX png) + list(APPEND wxWidgets_LIBRARIES ${PNG_LIBRARIES}) + endif () + endif () + + string(REGEX MATCH "wxtiff" WX_TIFF_BUILTIN ${wxWidgets_LIBRARIES}) + if (NOT WX_TIFF_BUILTIN) + find_package(TIFF QUIET) + if (TIFF_FOUND) + list(FILTER wxWidgets_LIBRARIES EXCLUDE REGEX tiff) + list(APPEND wxWidgets_LIBRARIES ${TIFF_LIBRARIES}) + endif () + endif () + + string(REGEX MATCH "wxjpeg" WX_JPEG_BUILTIN ${wxWidgets_LIBRARIES}) + if (TIFF_FOUND AND NOT WX_JPEG_BUILTIN) + list(FILTER wxWidgets_LIBRARIES EXCLUDE REGEX jpeg) + list(APPEND wxWidgets_LIBRARIES ${JPEG_LIBRARIES}) endif () string(REGEX MATCH "wxexpat" WX_EXPAT_BUILTIN ${wxWidgets_LIBRARIES}) @@ -68,7 +86,6 @@ if (SLIC3R_GUI) list(FILTER wxWidgets_LIBRARIES EXCLUDE REGEX expat) list(APPEND wxWidgets_LIBRARIES ${EXPAT_LIBRARIES}) endif () - # This is an issue in the new wxWidgets cmake build, doesn't deal with librt find_library(LIBRT rt) if(LIBRT) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index ed2d702728..50e5096bf3 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -38,6 +38,7 @@ #include "libslic3r/GCode/PostProcessor.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/ModelArrange.hpp" +#include "libslic3r/Platform.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/SLAPrint.hpp" #include "libslic3r/TriangleMesh.hpp" @@ -603,6 +604,9 @@ bool CLI::setup(int argc, char **argv) } } + // Detect the operating system flavor after SLIC3R_LOGLEVEL is set. + detect_platform(); + #ifdef WIN32 // Notify user if blacklisted library is already loaded (Nahimic) // If there are cases of no reports with blacklisted lib - this check should be performed later. diff --git a/src/PrusaSlicer_app_msvc.cpp b/src/PrusaSlicer_app_msvc.cpp index 7710d9eb7e..2ccf2f1ffd 100644 --- a/src/PrusaSlicer_app_msvc.cpp +++ b/src/PrusaSlicer_app_msvc.cpp @@ -217,6 +217,11 @@ int APIENTRY wWinMain(HINSTANCE /* hInstance */, HINSTANCE /* hPrevInstance */, int wmain(int argc, wchar_t **argv) { #endif + // Allow the asserts to open message box, such message box allows to ignore the assert and continue with the application. + // Without this call, the seemingly same message box is being opened by the abort() function, but that is too late and + // the application will be killed even if "Ignore" button is pressed. + _set_error_mode(_OUT_TO_MSGBOX); + std::vector argv_extended; argv_extended.emplace_back(argv[0]); diff --git a/src/clipper/clipper.cpp b/src/clipper/clipper.cpp index 3c0057b22b..cbe54a0647 100644 --- a/src/clipper/clipper.cpp +++ b/src/clipper/clipper.cpp @@ -287,6 +287,11 @@ bool Poly2ContainsPoly1(OutPt *OutPt1, OutPt *OutPt2) } //---------------------------------------------------------------------- +#ifdef CLIPPERLIB_INT32 +inline bool SlopesEqual(const cInt dx1, const cInt dy1, const cInt dx2, const cInt dy2, bool /* UseFullInt64Range */) { + return int64_t(dy1) * int64_t(dx2) == int64_t(dx1) * int64_t(dy2); +} +#else inline bool SlopesEqual(const cInt dx1, const cInt dy1, const cInt dx2, const cInt dy2, bool UseFullInt64Range) { return (UseFullInt64Range) ? // |dx1| < 2^63, |dx2| < 2^63 etc, @@ -296,6 +301,8 @@ inline bool SlopesEqual(const cInt dx1, const cInt dy1, const cInt dx2, const cI // therefore the following computation could be done with 64bit arithmetics. dy1 * dx2 == dx1 * dy2; } +#endif + inline bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range) { return SlopesEqual(e1.Delta.X, e1.Delta.Y, e2.Delta.X, e2.Delta.Y, UseFullInt64Range); } inline bool SlopesEqual(const IntPoint &pt1, const IntPoint &pt2, const IntPoint &pt3, bool UseFullInt64Range) @@ -363,8 +370,8 @@ void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip) } else { - b1 = Edge1.Bot.X - Edge1.Bot.Y * Edge1.Dx; - b2 = Edge2.Bot.X - Edge2.Bot.Y * Edge2.Dx; + b1 = double(Edge1.Bot.X) - double(Edge1.Bot.Y) * Edge1.Dx; + b2 = double(Edge2.Bot.X) - double(Edge2.Bot.Y) * Edge2.Dx; double q = (b2-b1) / (Edge1.Dx - Edge2.Dx); ip.Y = Round(q); ip.X = (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) ? @@ -569,6 +576,7 @@ bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b) // ClipperBase class methods ... //------------------------------------------------------------------------------ +#ifndef CLIPPERLIB_INT32 // Called from ClipperBase::AddPath() to verify the scale of the input polygon coordinates. inline void RangeTest(const IntPoint& Pt, bool& useFullRange) { @@ -583,6 +591,7 @@ inline void RangeTest(const IntPoint& Pt, bool& useFullRange) RangeTest(Pt, useFullRange); } } +#endif // CLIPPERLIB_INT32 //------------------------------------------------------------------------------ // Called from ClipperBase::AddPath() to construct the Local Minima List. @@ -805,13 +814,17 @@ bool ClipperBase::AddPathInternal(const Path &pg, int highI, PolyType PolyTyp, b try { edges[1].Curr = pg[1]; +#ifndef CLIPPERLIB_INT32 RangeTest(pg[0], m_UseFullRange); RangeTest(pg[highI], m_UseFullRange); +#endif // CLIPPERLIB_INT32 InitEdge(&edges[0], &edges[1], &edges[highI], pg[0]); InitEdge(&edges[highI], &edges[0], &edges[highI-1], pg[highI]); for (int i = highI - 1; i >= 1; --i) { +#ifndef CLIPPERLIB_INT32 RangeTest(pg[i], m_UseFullRange); +#endif // CLIPPERLIB_INT32 InitEdge(&edges[i], &edges[i+1], &edges[i-1], pg[i]); } } @@ -967,7 +980,9 @@ void ClipperBase::Clear() CLIPPERLIB_PROFILE_FUNC(); m_MinimaList.clear(); m_edges.clear(); +#ifndef CLIPPERLIB_INT32 m_UseFullRange = false; +#endif // CLIPPERLIB_INT32 m_HasOpenPaths = false; } //------------------------------------------------------------------------------ @@ -3322,9 +3337,9 @@ DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) if(pt2.X == pt1.X && pt2.Y == pt1.Y) return DoublePoint(0, 0); - double Dx = (double)(pt2.X - pt1.X); - double dy = (double)(pt2.Y - pt1.Y); - double f = 1 *1.0/ std::sqrt( Dx*Dx + dy*dy ); + double Dx = double(pt2.X - pt1.X); + double dy = double(pt2.Y - pt1.Y); + double f = 1.0 / std::sqrt( Dx*Dx + dy*dy ); Dx *= f; dy *= f; return DoublePoint(dy, -Dx); @@ -3530,8 +3545,9 @@ void ClipperOffset::DoOffset(double delta) } //see offset_triginometry3.svg in the documentation folder ... - if (MiterLimit > 2) m_miterLim = 2/(MiterLimit * MiterLimit); - else m_miterLim = 0.5; + m_miterLim = (MiterLimit > 2) ? + 2. / (MiterLimit * MiterLimit) : + 0.5; double y; if (ArcTolerance <= 0.0) y = def_arc_tolerance; @@ -3633,11 +3649,9 @@ void ClipperOffset::DoOffset(double delta) if (node.m_endtype == etOpenButt) { int j = len - 1; - pt1 = IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * - delta), Round(m_srcPoly[j].Y + m_normals[j].Y * delta)); + pt1 = IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * delta), Round(m_srcPoly[j].Y + m_normals[j].Y * delta)); m_destPoly.push_back(pt1); - pt1 = IntPoint(Round(m_srcPoly[j].X - m_normals[j].X * - delta), Round(m_srcPoly[j].Y - m_normals[j].Y * delta)); + pt1 = IntPoint(Round(m_srcPoly[j].X - m_normals[j].X * delta), Round(m_srcPoly[j].Y - m_normals[j].Y * delta)); m_destPoly.push_back(pt1); } else @@ -3662,11 +3676,9 @@ void ClipperOffset::DoOffset(double delta) if (node.m_endtype == etOpenButt) { - pt1 = IntPoint(Round(m_srcPoly[0].X - m_normals[0].X * delta), - Round(m_srcPoly[0].Y - m_normals[0].Y * delta)); + pt1 = IntPoint(Round(m_srcPoly[0].X - m_normals[0].X * delta), Round(m_srcPoly[0].Y - m_normals[0].Y * delta)); m_destPoly.push_back(pt1); - pt1 = IntPoint(Round(m_srcPoly[0].X + m_normals[0].X * delta), - Round(m_srcPoly[0].Y + m_normals[0].Y * delta)); + pt1 = IntPoint(Round(m_srcPoly[0].X + m_normals[0].X * delta), Round(m_srcPoly[0].Y + m_normals[0].Y * delta)); m_destPoly.push_back(pt1); } else @@ -3753,7 +3765,7 @@ void ClipperOffset::DoRound(int j, int k) { double a = std::atan2(m_sinA, m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y); - int steps = std::max((int)Round(m_StepsPerRad * std::fabs(a)), 1); + auto steps = std::max(Round(m_StepsPerRad * std::fabs(a)), 1); double X = m_normals[k].X, Y = m_normals[k].Y, X2; for (int i = 0; i < steps; ++i) @@ -3885,8 +3897,8 @@ void SimplifyPolygons(Paths &polys, PolyFillType fillType) inline double DistanceSqrd(const IntPoint& pt1, const IntPoint& pt2) { - double Dx = ((double)pt1.X - pt2.X); - double dy = ((double)pt1.Y - pt2.Y); + auto Dx = double(pt1.X - pt2.X); + auto dy = double(pt1.Y - pt2.Y); return (Dx*Dx + dy*dy); } //------------------------------------------------------------------------------ @@ -3937,8 +3949,8 @@ bool SlopesNearCollinear(const IntPoint& pt1, bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd) { - double Dx = (double)pt1.X - pt2.X; - double dy = (double)pt1.Y - pt2.Y; + auto Dx = double(pt1.X - pt2.X); + auto dy = double(pt1.Y - pt2.Y); return ((Dx * Dx) + (dy * dy) <= distSqrd); } //------------------------------------------------------------------------------ diff --git a/src/clipper/clipper.hpp b/src/clipper/clipper.hpp index 8b34779e3a..48e83d0461 100644 --- a/src/clipper/clipper.hpp +++ b/src/clipper/clipper.hpp @@ -71,12 +71,22 @@ enum PolyType { ptSubject, ptClip }; //see http://glprogramming.com/red/chapter11.html enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; +// If defined, Clipper will work with 32bit signed int coordinates to reduce memory +// consumption and to speed up exact orientation predicate calculation. +// In that case, coordinates and their differences (vectors of the coordinates) have to fit int32_t. +#define CLIPPERLIB_INT32 + // Point coordinate type -typedef int64_t cInt; -// Maximum cInt value to allow a cross product calculation using 32bit expressions. -static cInt const loRange = 0x3FFFFFFF; -// Maximum allowed cInt value. -static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL; +#ifdef CLIPPERLIB_INT32 + // Coordinates and their differences (vectors of the coordinates) have to fit int32_t. + typedef int32_t cInt; +#else + typedef int64_t cInt; + // Maximum cInt value to allow a cross product calculation using 32bit expressions. + static constexpr cInt const loRange = 0x3FFFFFFF; // 0x3FFFFFFF = 1 073 741 823 + // Maximum allowed cInt value. + static constexpr cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL; +#endif // CLIPPERLIB_INT32 struct IntPoint { cInt X; @@ -289,7 +299,11 @@ enum EdgeSide { esLeft = 1, esRight = 2}; class ClipperBase { public: - ClipperBase() : m_UseFullRange(false), m_HasOpenPaths(false) {} + ClipperBase() : +#ifndef CLIPPERLIB_INT32 + m_UseFullRange(false), +#endif // CLIPPERLIB_INT32 + m_HasOpenPaths(false) {} ~ClipperBase() { Clear(); } bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed); bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed); @@ -310,9 +324,14 @@ protected: // Local minima (Y, left edge, right edge) sorted by ascending Y. std::vector m_MinimaList; +#ifdef CLIPPERLIB_INT32 + static constexpr const bool m_UseFullRange = false; +#else // CLIPPERLIB_INT32 // True if the input polygons have abs values higher than loRange, but lower than hiRange. // False if the input polygons have abs values lower or equal to loRange. bool m_UseFullRange; +#endif // CLIPPERLIB_INT32 + // A vector of edges per each input path. std::vector> m_edges; // Don't remove intermediate vertices of a collinear sequence of points. diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index d52294acd2..1ee719288c 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -123,6 +123,8 @@ namespace ImGui const char ErrorMarker = 0x11; const char EjectButton = 0x12; const char EjectHoverButton = 0x13; + const char CancelButton = 0x14; + const char CancelHoverButton = 0x15; // void MyFunction(const char* name, const MyMatrix44& v); } diff --git a/src/libnest2d/CMakeLists.txt b/src/libnest2d/CMakeLists.txt index 592ab069c3..3892ed30bc 100644 --- a/src/libnest2d/CMakeLists.txt +++ b/src/libnest2d/CMakeLists.txt @@ -28,4 +28,4 @@ add_library(libnest2d STATIC ${LIBNEST2D_SRCFILES}) target_include_directories(libnest2d PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include) target_link_libraries(libnest2d PUBLIC clipper NLopt::nlopt TBB::tbb Boost::boost) -target_compile_definitions(libnest2d PUBLIC USE_TBB LIBNEST2D_STATIC LIBNEST2D_OPTIMIZER_nlopt LIBNEST2D_GEOMETRIES_clipper) +target_compile_definitions(libnest2d PUBLIC LIBNEST2D_THREADING_tbb LIBNEST2D_STATIC LIBNEST2D_OPTIMIZER_nlopt LIBNEST2D_GEOMETRIES_clipper) diff --git a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp index dd80322bcc..9586db35c6 100644 --- a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp +++ b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp @@ -26,7 +26,10 @@ template<> struct ShapeTag { using Type = PointTag; }; // 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 CoordType { + using Type = ClipperLib::cInt; + static const constexpr ClipperLib::cInt MM_IN_COORDS = 1000000; +}; // Enough to specialize for path, it will work for multishape and Polygon template<> struct PointType { using Type = PointImpl; }; @@ -95,6 +98,9 @@ inline void offset(PolygonImpl& sh, TCoord distance, const PolygonTag // Offsetting reverts the orientation and also removes the last vertex // so boost will not have a closed polygon. + // we plan to replace contours + sh.Holes.clear(); + bool found_the_contour = false; for(auto& r : result) { if(ClipperLib::Orientation(r)) { diff --git a/src/libnest2d/include/libnest2d/geometry_traits.hpp b/src/libnest2d/include/libnest2d/geometry_traits.hpp index c447cfcd93..3095c717db 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits.hpp @@ -47,7 +47,8 @@ using TPoint = typename PointType>::Type; /// Getting the coordinate data type for a geometry class. template struct CoordType { - using Type = typename CoordType>::Type; + using Type = typename CoordType>::Type; + static const constexpr Type MM_IN_COORDS = Type{1}; }; /// TCoord as shorthand for typename `CoordType::Type`. @@ -473,8 +474,8 @@ inline _Box

_Box

::infinite(const P& center) { // 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); + C Mx = C((std::numeric_limits::lowest() + 2 * getX(center)) / 4.01); + C My = C((std::numeric_limits::lowest() + 2 * getY(center)) / 4.01); ret.maxCorner() = center - P{Mx, My}; ret.minCorner() = center + P{Mx, My}; diff --git a/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp b/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp index 29a1ccd047..6b3ff60c18 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp @@ -220,7 +220,9 @@ inline NfpResult nfpConvexOnly(const RawShape& sh, auto next = std::next(first); while(next != sl::cend(sh)) { - edgelist.emplace_back(*(first), *(next)); + if (pl::magnsq(*next - *first) > 0) + edgelist.emplace_back(*(first), *(next)); + ++first; ++next; } } @@ -230,7 +232,9 @@ inline NfpResult nfpConvexOnly(const RawShape& sh, auto next = std::next(first); while(next != sl::cend(other)) { - edgelist.emplace_back(*(next), *(first)); + if (pl::magnsq(*next - *first) > 0) + edgelist.emplace_back(*(next), *(first)); + ++first; ++next; } } diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index 5eef28c407..9d24a25696 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -130,6 +130,11 @@ std::size_t nest(Container&& cont, return nest(cont.begin(), cont.end(), bin, dist, cfg, ctl); } +template enable_if_t::value, TCoord> mm(T val = T(1)) +{ + return TCoord(val * CoordType::MM_IN_COORDS); +} + } #endif // LIBNEST2D_HPP diff --git a/src/libnest2d/include/libnest2d/parallel.hpp b/src/libnest2d/include/libnest2d/parallel.hpp new file mode 100644 index 0000000000..2eaf86c018 --- /dev/null +++ b/src/libnest2d/include/libnest2d/parallel.hpp @@ -0,0 +1,63 @@ +#ifndef LIBNEST2D_PARALLEL_HPP +#define LIBNEST2D_PARALLEL_HPP + +#include +#include +#include + +#ifdef LIBNEST2D_THREADING_tbb +#include +#endif + +#ifdef LIBNEST2D_THREADING_omp +#include +#endif + +namespace libnest2d { namespace __parallel { + +template +using TIteratorValue = typename std::iterator_traits::value_type; + +template +inline void enumerate( + Iterator from, Iterator to, + std::function, size_t)> fn, + std::launch policy = std::launch::deferred | std::launch::async) +{ + using TN = size_t; + auto iN = to-from; + TN N = iN < 0? 0 : TN(iN); + +#ifdef LIBNEST2D_THREADING_tbb + if((policy & std::launch::async) == std::launch::async) { + tbb::parallel_for(0, N, [from, fn] (TN n) { fn(*(from + n), n); } ); + } else { + for(TN n = 0; n < N; n++) fn(*(from + n), n); + } +#endif + +#ifdef LIBNEST2D_THREADING_omp + if((policy & std::launch::async) == std::launch::async) { + #pragma omp parallel for + for(int n = 0; n < int(N); n++) fn(*(from + n), TN(n)); + } + else { + for(TN n = 0; n < N; n++) fn(*(from + n), n); + } +#endif + +#ifdef LIBNEST2D_THREADING_std + std::vector> rets(N); + + auto it = from; + for(TN b = 0; b < N; b++) { + rets[b] = std::async(policy, fn, *it++, unsigned(b)); + } + + for(TN fi = 0; fi < N; ++fi) rets[fi].wait(); +#endif +} + +}} + +#endif //LIBNEST2D_PARALLEL_HPP diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index 83f7bd246a..bd9c603558 100644 --- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -20,59 +20,9 @@ // temporary //#include "../tools/svgtools.hpp" -#ifdef USE_TBB -#include -#elif defined(_OPENMP) -#include -#endif +#include namespace libnest2d { - -namespace __parallel { - -using std::function; -using std::iterator_traits; -template -using TIteratorValue = typename iterator_traits::value_type; - -template -inline void enumerate( - Iterator from, Iterator to, - function, size_t)> fn, - std::launch policy = std::launch::deferred | std::launch::async) -{ - using TN = size_t; - auto iN = to-from; - TN N = iN < 0? 0 : TN(iN); - -#ifdef USE_TBB - if((policy & std::launch::async) == std::launch::async) { - tbb::parallel_for(0, N, [from, fn] (TN n) { fn(*(from + n), n); } ); - } else { - for(TN n = 0; n < N; n++) fn(*(from + n), n); - } -#elif defined(_OPENMP) - if((policy & std::launch::async) == std::launch::async) { - #pragma omp parallel for - for(int n = 0; n < int(N); n++) fn(*(from + n), TN(n)); - } - else { - for(TN n = 0; n < N; n++) fn(*(from + n), n); - } -#else - std::vector> rets(N); - - auto it = from; - for(TN b = 0; b < N; b++) { - rets[b] = std::async(policy, fn, *it++, unsigned(b)); - } - - for(TN fi = 0; fi < N; ++fi) rets[fi].wait(); -#endif -} - -} - namespace placers { template diff --git a/src/libnest2d/tools/svgtools.hpp b/src/libnest2d/tools/svgtools.hpp index 2a05b551d1..47757cd46c 100644 --- a/src/libnest2d/tools/svgtools.hpp +++ b/src/libnest2d/tools/svgtools.hpp @@ -54,10 +54,10 @@ public: if(conf_.origo_location == BOTTOMLEFT) { auto d = static_cast( std::round(conf_.height*conf_.mm_in_coord_units) ); - + auto& contour = shapelike::contour(tsh); for(auto& v : contour) setY(v, -getY(v) + d); - + auto& holes = shapelike::holes(tsh); for(auto& h : holes) for(auto& v : h) setY(v, -getY(v) + d); @@ -81,6 +81,18 @@ public: finishLayer(); } } + + template void writeItems(ItemIt from, ItemIt to) { + auto it = from; + PackGroup pg; + while(it != to) { + if(it->binId() == BIN_ID_UNSET) continue; + while(pg.size() <= size_t(it->binId())) pg.emplace_back(); + pg[it->binId()].emplace_back(*it); + ++it; + } + writePackGroup(pg); + } void addLayer() { svg_layers_.emplace_back(header()); diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp index b11c570f66..76aa36194e 100644 --- a/src/libslic3r/AABBTreeIndirect.hpp +++ b/src/libslic3r/AABBTreeIndirect.hpp @@ -11,6 +11,8 @@ #include #include +#include + #include "Utils.hpp" // for next_highest_power_of_2() extern "C" @@ -752,6 +754,83 @@ void get_candidate_idxs(const TreeType& tree, const VectorType& v, std::vector struct Intersecting {}; + +// Intersection predicate specialization for box-box intersections +template +struct Intersecting> { + Eigen::AlignedBox box; + + Intersecting(const Eigen::AlignedBox &bb): box{bb} {} + + bool operator() (const typename Tree::Node &node) const + { + return box.intersects(node.bbox); + } +}; + +template auto intersecting(const G &g) { return Intersecting{g}; } + +template struct Containing {}; + +// Intersection predicate specialization for box-box intersections +template +struct Containing> { + Eigen::AlignedBox box; + + Containing(const Eigen::AlignedBox &bb): box{bb} {} + + bool operator() (const typename Tree::Node &node) const + { + return box.contains(node.bbox); + } +}; + +template auto containing(const G &g) { return Containing{g}; } + +namespace detail { + +template +void traverse_recurse(const Tree &tree, + size_t idx, + Pred && pred, + Fn && callback) +{ + assert(tree.node(idx).is_valid()); + + if (!pred(tree.node(idx))) return; + + if (tree.node(idx).is_leaf()) { + callback(tree.node(idx).idx); + } else { + + // call this with left and right node idx: + auto trv = [&](size_t idx) { + traverse_recurse(tree, idx, std::forward(pred), + std::forward(callback)); + }; + + // Left / right child node index. + trv(Tree::left_child_idx(idx)); + trv(Tree::right_child_idx(idx)); + } +} + +} // namespace detail + +// Tree traversal with a predicate. Example usage: +// traverse(tree, intersecting(QueryBox), [](size_t face_idx) { +// /* ... */ +// }); +template +void traverse(const Tree &tree, Predicate &&pred, Fn &&callback) +{ + if (tree.empty()) return; + + detail::traverse_recurse(tree, size_t(0), std::forward(pred), + std::forward(callback)); +} } // namespace AABBTreeIndirect } // namespace Slic3r diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 08e5fdf6d8..d9301d1f3d 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -68,14 +68,12 @@ void AppConfig::set_defaults() if (get("export_sources_full_pathnames").empty()) set("export_sources_full_pathnames", "0"); -#if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN #ifdef _WIN32 if (get("associate_3mf").empty()) set("associate_3mf", "0"); if (get("associate_stl").empty()) set("associate_stl", "0"); #endif // _WIN32 -#endif // ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN // remove old 'use_legacy_opengl' parameter from this config, if present if (!get("use_legacy_opengl").empty()) @@ -127,14 +125,12 @@ void AppConfig::set_defaults() if (get("color_mapinulation_panel").empty()) set("color_mapinulation_panel", "0"); } -#if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN else { #ifdef _WIN32 if (get("associate_gcode").empty()) set("associate_gcode", "0"); #endif // _WIN32 } -#endif // ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN if (get("seq_top_layer_only").empty()) set("seq_top_layer_only", "1"); diff --git a/src/libslic3r/BoundingBox.cpp b/src/libslic3r/BoundingBox.cpp index eb4e042a08..4f52c5108d 100644 --- a/src/libslic3r/BoundingBox.cpp +++ b/src/libslic3r/BoundingBox.cpp @@ -225,24 +225,11 @@ BoundingBox3Base::max_size() const template coordf_t BoundingBox3Base::max_size() const; template coordf_t BoundingBox3Base::max_size() const; -// Align a coordinate to a grid. The coordinate may be negative, -// the aligned value will never be bigger than the original one. -static inline coord_t _align_to_grid(const coord_t coord, const coord_t spacing) { - // Current C++ standard defines the result of integer division to be rounded to zero, - // for both positive and negative numbers. Here we want to round down for negative - // numbers as well. - coord_t aligned = (coord < 0) ? - ((coord - spacing + 1) / spacing) * spacing : - (coord / spacing) * spacing; - assert(aligned <= coord); - return aligned; -} - void BoundingBox::align_to_grid(const coord_t cell_size) { if (this->defined) { - min(0) = _align_to_grid(min(0), cell_size); - min(1) = _align_to_grid(min(1), cell_size); + min(0) = Slic3r::align_to_grid(min(0), cell_size); + min(1) = Slic3r::align_to_grid(min(1), cell_size); } } diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 8de28af5cd..37483fc3e2 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -21,22 +21,30 @@ public: min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {} BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) : min(p1), max(p1), defined(false) { merge(p2); merge(p3); } - BoundingBoxBase(const std::vector& points) : min(PointClass::Zero()), max(PointClass::Zero()) + + template > + BoundingBoxBase(It from, It to) : min(PointClass::Zero()), max(PointClass::Zero()) { - if (points.empty()) { + if (from == to) { this->defined = false; // throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBoxBase constructor"); } else { - typename std::vector::const_iterator it = points.begin(); - this->min = *it; - this->max = *it; - for (++ it; it != points.end(); ++ it) { - this->min = this->min.cwiseMin(*it); - this->max = this->max.cwiseMax(*it); + auto it = from; + this->min = it->template cast(); + this->max = this->min; + for (++ it; it != to; ++ it) { + auto vec = it->template cast(); + this->min = this->min.cwiseMin(vec); + this->max = this->max.cwiseMax(vec); } this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1)); } } + + BoundingBoxBase(const std::vector &points) + : BoundingBoxBase(points.begin(), points.end()) + {} + void reset() { this->defined = false; this->min = PointClass::Zero(); this->max = PointClass::Zero(); } void merge(const PointClass &point); void merge(const std::vector &points); @@ -74,19 +82,27 @@ public: { if (pmin(2) >= pmax(2)) BoundingBoxBase::defined = false; } BoundingBox3Base(const PointClass &p1, const PointClass &p2, const PointClass &p3) : BoundingBoxBase(p1, p1) { merge(p2); merge(p3); } - BoundingBox3Base(const std::vector& points) + + template > BoundingBox3Base(It from, It to) { - if (points.empty()) + if (from == to) throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBox3Base constructor"); - typename std::vector::const_iterator it = points.begin(); - this->min = *it; - this->max = *it; - for (++ it; it != points.end(); ++ it) { - this->min = this->min.cwiseMin(*it); - this->max = this->max.cwiseMax(*it); + + auto it = from; + this->min = it->template cast(); + this->max = this->min; + for (++ it; it != to; ++ it) { + auto vec = it->template cast(); + this->min = this->min.cwiseMin(vec); + this->max = this->max.cwiseMax(vec); } this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1)) && (this->min(2) < this->max(2)); } + + BoundingBox3Base(const std::vector &points) + : BoundingBox3Base(points.begin(), points.end()) + {} + void merge(const PointClass &point); void merge(const std::vector &points); void merge(const BoundingBox3Base &bb); @@ -188,9 +204,7 @@ public: class BoundingBoxf3 : public BoundingBox3Base { public: - BoundingBoxf3() : BoundingBox3Base() {} - BoundingBoxf3(const Vec3d &pmin, const Vec3d &pmax) : BoundingBox3Base(pmin, pmax) {} - BoundingBoxf3(const std::vector &points) : BoundingBox3Base(points) {} + using BoundingBox3Base::BoundingBox3Base; BoundingBoxf3 transformed(const Transform3d& matrix) const; }; diff --git a/src/libslic3r/BridgeDetector.cpp b/src/libslic3r/BridgeDetector.cpp index ff33e81d53..671ebbdaad 100644 --- a/src/libslic3r/BridgeDetector.cpp +++ b/src/libslic3r/BridgeDetector.cpp @@ -40,7 +40,7 @@ void BridgeDetector::initialize() this->angle = -1.; // Outset our bridge by an arbitrary amout; we'll use this outer margin for detecting anchors. - Polygons grown = offset(to_polygons(this->expolygons), float(this->spacing)); + Polygons grown = offset(this->expolygons, float(this->spacing)); // Detect possible anchoring edges of this bridging region. // Detect what edges lie on lower slices by turning bridge contour and holes diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index d5ec0d9285..08bedc5c04 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -320,7 +320,7 @@ static void make_inner_brim(const Print &print, const ConstPrintObjectPtrs &top_ loops = union_pt_chained_outside_in(loops, false); std::reverse(loops.begin(), loops.end()); extrusion_entities_append_loops(brim.entities, std::move(loops), erSkirt, float(flow.mm3_per_mm()), - float(flow.width), float(print.skirt_first_layer_height())); + float(flow.width()), float(print.skirt_first_layer_height())); } // Produce brim lines around those objects, that have the brim enabled. @@ -495,7 +495,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance if (i + 1 == j && first_path.size() > 3 && first_path.front().X == first_path.back().X && first_path.front().Y == first_path.back().Y) { auto *loop = new ExtrusionLoop(); brim.entities.emplace_back(loop); - loop->paths.emplace_back(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(print.skirt_first_layer_height())); + loop->paths.emplace_back(erSkirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height())); Points &points = loop->paths.front().polyline.points; points.reserve(first_path.size()); for (const ClipperLib_Z::IntPoint &pt : first_path) @@ -506,7 +506,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance ExtrusionEntityCollection this_loop_trimmed; this_loop_trimmed.entities.reserve(j - i); for (; i < j; ++ i) { - this_loop_trimmed.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(print.skirt_first_layer_height()))); + this_loop_trimmed.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()))); const ClipperLib_Z::Path &path = *loops_trimmed_order[i].first; Points &points = static_cast(this_loop_trimmed.entities.back())->polyline.points; points.reserve(path.size()); @@ -522,7 +522,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance } } } else { - extrusion_entities_append_loops_and_paths(brim.entities, std::move(all_loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(print.skirt_first_layer_height())); + extrusion_entities_append_loops_and_paths(brim.entities, std::move(all_loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height())); } make_inner_brim(print, top_level_objects_with_brim, brim); diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index b12ae84193..507d2f0aa7 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -211,10 +211,14 @@ add_library(libslic3r STATIC PerimeterGenerator.hpp PlaceholderParser.cpp PlaceholderParser.hpp + Platform.cpp + Platform.hpp Point.cpp Point.hpp Polygon.cpp Polygon.hpp + MutablePolygon.cpp + MutablePolygon.hpp PolygonTrimmer.cpp PolygonTrimmer.hpp Polyline.cpp @@ -285,6 +289,9 @@ add_library(libslic3r STATIC SimplifyMeshImpl.hpp SimplifyMesh.cpp MarchingSquares.hpp + Execution/Execution.hpp + Execution/ExecutionSeq.hpp + Execution/ExecutionTBB.hpp Optimize/Optimizer.hpp Optimize/NLoptOptimizer.hpp Optimize/BruteforceOptimizer.hpp diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 305ea134f0..cd243dfb1b 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -57,6 +57,7 @@ err: } #endif /* CLIPPER_UTILS_DEBUG */ +#ifdef CLIPPERUTILS_OFFSET_SCALE void scaleClipperPolygon(ClipperLib::Path &polygon) { CLIPPERUTILS_PROFILE_FUNC(); @@ -98,6 +99,7 @@ void unscaleClipperPolygons(ClipperLib::Paths &polygons) pit->Y >>= CLIPPER_OFFSET_POWER_OF_2; } } +#endif // CLIPPERUTILS_OFFSET_SCALE //----------------------------------------------------------- // legacy code from Clipper documentation @@ -222,8 +224,10 @@ ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polylines &input) ClipperLib::Paths _offset(ClipperLib::Paths &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit) { +#ifdef CLIPPERUTILS_OFFSET_SCALE // scale input scaleClipperPolygons(input); +#endif // CLIPPERUTILS_OFFSET_SCALE // perform offset ClipperLib::ClipperOffset co; @@ -231,14 +235,20 @@ ClipperLib::Paths _offset(ClipperLib::Paths &&input, ClipperLib::EndType endType co.ArcTolerance = miterLimit; else co.MiterLimit = miterLimit; +#ifdef CLIPPERUTILS_OFFSET_SCALE float delta_scaled = delta * float(CLIPPER_OFFSET_SCALE); +#else // CLIPPERUTILS_OFFSET_SCALE + float delta_scaled = delta; +#endif // CLIPPERUTILS_OFFSET_SCALE co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); co.AddPaths(input, joinType, endType); ClipperLib::Paths retval; co.Execute(retval, delta_scaled); +#ifdef CLIPPERUTILS_OFFSET_SCALE // unscale output unscaleClipperPolygons(retval); +#endif // CLIPPERUTILS_OFFSET_SCALE return retval; } @@ -257,14 +267,24 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, { // printf("new ExPolygon offset\n"); // 1) Offset the outer contour. - const float delta_scaled = delta * float(CLIPPER_OFFSET_SCALE); +#ifdef CLIPPERUTILS_OFFSET_SCALE + float delta_scaled = delta * float(CLIPPER_OFFSET_SCALE); +#else // CLIPPERUTILS_OFFSET_SCALE + float delta_scaled = delta; +#endif // CLIPPERUTILS_OFFSET_SCALE ClipperLib::Paths contours; { ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath(expolygon.contour); +#ifdef CLIPPERUTILS_OFFSET_SCALE scaleClipperPolygon(input); +#endif // CLIPPERUTILS_OFFSET_SCALE ClipperLib::ClipperOffset co; if (joinType == jtRound) +#ifdef CLIPPERUTILS_OFFSET_SCALE co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE); +#else // CLIPPERUTILS_OFFSET_SCALE + co.ArcTolerance = miterLimit; +#endif // CLIPPERUTILS_OFFSET_SCALE else co.MiterLimit = miterLimit; co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); @@ -278,17 +298,23 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, holes.reserve(expolygon.holes.size()); for (Polygons::const_iterator it_hole = expolygon.holes.begin(); it_hole != expolygon.holes.end(); ++ it_hole) { ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath_reversed(*it_hole); +#ifdef CLIPPERUTILS_OFFSET_SCALE scaleClipperPolygon(input); +#endif // CLIPPERUTILS_OFFSET_SCALE ClipperLib::ClipperOffset co; if (joinType == jtRound) +#ifdef CLIPPERUTILS_OFFSET_SCALE co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE); +#else // CLIPPERUTILS_OFFSET_SCALE + co.ArcTolerance = miterLimit; +#endif // CLIPPERUTILS_OFFSET_SCALE else co.MiterLimit = miterLimit; co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); co.AddPath(input, joinType, ClipperLib::etClosedPolygon); ClipperLib::Paths out; co.Execute(out, - delta_scaled); - holes.insert(holes.end(), out.begin(), out.end()); + append(holes, std::move(out)); } } @@ -305,7 +331,9 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, } // 4) Unscale the output. +#ifdef CLIPPERUTILS_OFFSET_SCALE unscaleClipperPolygons(output); +#endif // CLIPPERUTILS_OFFSET_SCALE return output; } @@ -315,7 +343,11 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::Paths _offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) { - const float delta_scaled = delta * float(CLIPPER_OFFSET_SCALE); +#ifdef CLIPPERUTILS_OFFSET_SCALE + float delta_scaled = delta * float(CLIPPER_OFFSET_SCALE); +#else // CLIPPERUTILS_OFFSET_SCALE + float delta_scaled = delta; +#endif // CLIPPERUTILS_OFFSET_SCALE // Offsetted ExPolygons before they are united. ClipperLib::Paths contours_cummulative; contours_cummulative.reserve(expolygons.size()); @@ -327,10 +359,16 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygons &expolygons, const float delt ClipperLib::Paths contours; { ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath(it_expoly->contour); +#ifdef CLIPPERUTILS_OFFSET_SCALE scaleClipperPolygon(input); +#endif // CLIPPERUTILS_OFFSET_SCALE ClipperLib::ClipperOffset co; if (joinType == jtRound) +#ifdef CLIPPERUTILS_OFFSET_SCALE co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE); +#else // CLIPPERUTILS_OFFSET_SCALE + co.ArcTolerance = miterLimit; +#endif // CLIPPERUTILS_OFFSET_SCALE else co.MiterLimit = miterLimit; co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); @@ -351,10 +389,16 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygons &expolygons, const float delt { for (Polygons::const_iterator it_hole = it_expoly->holes.begin(); it_hole != it_expoly->holes.end(); ++ it_hole) { ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath_reversed(*it_hole); +#ifdef CLIPPERUTILS_OFFSET_SCALE scaleClipperPolygon(input); +#endif // CLIPPERUTILS_OFFSET_SCALE ClipperLib::ClipperOffset co; if (joinType == jtRound) +#ifdef CLIPPERUTILS_OFFSET_SCALE co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE); +#else // CLIPPERUTILS_OFFSET_SCALE + co.ArcTolerance = miterLimit; +#endif // CLIPPERUTILS_OFFSET_SCALE else co.MiterLimit = miterLimit; co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); @@ -413,8 +457,10 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygons &expolygons, const float delt output = std::move(contours_cummulative); } +#ifdef CLIPPERUTILS_OFFSET_SCALE // 4) Unscale the output. unscaleClipperPolygons(output); +#endif // CLIPPERUTILS_OFFSET_SCALE return output; } @@ -425,8 +471,10 @@ _offset2(const Polygons &polygons, const float delta1, const float delta2, // read input ClipperLib::Paths input = Slic3rMultiPoints_to_ClipperPaths(polygons); +#ifdef CLIPPERUTILS_OFFSET_SCALE // scale input scaleClipperPolygons(input); +#endif // CLIPPERUTILS_OFFSET_SCALE // prepare ClipperOffset object ClipperLib::ClipperOffset co; @@ -435,8 +483,13 @@ _offset2(const Polygons &polygons, const float delta1, const float delta2, } else { co.MiterLimit = miterLimit; } +#ifdef CLIPPERUTILS_OFFSET_SCALE float delta_scaled1 = delta1 * float(CLIPPER_OFFSET_SCALE); float delta_scaled2 = delta2 * float(CLIPPER_OFFSET_SCALE); +#else // CLIPPERUTILS_OFFSET_SCALE + float delta_scaled1 = delta1; + float delta_scaled2 = delta2; +#endif // CLIPPERUTILS_OFFSET_SCALE co.ShortestEdgeLength = double(std::max(std::abs(delta_scaled1), std::abs(delta_scaled2)) * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR); // perform first offset @@ -450,8 +503,10 @@ _offset2(const Polygons &polygons, const float delta1, const float delta2, ClipperLib::Paths retval; co.Execute(retval, delta_scaled2); +#ifdef CLIPPERUTILS_OFFSET_SCALE // unscale output unscaleClipperPolygons(retval); +#endif // CLIPPERUTILS_OFFSET_SCALE return retval; } @@ -789,8 +844,10 @@ void safety_offset(ClipperLib::Paths* paths) { CLIPPERUTILS_PROFILE_FUNC(); +#ifdef CLIPPERUTILS_OFFSET_SCALE // scale input scaleClipperPolygons(*paths); +#endif // CLIPPERUTILS_OFFSET_SCALE // perform offset (delta = scale 1e-05) ClipperLib::ClipperOffset co; @@ -816,7 +873,11 @@ void safety_offset(ClipperLib::Paths* paths) CLIPPERUTILS_PROFILE_BLOCK(safety_offset_Execute); // offset outside by 10um ClipperLib::Paths out_this; +#ifdef CLIPPERUTILS_OFFSET_SCALE co.Execute(out_this, ccw ? 10.f * float(CLIPPER_OFFSET_SCALE) : -10.f * float(CLIPPER_OFFSET_SCALE)); +#else // CLIPPERUTILS_OFFSET_SCALE + co.Execute(out_this, ccw ? 10.f : -10.f); +#endif // CLIPPERUTILS_OFFSET_SCALE if (! ccw) { // Reverse the resulting contours once again. for (ClipperLib::Paths::iterator it = out_this.begin(); it != out_this.end(); ++ it) @@ -830,8 +891,10 @@ void safety_offset(ClipperLib::Paths* paths) } *paths = std::move(out); +#ifdef CLIPPERUTILS_OFFSET_SCALE // unscale output unscaleClipperPolygons(*paths); +#endif // CLIPPERUTILS_OFFSET_SCALE } Polygons top_level_islands(const Slic3r::Polygons &polygons) @@ -925,8 +988,10 @@ ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::v // Add a new point to the output, scale by CLIPPER_OFFSET_SCALE and round to ClipperLib::cInt. auto add_offset_point = [&out](Vec2d pt) { +#ifdef CLIPPERUTILS_OFFSET_SCALE pt *= double(CLIPPER_OFFSET_SCALE); - pt += Vec2d(0.5 - (pt.x() < 0), 0.5 - (pt.y() < 0)); +#endif // CLIPPERUTILS_OFFSET_SCALE + pt += Vec2d(0.5 - (pt.x() < 0), 0.5 - (pt.y() < 0)); out.emplace_back(ClipperLib::cInt(pt.x()), ClipperLib::cInt(pt.y())); }; @@ -1075,8 +1140,10 @@ Polygons variable_offset_inner(const ExPolygon &expoly, const std::vector& ds : deltas) clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); } +#ifdef CLIPPERUTILS_OFFSET_SCALE // 4) Unscale the output. unscaleClipperPolygons(output); +#endif // CLIPPERUTILS_OFFSET_SCALE return ClipperPaths_to_Slic3rPolygons(output); } @@ -1152,7 +1221,9 @@ for (const std::vector& ds : deltas) #endif /* NDEBUG */ // 3) Subtract holes from the contours. +#ifdef CLIPPERUTILS_OFFSET_SCALE unscaleClipperPolygons(contours); +#endif // CLIPPERUTILS_OFFSET_SCALE ExPolygons output; if (holes.empty()) { output.reserve(contours.size()); @@ -1160,7 +1231,9 @@ for (const std::vector& ds : deltas) output.emplace_back(ClipperPath_to_Slic3rPolygon(path)); } else { ClipperLib::Clipper clipper; +#ifdef CLIPPERUTILS_OFFSET_SCALE unscaleClipperPolygons(holes); +#endif // CLIPPERUTILS_OFFSET_SCALE clipper.AddPaths(contours, ClipperLib::ptSubject, true); clipper.AddPaths(holes, ClipperLib::ptClip, true); ClipperLib::PolyTree polytree; @@ -1200,7 +1273,9 @@ ExPolygons variable_offset_inner_ex(const ExPolygon &expoly, const std::vector &deltas, double miter_limit); Polygons variable_offset_inner(const ExPolygon &expoly, const std::vector> &deltas, double miter_limit = 2.); Polygons variable_offset_outer(const ExPolygon &expoly, const std::vector> &deltas, double miter_limit = 2.); ExPolygons variable_offset_outer_ex(const ExPolygon &expoly, const std::vector> &deltas, double miter_limit = 2.); diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 9c9b5d3486..9aab435edd 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -1441,6 +1441,24 @@ private: class ConfigOptionDef { public: + enum class GUIType { + undefined, + // Open enums, integer value could be one of the enumerated values or something else. + i_enum_open, + // Open enums, float value could be one of the enumerated values or something else. + f_enum_open, + // Color picker, string value. + color, + // ??? + select_open, + // Currently unused. + slider, + // Static text + legend, + // Vector value, but edited as a single string. + one_string, + }; + // 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. @@ -1524,7 +1542,7 @@ public: // Usually empty. // Special values - "i_enum_open", "f_enum_open" to provide combo box for int or float selection, // "select_open" - to open a selection dialog (currently only a serial port selection). - std::string gui_type; + GUIType gui_type { GUIType::undefined }; // Usually empty. Otherwise "serialized" or "show_value" // The flags may be combined. // "serialized" - vector valued option is entered in a single edit field. Values are separated by a semicolon. @@ -1949,8 +1967,9 @@ public: int opt_int(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx); } // In ConfigManipulation::toggle_print_fff_options, it is called on option with type ConfigOptionEnumGeneric* and also ConfigOptionEnum*. + // Thus the virtual method getInt() is used to retrieve the enum value. template - ENUM opt_enum(const t_config_option_key &opt_key) const { return this->option>(opt_key)->value; } + ENUM opt_enum(const t_config_option_key &opt_key) const { return static_cast(this->option(opt_key)->getInt()); } bool opt_bool(const t_config_option_key &opt_key) const { return this->option(opt_key)->value != 0; } bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option(opt_key)->get_at(idx) != 0; } diff --git a/src/libslic3r/ElephantFootCompensation.cpp b/src/libslic3r/ElephantFootCompensation.cpp index f28d88f7e9..0895e16d68 100644 --- a/src/libslic3r/ElephantFootCompensation.cpp +++ b/src/libslic3r/ElephantFootCompensation.cpp @@ -621,7 +621,7 @@ ExPolygon elephant_foot_compensation(const ExPolygon &input_expoly, double min_c ExPolygon elephant_foot_compensation(const ExPolygon &input, const Flow &external_perimeter_flow, const double compensation) { // The contour shall be wide enough to apply the external perimeter plus compensation on both sides. - double min_contour_width = double(external_perimeter_flow.width + external_perimeter_flow.spacing()); + double min_contour_width = double(external_perimeter_flow.width() + external_perimeter_flow.spacing()); return elephant_foot_compensation(input, min_contour_width, compensation); } diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 46b3a3a1b9..73770bb185 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -217,6 +217,28 @@ inline Polygons to_polygons(const ExPolygons &src) return polygons; } +inline ConstPolygonPtrs to_polygon_ptrs(const ExPolygon &src) +{ + ConstPolygonPtrs polygons; + polygons.reserve(src.holes.size() + 1); + polygons.emplace_back(&src.contour); + for (const Polygon &hole : src.holes) + polygons.emplace_back(&hole); + return polygons; +} + +inline ConstPolygonPtrs to_polygon_ptrs(const ExPolygons &src) +{ + ConstPolygonPtrs polygons; + polygons.reserve(number_polygons(src)); + for (const ExPolygon &expoly : src) { + polygons.emplace_back(&expoly.contour); + for (const Polygon &hole : expoly.holes) + polygons.emplace_back(&hole); + } + return polygons; +} + inline Polygons to_polygons(ExPolygon &&src) { Polygons polygons; diff --git a/src/libslic3r/Execution/Execution.hpp b/src/libslic3r/Execution/Execution.hpp new file mode 100644 index 0000000000..e4bad9f237 --- /dev/null +++ b/src/libslic3r/Execution/Execution.hpp @@ -0,0 +1,132 @@ +#ifndef EXECUTION_HPP +#define EXECUTION_HPP + +#include +#include +#include +#include + +#include "libslic3r/libslic3r.h" + +namespace Slic3r { + +// Borrowed from C++20 +template +using remove_cvref_t = std::remove_reference_t>; + +// Override for valid execution policies +template struct IsExecutionPolicy_ : public std::false_type {}; + +template constexpr bool IsExecutionPolicy = + IsExecutionPolicy_>::value; + +template +using ExecutionPolicyOnly = std::enable_if_t, T>; + +namespace execution { + +// This struct needs to be specialized for each execution policy. +// See ExecutionSeq.hpp and ExecutionTBB.hpp for example. +template struct Traits {}; + +template using AsTraits = Traits>; + +// Each execution policy should declare two types of mutexes. A a spin lock and +// a blocking mutex. These types should satisfy the BasicLockable concept. +template using SpinningMutex = typename Traits::SpinningMutex; +template using BlockingMutex = typename Traits::BlockingMutex; + +// Query the available threads for concurrency. +template > +size_t max_concurrency(const EP &ep) +{ + return AsTraits::max_concurrency(ep); +} + +// foreach loop with the execution policy passed as argument. Granularity can +// be specified explicitly. max_concurrency() can be used for optimal results. +template> +void for_each(const EP &ep, It from, It to, Fn &&fn, size_t granularity = 1) +{ + AsTraits::for_each(ep, from, to, std::forward(fn), granularity); +} + +// A reduce operation with the execution policy passed as argument. +// mergefn has T(const T&, const T&) signature +// accessfn has T(I) signature if I is an integral type and +// T(const I::value_type &) if I is an iterator type. +template > +T reduce(const EP & ep, + I from, + I to, + const T & init, + MergeFn && mergefn, + AccessFn &&accessfn, + size_t granularity = 1) +{ + return AsTraits::reduce(ep, from, to, init, + std::forward(mergefn), + std::forward(accessfn), + granularity); +} + +// An overload of reduce method to be used with iterators as 'from' and 'to' +// arguments. Access functor is omitted here. +template > +T reduce(const EP &ep, + I from, + I to, + const T & init, + MergeFn &&mergefn, + size_t granularity = 1) +{ + return reduce( + ep, from, to, init, std::forward(mergefn), + [](const auto &i) { return i; }, granularity); +} + +template> +T accumulate(const EP & ep, + I from, + I to, + const T & init, + AccessFn &&accessfn, + size_t granularity = 1) +{ + return reduce(ep, from, to, init, std::plus{}, + std::forward(accessfn), granularity); +} + + +template > +T accumulate(const EP &ep, + I from, + I to, + const T & init, + size_t granularity = 1) +{ + return reduce( + ep, from, to, init, std::plus{}, [](const auto &i) { return i; }, + granularity); +} + +} // namespace execution_policy +} // namespace Slic3r + +#endif // EXECUTION_HPP diff --git a/src/libslic3r/Execution/ExecutionSeq.hpp b/src/libslic3r/Execution/ExecutionSeq.hpp new file mode 100644 index 0000000000..321d65631b --- /dev/null +++ b/src/libslic3r/Execution/ExecutionSeq.hpp @@ -0,0 +1,84 @@ +#ifndef EXECUTIONSEQ_HPP +#define EXECUTIONSEQ_HPP + +#ifdef PRUSASLICER_USE_EXECUTION_STD // Conflicts with our version of TBB +#include +#endif + +#include "Execution.hpp" + +namespace Slic3r { + +// Execution policy implementing dummy sequential algorithms +struct ExecutionSeq {}; + +template<> struct IsExecutionPolicy_ : public std::true_type {}; + +static constexpr ExecutionSeq ex_seq = {}; + +template struct IsSequentialEP_ { static constexpr bool value = false; }; + +template<> struct IsSequentialEP_: public std::true_type {}; +#ifdef PRUSASLICER_USE_EXECUTION_STD +template<> struct IsExecutionPolicy_: public std::true_type {}; +template<> struct IsSequentialEP_: public std::true_type {}; +#endif + +template +constexpr bool IsSequentialEP = IsSequentialEP_>::value; + +template +using SequentialEPOnly = std::enable_if_t, R>; + +template +struct execution::Traits> { +private: + struct _Mtx { inline void lock() {} inline void unlock() {} }; + + template + static IteratorOnly loop_(It from, It to, Fn &&fn) + { + for (auto it = from; it != to; ++it) fn(*it); + } + + template + static IntegerOnly loop_(I from, I to, Fn &&fn) + { + for (I i = from; i < to; ++i) fn(i); + } + +public: + using SpinningMutex = _Mtx; + using BlockingMutex = _Mtx; + + template + static void for_each(const EP &, + It from, + It to, + Fn &&fn, + size_t /* ignore granularity */ = 1) + { + loop_(from, to, std::forward(fn)); + } + + template + static T reduce(const EP &, + I from, + I to, + const T & init, + MergeFn &&mergefn, + AccessFn &&access, + size_t /*granularity*/ = 1 + ) + { + T acc = init; + loop_(from, to, [&](auto &i) { acc = mergefn(acc, access(i)); }); + return acc; + } + + static size_t max_concurrency(const EP &) { return 1; } +}; + +} // namespace Slic3r + +#endif // EXECUTIONSEQ_HPP diff --git a/src/libslic3r/Execution/ExecutionTBB.hpp b/src/libslic3r/Execution/ExecutionTBB.hpp new file mode 100644 index 0000000000..cf6373c466 --- /dev/null +++ b/src/libslic3r/Execution/ExecutionTBB.hpp @@ -0,0 +1,77 @@ +#ifndef EXECUTIONTBB_HPP +#define EXECUTIONTBB_HPP + +#include +#include +#include +#include +#include + +#include "Execution.hpp" + +namespace Slic3r { + +struct ExecutionTBB {}; +template<> struct IsExecutionPolicy_ : public std::true_type {}; + +// Execution policy using Intel TBB library under the hood. +static constexpr ExecutionTBB ex_tbb = {}; + +template<> struct execution::Traits { +private: + + template + static IteratorOnly loop_(const tbb::blocked_range &range, Fn &&fn) + { + for (auto &el : range) fn(el); + } + + template + static IntegerOnly loop_(const tbb::blocked_range &range, Fn &&fn) + { + for (I i = range.begin(); i < range.end(); ++i) fn(i); + } + +public: + using SpinningMutex = tbb::spin_mutex; + using BlockingMutex = tbb::mutex; + + template + static void for_each(const ExecutionTBB &, + It from, It to, Fn &&fn, size_t granularity) + { + tbb::parallel_for(tbb::blocked_range{from, to, granularity}, + [&fn](const auto &range) { + loop_(range, std::forward(fn)); + }); + } + + template + static T reduce(const ExecutionTBB &, + I from, + I to, + const T &init, + MergeFn &&mergefn, + AccessFn &&access, + size_t granularity = 1 + ) + { + return tbb::parallel_reduce( + tbb::blocked_range{from, to, granularity}, init, + [&](const auto &range, T subinit) { + T acc = subinit; + loop_(range, [&](auto &i) { acc = mergefn(acc, access(i)); }); + return acc; + }, + std::forward(mergefn)); + } + + static size_t max_concurrency(const ExecutionTBB &) + { + return tbb::this_task_arena::max_concurrency(); + } +}; + +} + +#endif // EXECUTIONTBB_HPP diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp index 390d107f27..3284bc39e4 100644 --- a/src/libslic3r/ExtrusionEntity.cpp +++ b/src/libslic3r/ExtrusionEntity.cpp @@ -52,7 +52,9 @@ void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scale { // Instantiating the Flow class to get the line spacing. // Don't know the nozzle diameter, setting to zero. It shall not matter it shall be optimized out by the compiler. - Flow flow(this->width, this->height, 0.f, is_bridge(this->role())); + bool bridge = is_bridge(this->role()); + assert(! bridge || this->width == this->height); + auto flow = bridge ? Flow::bridging_flow(this->width, 0.f) : Flow(this->width, this->height, 0.f); polygons_append(out, offset(this->polyline, 0.5f * float(flow.scaled_spacing()) + scaled_epsilon)); } diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index 2c43cd4af8..2f35083169 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -203,6 +203,8 @@ public: void reverse() override; const Point& first_point() const override { return this->paths.front().polyline.points.front(); } const Point& last_point() const override { return this->paths.back().polyline.points.back(); } + size_t size() const { return this->paths.size(); } + bool empty() const { return this->paths.empty(); } double length() const override; ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); } // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index ee493ca9cb..579259a5fc 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -28,6 +28,8 @@ struct SurfaceFillParams // coordf_t overlap = 0.; // Angle as provided by the region config, in radians. float angle = 0.f; + // Is bridging used for this fill? Bridging parameters may be used even if this->flow.bridge() is not set. + bool bridge; // Non-negative for a bridge. float bridge_angle = 0.f; @@ -42,7 +44,7 @@ struct SurfaceFillParams // width, height of extrusion, nozzle diameter, is bridge // For the output, for fill generator. - Flow flow = Flow(0.f, 0.f, 0.f, false); + Flow flow; // For the output ExtrusionRole extrusion_role = ExtrusionRole(0); @@ -70,21 +72,22 @@ struct SurfaceFillParams // RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, dont_adjust); RETURN_COMPARE_NON_EQUAL(anchor_length); RETURN_COMPARE_NON_EQUAL(anchor_length_max); - RETURN_COMPARE_NON_EQUAL(flow.width); - RETURN_COMPARE_NON_EQUAL(flow.height); - RETURN_COMPARE_NON_EQUAL(flow.nozzle_diameter); - RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, flow.bridge); + RETURN_COMPARE_NON_EQUAL(flow.width()); + RETURN_COMPARE_NON_EQUAL(flow.height()); + RETURN_COMPARE_NON_EQUAL(flow.nozzle_diameter()); + RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, bridge); RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, extrusion_role); return false; } bool operator==(const SurfaceFillParams &rhs) const { return this->extruder == rhs.extruder && - this->pattern == rhs.pattern && this->pattern == rhs.pattern && this->spacing == rhs.spacing && // this->overlap == rhs.overlap && this->angle == rhs.angle && + this->bridge == rhs.bridge && +// this->bridge_angle == rhs.bridge_angle && this->density == rhs.density && // this->dont_adjust == rhs.dont_adjust && this->anchor_length == rhs.anchor_length && @@ -128,6 +131,7 @@ std::vector group_fills(const Layer &layer) if (surface.is_solid()) { params.density = 100.f; + //FIXME for non-thick bridges, shall we allow a bottom surface pattern? params.pattern = (surface.is_external() && ! is_bridge) ? (surface.is_top() ? region_config.top_fill_pattern.value : region_config.bottom_fill_pattern.value) : region_config.top_fill_pattern == ipMonotonic ? ipMonotonic : ipRectilinear; @@ -143,17 +147,13 @@ std::vector group_fills(const Layer &layer) params.bridge_angle = float(surface.bridge_angle); params.angle = float(Geometry::deg2rad(region_config.fill_angle.value)); - // calculate the actual flow we'll be using for this infill - params.flow = layerm.region()->flow( - extrusion_role, - (surface.thickness == -1) ? layer.height : surface.thickness, // extrusion height - is_bridge || Fill::use_bridge_flow(params.pattern), // bridge flow? - layer.id() == 0, // first layer? - -1, // auto width - *layer.object() - ); - - // Calculate flow spacing for infill pattern generation. + // Calculate the actual flow we'll be using for this infill. + params.bridge = is_bridge || Fill::use_bridge_flow(params.pattern); + params.flow = params.bridge ? + layerm.bridging_flow(extrusion_role) : + layerm.flow(extrusion_role, (surface.thickness == -1) ? layer.height : surface.thickness); + + // Calculate flow spacing for infill pattern generation. if (surface.is_solid() || is_bridge) { params.spacing = params.flow.spacing(); // Don't limit anchor length for solid or bridging infill. @@ -164,14 +164,7 @@ std::vector group_fills(const Layer &layer) // for all layers, for avoiding the ugly effect of // misaligned infill on first layer because of different extrusion width and // layer height - params.spacing = layerm.region()->flow( - frInfill, - layer.object()->config().layer_height.value, // TODO: handle infill_every_layers? - false, // no bridge - false, // no first layer - -1, // auto width - *layer.object() - ).spacing(); + params.spacing = layerm.flow(frInfill, layer.object()->config().layer_height).spacing(); // Anchor a sparse infill to inner perimeters with the following anchor length: params.anchor_length = float(region_config.infill_anchor); if (region_config.infill_anchor.percent) @@ -278,7 +271,7 @@ std::vector group_fills(const Layer &layer) region_id = region_some_infill; const LayerRegion& layerm = *layer.regions()[region_id]; for (SurfaceFill &surface_fill : surface_fills) - if (surface_fill.surface.surface_type == stInternalSolid && std::abs(layer.height - surface_fill.params.flow.height) < EPSILON) { + if (surface_fill.surface.surface_type == stInternalSolid && std::abs(layer.height - surface_fill.params.flow.height()) < EPSILON) { internal_solid_fill = &surface_fill; break; } @@ -290,14 +283,7 @@ std::vector group_fills(const Layer &layer) params.extrusion_role = erInternalInfill; params.angle = float(Geometry::deg2rad(layerm.region()->config().fill_angle.value)); // calculate the actual flow we'll be using for this infill - params.flow = layerm.region()->flow( - frSolidInfill, - layer.height, // extrusion height - false, // bridge flow? - layer.id() == 0, // first layer? - -1, // auto width - *layer.object() - ); + params.flow = layerm.flow(frSolidInfill); params.spacing = params.flow.spacing(); surface_fills.emplace_back(params); surface_fills.back().surface.surface_type = stInternalSolid; @@ -365,9 +351,9 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree; // calculate flow spacing for infill pattern generation - bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.flow.bridge; + bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.bridge; double link_max_length = 0.; - if (! surface_fill.params.flow.bridge) { + if (! surface_fill.params.bridge) { #if 0 link_max_length = layerm.region()->config().get_abs_value(surface.is_external() ? "external_fill_link_max_length" : "fill_link_max_length", flow.spacing()); // printf("flow spacing: %f, is_external: %d, link_max_length: %lf\n", flow.spacing(), int(surface.is_external()), link_max_length); @@ -380,7 +366,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: // Maximum length of the perimeter segment linking two infill lines. 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 = coord_t(scale_(surface_fill.params.flow.nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER); + f->loop_clipping = coord_t(scale_(surface_fill.params.flow.nozzle_diameter()) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER); // apply half spacing using this flow's own spacing and generate infill FillParams params; @@ -402,15 +388,15 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: // calculate actual flow from spacing (which might have been adjusted by the infill // pattern generator) double flow_mm3_per_mm = surface_fill.params.flow.mm3_per_mm(); - double flow_width = surface_fill.params.flow.width; + double flow_width = surface_fill.params.flow.width(); if (using_internal_flow) { // if we used the internal flow we're not doing a solid infill // so we can safely ignore the slight variation that might have // been applied to f->spacing } else { - Flow new_flow = Flow::new_from_spacing(float(f->spacing), surface_fill.params.flow.nozzle_diameter, surface_fill.params.flow.height, surface_fill.params.flow.bridge); + Flow new_flow = surface_fill.params.flow.with_spacing(float(f->spacing)); flow_mm3_per_mm = new_flow.mm3_per_mm(); - flow_width = new_flow.width; + flow_width = new_flow.width(); } // Save into layer. ExtrusionEntityCollection* eec = nullptr; @@ -420,7 +406,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: extrusion_entities_append_paths( eec->entities, std::move(polylines), surface_fill.params.extrusion_role, - flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height); + flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height()); } } } @@ -618,9 +604,9 @@ void Layer::make_ironing() fill.spacing = ironing_params.line_spacing; fill.angle = float(ironing_params.angle + 0.25 * M_PI); fill.link_max_length = (coord_t)scale_(3. * fill.spacing); - double height = ironing_params.height * fill.spacing / nozzle_dmr; - Flow flow = Flow::new_from_spacing(float(nozzle_dmr), 0., float(height), false); - double flow_mm3_per_mm = flow.mm3_per_mm(); + double extrusion_height = ironing_params.height * fill.spacing / nozzle_dmr; + float extrusion_width = Flow::rounded_rectangle_extrusion_width_from_spacing(float(nozzle_dmr), float(extrusion_height)); + double flow_mm3_per_mm = nozzle_dmr * extrusion_height; Surface surface_fill(stTop, ExPolygon()); for (ExPolygon &expoly : ironing_areas) { surface_fill.expolygon = std::move(expoly); @@ -638,7 +624,7 @@ void Layer::make_ironing() extrusion_entities_append_paths( eec->entities, std::move(polylines), erIroning, - flow_mm3_per_mm, float(flow.width), float(height)); + flow_mm3_per_mm, extrusion_width, float(extrusion_height)); } } } diff --git a/src/libslic3r/Fill/Fill3DHoneycomb.cpp b/src/libslic3r/Fill/Fill3DHoneycomb.cpp index 2ddca7fe4a..95c26fbad4 100644 --- a/src/libslic3r/Fill/Fill3DHoneycomb.cpp +++ b/src/libslic3r/Fill/Fill3DHoneycomb.cpp @@ -147,7 +147,7 @@ void Fill3DHoneycomb::_fill_surface_single( // align bounding box to a multiple of our honeycomb grid module // (a module is 2*$distance since one $distance half-module is // growing while the other $distance half-module is shrinking) - bb.merge(_align_to_grid(bb.min, Point(2*distance, 2*distance))); + bb.merge(align_to_grid(bb.min, Point(2*distance, 2*distance))); // generate pattern Polylines polylines = makeGrid( diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index b4afd25917..6d1d94ff8c 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -19,6 +19,8 @@ #include "FillRectilinear.hpp" #include "FillAdaptive.hpp" +// #define INFILL_DEBUG_OUTPUT + namespace Slic3r { Fill* Fill::new_from_type(const InfillPattern type) @@ -41,6 +43,7 @@ Fill* Fill::new_from_type(const InfillPattern type) case ipOctagramSpiral: return new FillOctagramSpiral(); case ipAdaptiveCubic: return new FillAdaptive::Filler(); case ipSupportCubic: return new FillAdaptive::Filler(); + case ipSupportBase: return new FillSupportBase(); default: throw Slic3r::InvalidArgument("unknown type"); } } @@ -253,15 +256,15 @@ std::pair path_lengths_along_contour(const ContourIntersectionPo } // Add contour points from interval (idx_start, idx_end> to polyline. -static inline void take_cw_full(Polyline &pl, const Points& contour, size_t idx_start, size_t idx_end) +static inline void take_cw_full(Polyline &pl, const Points &contour, size_t idx_start, size_t idx_end) { assert(! pl.empty() && pl.points.back() == contour[idx_start]); - size_t i = (idx_end == 0) ? contour.size() - 1 : idx_start - 1; + size_t i = (idx_start == 0) ? contour.size() - 1 : idx_start - 1; while (i != idx_end) { pl.points.emplace_back(contour[i]); if (i == 0) i = contour.size(); - --i; + -- i; } pl.points.emplace_back(contour[i]); } @@ -612,13 +615,13 @@ static inline bool line_rounded_thick_segment_collision( }; // Intersections with the inflated segment end points. - auto ray_circle_intersection_interval_extend = [&extend_interval, &line_v0](const Vec2d &segment_pt, const double offset2, const Vec2d &line_pt, const Vec2d &line_vec) { + auto ray_circle_intersection_interval_extend = [&extend_interval](const Vec2d &segment_pt, const double offset2, const Vec2d &line_pt, const Vec2d &line_vec) { std::pair pts; Vec2d p0 = line_pt - segment_pt; - double c = - line_pt.dot(p0); - if (Geometry::ray_circle_intersections_r2_lv2_c(offset2, line_vec.x(), line_vec.y(), line_vec.squaredNorm(), c, pts)) { - double tmin = (pts.first - p0).dot(line_v0); - double tmax = (pts.second - p0).dot(line_v0); + double lv2 = line_vec.squaredNorm(); + if (Geometry::ray_circle_intersections_r2_lv2_c(offset2, line_vec.y(), - line_vec.x(), lv2, - line_vec.y() * p0.x() + line_vec.x() * p0.y(), pts)) { + double tmin = (pts.first - p0).dot(line_vec) / lv2; + double tmax = (pts.second - p0).dot(line_vec) / lv2; if (tmin > tmax) std::swap(tmin, tmax); tmin = std::max(tmin, 0.); @@ -705,8 +708,6 @@ static inline bool cyclic_interval_inside_interval(double outer_low, double oute } #endif // NDEBUG -// #define INFILL_DEBUG_OUTPUT - #ifdef INFILL_DEBUG_OUTPUT static void export_infill_to_svg( // Boundary contour, along which the perimeter extrusions will be drawn. @@ -1099,61 +1100,216 @@ void Fill::connect_infill(Polylines &&infill_ordered, const Polygons &boundary_s connect_infill(std::move(infill_ordered), polygons_src, bbox, polylines_out, spacing, params); } -void Fill::connect_infill(Polylines &&infill_ordered, const std::vector &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms) +static constexpr auto boundary_idx_unconnected = std::numeric_limits::max(); + +struct BoundaryInfillGraph { - assert(! infill_ordered.empty()); - assert(params.anchor_length >= 0.); - assert(params.anchor_length_max >= 0.01f); - assert(params.anchor_length_max >= params.anchor_length); - const double anchor_length = scale_(params.anchor_length); - const double anchor_length_max = scale_(params.anchor_length_max); + std::vector boundary; + std::vector> boundary_params; + std::vector map_infill_end_point_to_boundary; -#if 0 - append(polylines_out, infill_ordered); - return; -#endif + const Point& point(const ContourIntersectionPoint &cp) const { + assert(cp.contour_idx != size_t(-1)); + assert(cp.point_idx != size_t(-1)); + return this->boundary[cp.contour_idx][cp.point_idx]; + } - // 1) Add the end points of infill_ordered to boundary_src. - std::vector boundary; - std::vector> boundary_params; - boundary.assign(boundary_src.size(), Points()); - boundary_params.assign(boundary_src.size(), std::vector()); - // Mapping the infill_ordered end point to a (contour, point) of boundary. - static constexpr auto boundary_idx_unconnected = std::numeric_limits::max(); - std::vector map_infill_end_point_to_boundary(infill_ordered.size() * 2, ContourIntersectionPoint{ boundary_idx_unconnected, boundary_idx_unconnected }); - { - // Project the infill_ordered end points onto boundary_src. - std::vector> intersection_points; - { - EdgeGrid::Grid grid; - grid.set_bbox(bbox.inflated(SCALED_EPSILON)); - grid.create(boundary_src, coord_t(scale_(10.))); - intersection_points.reserve(infill_ordered.size() * 2); - for (const Polyline &pl : infill_ordered) - for (const Point *pt : { &pl.points.front(), &pl.points.back() }) { - EdgeGrid::Grid::ClosestPointResult cp = grid.closest_point_signed_distance(*pt, coord_t(SCALED_EPSILON)); - if (cp.valid()) { - // The infill end point shall lie on the contour. - assert(cp.distance <= 3.); - intersection_points.emplace_back(cp, (&pl - infill_ordered.data()) * 2 + (pt == &pl.points.front() ? 0 : 1)); - } - } - std::sort(intersection_points.begin(), intersection_points.end(), [](const std::pair &cp1, const std::pair &cp2) { - return cp1.first.contour_idx < cp2.first.contour_idx || - (cp1.first.contour_idx == cp2.first.contour_idx && - (cp1.first.start_point_idx < cp2.first.start_point_idx || - (cp1.first.start_point_idx == cp2.first.start_point_idx && cp1.first.t < cp2.first.t))); - }); - } - auto it = intersection_points.begin(); - auto it_end = intersection_points.end(); - std::vector> boundary_intersection_points(boundary.size(), std::vector()); - for (size_t idx_contour = 0; idx_contour < boundary_src.size(); ++ idx_contour) { + const Point& infill_end_point(size_t infill_end_point_idx) const { + return this->point(this->map_infill_end_point_to_boundary[infill_end_point_idx]); + } + + const Point interpolate_contour_point(const ContourIntersectionPoint &cp, double param) { + const Points &contour = this->boundary[cp.contour_idx]; + const std::vector &contour_params = this->boundary_params[cp.contour_idx]; + // Find the start of a contour segment with param. + auto it = std::lower_bound(contour_params.begin(), contour_params.end(), param); + if (*it != param) { + assert(it != contour_params.begin()); + -- it; + } + size_t i = it - contour_params.begin(); + if (i == contour.size()) + i = 0; + double t1 = contour_params[i]; + double t2 = next_value_modulo(i, contour_params); + return lerp(contour[i], next_value_modulo(i, contour), (param - t1) / (t2 - t1)); + } + + enum Direction { + Left, + Right, + Up, + Down, + Taken, + }; + + static Direction dir(const Point &p1, const Point &p2) { + return p1.x() == p2.x() ? + (p1.y() < p2.y() ? Up : Down) : + (p1.x() < p2.x() ? Right : Left); + } + + const Direction dir_prev(const ContourIntersectionPoint &cp) const { + assert(cp.prev_on_contour); + return cp.could_take_prev() ? + dir(this->point(cp), this->point(*cp.prev_on_contour)) : + Taken; + } + + const Direction dir_next(const ContourIntersectionPoint &cp) const { + assert(cp.next_on_contour); + return cp.could_take_next() ? + dir(this->point(cp), this->point(*cp.next_on_contour)) : + Taken; + } + + bool first(const ContourIntersectionPoint &cp) const { + return ((&cp - this->map_infill_end_point_to_boundary.data()) & 1) == 0; + } + + const ContourIntersectionPoint& other(const ContourIntersectionPoint &cp) const { + return this->map_infill_end_point_to_boundary[((&cp - this->map_infill_end_point_to_boundary.data()) ^ 1)]; + } + + ContourIntersectionPoint& other(const ContourIntersectionPoint &cp) { + return this->map_infill_end_point_to_boundary[((&cp - this->map_infill_end_point_to_boundary.data()) ^ 1)]; + } + + bool prev_vertical(const ContourIntersectionPoint &cp) const { + return this->point(cp).x() == this->point(*cp.prev_on_contour).x(); + } + + bool next_vertical(const ContourIntersectionPoint &cp) const { + return this->point(cp).x() == this->point(*cp.next_on_contour).x(); + } + +}; + + +// After mark_boundary_segments_touching_infill() marks boundary segments overlapping trimmed infill lines, +// there are possibly some very short boundary segments unmarked, but overlapping the untrimmed infill lines fully +// Mark those short boundary segments. +static inline void mark_boundary_segments_overlapping_infill( + BoundaryInfillGraph &graph, + // Infill lines, either completely inside the boundary, or touching the boundary. + const Polylines &infill, + // Spacing (width) of the infill lines. + const double spacing) +{ + struct Linef { Vec2d a; Vec2d b; }; + + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + const Points &contour = graph.boundary[cp.contour_idx]; + const std::vector &contour_params = graph.boundary_params[cp.contour_idx]; + const Polyline &infill_polyline = infill[(&cp - graph.map_infill_end_point_to_boundary.data()) / 2]; + const double radius = 0.5 * (spacing + SCALED_EPSILON); + assert(infill_polyline.size() == 2); + const Linef infill_line { infill_polyline.points.front().cast(), infill_polyline.points.back().cast() }; + if (cp.could_take_next()) { + bool inside = true; + for (size_t i = cp.point_idx; i != cp.next_on_contour->point_idx; ) { + size_t j = next_idx_modulo(i, contour); + const Vec2d seg_pt2 = contour[j].cast(); + if (line_alg::distance_to_squared(infill_line, seg_pt2) < radius * radius) { + // The segment is completely inside. + } else { + std::pair interval; + line_rounded_thick_segment_collision(contour[i].cast(), seg_pt2, infill_line.a, infill_line.b, radius, interval); + assert(interval.first == 0.); + double len_out = closed_contour_distance_ccw(contour_params[cp.point_idx], contour_params[i], contour_params.back()) + interval.second; + if (len_out < cp.contour_not_taken_length_next) { + // Leaving the infill line region before exiting cp.contour_not_taken_length_next, + // thus at least some of the contour is outside and we will extrude this segment. + inside = false; + break; + } + } + if (closed_contour_distance_ccw(contour_params[cp.point_idx], contour_params[j], contour_params.back()) >= cp.contour_not_taken_length_next) + break; + i = j; + } + if (inside) { + if (! cp.next_trimmed) + // The arc from cp to cp.next_on_contour was not trimmed yet, however it is completely overlapping the infill line. + cp.next_on_contour->trim_prev(0); + cp.trim_next(0); + } + } else + cp.trim_next(0); + if (cp.could_take_prev()) { + bool inside = true; + for (size_t i = cp.point_idx; i != cp.prev_on_contour->point_idx; ) { + size_t j = prev_idx_modulo(i, contour); + const Vec2d seg_pt2 = contour[j].cast(); + // Distance of the second segment line from the infill line. + if (line_alg::distance_to_squared(infill_line, seg_pt2) < radius * radius) { + // The segment is completely inside. + } else { + std::pair interval; + line_rounded_thick_segment_collision(contour[i].cast(), seg_pt2, infill_line.a, infill_line.b, radius, interval); + assert(interval.first == 0.); + double len_out = closed_contour_distance_cw(contour_params[cp.point_idx], contour_params[i], contour_params.back()) + interval.second; + if (len_out < cp.contour_not_taken_length_prev) { + // Leaving the infill line region before exiting cp.contour_not_taken_length_next, + // thus at least some of the contour is outside and we will extrude this segment. + inside = false; + break; + } + } + if (closed_contour_distance_cw(contour_params[cp.point_idx], contour_params[j], contour_params.back()) >= cp.contour_not_taken_length_prev) + break; + i = j; + } + if (inside) { + if (! cp.prev_trimmed) + // The arc from cp to cp.prev_on_contour was not trimmed yet, however it is completely overlapping the infill line. + cp.prev_on_contour->trim_next(0); + cp.trim_prev(0); + } + } else + cp.trim_prev(0); + } +} + +BoundaryInfillGraph create_boundary_infill_graph(const Polylines &infill_ordered, const std::vector &boundary_src, const BoundingBox &bbox, const double spacing) +{ + BoundaryInfillGraph out; + out.boundary.assign(boundary_src.size(), Points()); + out.boundary_params.assign(boundary_src.size(), std::vector()); + out.map_infill_end_point_to_boundary.assign(infill_ordered.size() * 2, ContourIntersectionPoint{ boundary_idx_unconnected, boundary_idx_unconnected }); + { + // Project the infill_ordered end points onto boundary_src. + std::vector> intersection_points; + { + EdgeGrid::Grid grid; + grid.set_bbox(bbox.inflated(SCALED_EPSILON)); + grid.create(boundary_src, coord_t(scale_(10.))); + intersection_points.reserve(infill_ordered.size() * 2); + for (const Polyline &pl : infill_ordered) + for (const Point *pt : { &pl.points.front(), &pl.points.back() }) { + EdgeGrid::Grid::ClosestPointResult cp = grid.closest_point_signed_distance(*pt, coord_t(SCALED_EPSILON)); + if (cp.valid()) { + // The infill end point shall lie on the contour. + assert(cp.distance <= 3.); + intersection_points.emplace_back(cp, (&pl - infill_ordered.data()) * 2 + (pt == &pl.points.front() ? 0 : 1)); + } + } + std::sort(intersection_points.begin(), intersection_points.end(), [](const std::pair &cp1, const std::pair &cp2) { + return cp1.first.contour_idx < cp2.first.contour_idx || + (cp1.first.contour_idx == cp2.first.contour_idx && + (cp1.first.start_point_idx < cp2.first.start_point_idx || + (cp1.first.start_point_idx == cp2.first.start_point_idx && cp1.first.t < cp2.first.t))); + }); + } + auto it = intersection_points.begin(); + auto it_end = intersection_points.end(); + std::vector> boundary_intersection_points(out.boundary.size(), std::vector()); + for (size_t idx_contour = 0; idx_contour < boundary_src.size(); ++ idx_contour) { // Copy contour_src to contour_dst while adding intersection points. // Map infill end points map_infill_end_point_to_boundary to the newly inserted boundary points of contour_dst. // chain the points of map_infill_end_point_to_boundary along their respective contours. - const Polygon &contour_src = *boundary_src[idx_contour]; - Points &contour_dst = boundary[idx_contour]; + const Polygon &contour_src = *boundary_src[idx_contour]; + Points &contour_dst = out.boundary[idx_contour]; std::vector &contour_intersection_points = boundary_intersection_points[idx_contour]; ContourIntersectionPoint *pfirst = nullptr; ContourIntersectionPoint *pprev = nullptr; @@ -1164,18 +1320,18 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectorfirst.contour_idx == idx_contour && it->first.start_point_idx == idx_point; ++ it) { - // Add these points to the destination contour. + contour_dst.emplace_back(ipt); + for (; it != it_end && it->first.contour_idx == idx_contour && it->first.start_point_idx == idx_point; ++ it) { + // Add these points to the destination contour. const Polyline &infill_line = infill_ordered[it->second / 2]; const Point &pt = (it->second & 1) ? infill_line.points.back() : infill_line.points.front(); //#ifndef NDEBUG // { -// const Vec2d pt1 = ipt.cast(); -// const Vec2d pt2 = (idx_point + 1 == contour_src.size() ? contour_src.points.front() : contour_src.points[idx_point + 1]).cast(); +// const Vec2d pt1 = ipt.cast(); +// const Vec2d pt2 = (idx_point + 1 == contour_src.size() ? contour_src.points.front() : contour_src.points[idx_point + 1]).cast(); // const Vec2d ptx = lerp(pt1, pt2, it->first.t); // assert(std::abs(ptx.x() - pt.x()) < SCALED_EPSILON); // assert(std::abs(ptx.y() - pt.y()) < SCALED_EPSILON); @@ -1187,8 +1343,8 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectorsecond] = ContourIntersectionPoint{ idx_contour, idx_tjoint_pt }; - ContourIntersectionPoint *pthis = &map_infill_end_point_to_boundary[it->second]; + out.map_infill_end_point_to_boundary[it->second] = ContourIntersectionPoint{ /* it->second, */ idx_contour, idx_tjoint_pt }; + ContourIntersectionPoint *pthis = &out.map_infill_end_point_to_boundary[it->second]; if (pprev) { pprev->next_on_contour = pthis; pthis->prev_on_contour = pprev; @@ -1196,15 +1352,15 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectornext_on_contour = pfirst; pfirst->prev_on_contour = pprev; } - } - // Parametrize the new boundary with the intersection points inserted. - std::vector &contour_params = boundary_params[idx_contour]; - contour_params.assign(contour_dst.size() + 1, 0.); + } + // Parametrize the new boundary with the intersection points inserted. + std::vector &contour_params = out.boundary_params[idx_contour]; + contour_params.assign(contour_dst.size() + 1, 0.); for (size_t i = 1; i < contour_dst.size(); ++i) { contour_params[i] = contour_params[i - 1] + (contour_dst[i].cast() - contour_dst[i - 1].cast()).norm(); assert(contour_params[i] > contour_params[i - 1]); @@ -1225,18 +1381,18 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectorcontour_not_taken_length_prev = closed_contour_distance_ccw(ip->prev_on_contour->param, ip->param, contour_length); ip->contour_not_taken_length_next = closed_contour_distance_ccw(ip->param, ip->next_on_contour->param, contour_length); } - } + } - assert(boundary.size() == boundary_src.size()); + assert(out.boundary.size() == boundary_src.size()); #if 0 // Adaptive Cubic Infill produces infill lines, which not always end at the outer boundary. - assert(std::all_of(map_infill_end_point_to_boundary.begin(), map_infill_end_point_to_boundary.end(), - [&boundary](const ContourIntersectionPoint &contour_point) { - return contour_point.contour_idx < boundary.size() && contour_point.point_idx < boundary[contour_point.contour_idx].size(); - })); + assert(std::all_of(out.map_infill_end_point_to_boundary.begin(), out.map_infill_end_point_to_boundary.end(), + [&out.boundary](const ContourIntersectionPoint &contour_point) { + return contour_point.contour_idx < out.boundary.size() && contour_point.point_idx < out.boundary[contour_point.contour_idx].size(); + })); #endif - // Mark the points and segments of split boundary as consumed if they are very close to some of the infill line. + // Mark the points and segments of split out.boundary as consumed if they are very close to some of the infill line. { // @supermerill used 2. * scale_(spacing) const double clip_distance = 1.7 * scale_(spacing); @@ -1244,37 +1400,31 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector merged_with(infill_ordered.size()); + return out; +} + +void Fill::connect_infill(Polylines &&infill_ordered, const std::vector &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms) +{ + assert(! infill_ordered.empty()); + assert(params.anchor_length >= 0.); + assert(params.anchor_length_max >= 0.01f); + assert(params.anchor_length_max >= params.anchor_length); + const double anchor_length = scale_(params.anchor_length); + const double anchor_length_max = scale_(params.anchor_length_max); + +#if 0 + append(polylines_out, infill_ordered); + return; +#endif + + BoundaryInfillGraph graph = create_boundary_infill_graph(infill_ordered, boundary_src, bbox, spacing); + + std::vector merged_with(infill_ordered.size()); std::iota(merged_with.begin(), merged_with.end(), 0); - struct ConnectionCost { - ConnectionCost(size_t idx_first, double cost, bool reversed) : idx_first(idx_first), cost(cost), reversed(reversed) {} - size_t idx_first; - double cost; - bool reversed; - }; - std::vector connections_sorted; - connections_sorted.reserve(infill_ordered.size() * 2 - 2); - for (size_t idx_chain = 1; idx_chain < infill_ordered.size(); ++ idx_chain) { - const ContourIntersectionPoint *cp1 = &map_infill_end_point_to_boundary[(idx_chain - 1) * 2 + 1]; - const ContourIntersectionPoint *cp2 = &map_infill_end_point_to_boundary[idx_chain * 2]; - if (cp1->contour_idx != boundary_idx_unconnected && cp1->contour_idx == cp2->contour_idx) { - // End points on the same contour. Try to connect them. - std::pair len = path_lengths_along_contour(cp1, cp2, boundary_params[cp1->contour_idx].back()); - if (len.first < length_max) - connections_sorted.emplace_back(idx_chain - 1, len.first, false); - if (len.second < length_max) - connections_sorted.emplace_back(idx_chain - 1, len.second, true); - } - } - std::sort(connections_sorted.begin(), connections_sorted.end(), [](const ConnectionCost& l, const ConnectionCost& r) { return l.cost < r.cost; }); auto get_and_update_merged_with = [&merged_with](size_t polyline_idx) -> size_t { for (size_t last = polyline_idx;;) { @@ -1293,9 +1443,35 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector connections_sorted; + connections_sorted.reserve(infill_ordered.size() * 2 - 2); + for (size_t idx_chain = 1; idx_chain < infill_ordered.size(); ++ idx_chain) { + const ContourIntersectionPoint *cp1 = &graph.map_infill_end_point_to_boundary[(idx_chain - 1) * 2 + 1]; + const ContourIntersectionPoint *cp2 = &graph.map_infill_end_point_to_boundary[idx_chain * 2]; + if (cp1->contour_idx != boundary_idx_unconnected && cp1->contour_idx == cp2->contour_idx) { + // End points on the same contour. Try to connect them. + std::pair len = path_lengths_along_contour(cp1, cp2, graph.boundary_params[cp1->contour_idx].back()); + if (len.first < length_max) + connections_sorted.emplace_back(idx_chain - 1, len.first, false); + if (len.second < length_max) + connections_sorted.emplace_back(idx_chain - 1, len.second, true); + } + } + std::sort(connections_sorted.begin(), connections_sorted.end(), [](const ConnectionCost& l, const ConnectionCost& r) { return l.cost < r.cost; }); + for (ConnectionCost &connection_cost : connections_sorted) { - ContourIntersectionPoint *cp1 = &map_infill_end_point_to_boundary[connection_cost.idx_first * 2 + 1]; - ContourIntersectionPoint *cp2 = &map_infill_end_point_to_boundary[(connection_cost.idx_first + 1) * 2]; + ContourIntersectionPoint *cp1 = &graph.map_infill_end_point_to_boundary[connection_cost.idx_first * 2 + 1]; + ContourIntersectionPoint *cp2 = &graph.map_infill_end_point_to_boundary[(connection_cost.idx_first + 1) * 2]; assert(cp1 != cp2); assert(cp1->contour_idx == cp2->contour_idx && cp1->contour_idx != boundary_idx_unconnected); if (cp1->consumed || cp2->consumed) @@ -1306,7 +1482,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectorparam, cp_high->param, boundary_params[cp1->contour_idx].back())) < SCALED_EPSILON); + assert(std::abs(length - closed_contour_distance_ccw(cp_low->param, cp_high->param, graph.boundary_params[cp1->contour_idx].back())) < SCALED_EPSILON); could_connect = ! cp_low->next_trimmed && ! cp_high->prev_trimmed; if (could_connect && cp_low->next_on_contour != cp_high) { // Other end of cp1, may or may not be on the same contour as cp1. @@ -1329,14 +1505,14 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectorcontour_idx], cp1, cp2, connection_cost.reversed); + take(infill_ordered[idx_first], infill_ordered[idx_second], graph.boundary[cp1->contour_idx], cp1, cp2, connection_cost.reversed); // Mark the second polygon as merged with the first one. merged_with[idx_second] = merged_with[idx_first]; infill_ordered[idx_second].points.clear(); } else { // Try to connect cp1 resp. cp2 with a piece of perimeter line. - take_limited(infill_ordered[idx_first], boundary[cp1->contour_idx], boundary_params[cp1->contour_idx], cp1, cp2, connection_cost.reversed, anchor_length, line_half_width); - take_limited(infill_ordered[idx_second], boundary[cp1->contour_idx], boundary_params[cp1->contour_idx], cp2, cp1, ! connection_cost.reversed, anchor_length, line_half_width); + take_limited(infill_ordered[idx_first], graph.boundary[cp1->contour_idx], graph.boundary_params[cp1->contour_idx], cp1, cp2, connection_cost.reversed, anchor_length, line_half_width); + take_limited(infill_ordered[idx_second], graph.boundary[cp1->contour_idx], graph.boundary_params[cp1->contour_idx], cp2, cp1, ! connection_cost.reversed, anchor_length, line_half_width); } } #endif @@ -1346,10 +1522,10 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector arches; - arches.reserve(map_infill_end_point_to_boundary.size()); - for (ContourIntersectionPoint &cp : map_infill_end_point_to_boundary) + arches.reserve(graph.map_infill_end_point_to_boundary.size()); + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) if (cp.contour_idx != boundary_idx_unconnected && cp.next_on_contour != &cp && cp.could_connect_next()) - arches.push_back({ &cp, path_length_along_contour_ccw(&cp, cp.next_on_contour, boundary_params[cp.contour_idx].back()) }); + arches.push_back({ &cp, path_length_along_contour_ccw(&cp, cp.next_on_contour, graph.boundary_params[cp.contour_idx].back()) }); std::sort(arches.begin(), arches.end(), [](const auto &l, const auto &r) { return l.arc_length < r.arc_length; }); //FIXME improve the Traveling Salesman problem with 2-opt and 3-opt local optimization. @@ -1358,10 +1534,10 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectornext_on_contour; - size_t polyline_idx1 = get_and_update_merged_with(((cp1 - map_infill_end_point_to_boundary.data()) / 2)); - size_t polyline_idx2 = get_and_update_merged_with(((cp2 - map_infill_end_point_to_boundary.data()) / 2)); - const Points &contour = boundary[cp1->contour_idx]; - const std::vector &contour_params = boundary_params[cp1->contour_idx]; + size_t polyline_idx1 = get_and_update_merged_with(((cp1 - graph.map_infill_end_point_to_boundary.data()) / 2)); + size_t polyline_idx2 = get_and_update_merged_with(((cp2 - graph.map_infill_end_point_to_boundary.data()) / 2)); + const Points &contour = graph.boundary[cp1->contour_idx]; + const std::vector &contour_params = graph.boundary_params[cp1->contour_idx]; if (polyline_idx1 != polyline_idx2) { Polyline &polyline1 = infill_ordered[polyline_idx1]; Polyline &polyline2 = infill_ordered[polyline_idx2]; @@ -1392,10 +1568,10 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector &contour_params = boundary_params[contour_point.contour_idx]; + const Points &contour = graph.boundary[contour_point.contour_idx]; + const std::vector &contour_params = graph.boundary_params[contour_point.contour_idx]; double lprev = contour_point.could_connect_prev() ? path_length_along_contour_ccw(contour_point.prev_on_contour, &contour_point, contour_params.back()) : @@ -1403,7 +1579,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector::max(); - size_t polyline_idx = get_and_update_merged_with(((&contour_point - map_infill_end_point_to_boundary.data()) / 2)); + size_t polyline_idx = get_and_update_merged_with(((&contour_point - graph.map_infill_end_point_to_boundary.data()) / 2)); Polyline &polyline = infill_ordered[polyline_idx]; assert(! polyline.empty()); assert(contour[contour_point.point_idx] == polyline.points.front() || contour[contour_point.point_idx] == polyline.points.back()); @@ -1415,7 +1591,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector &contour_param = graph.boundary_params[cp.contour_idx]; + const Point &pt = contour[cp.point_idx]; + const bool first = graph.first(cp); + int extend_next_idx = -1; + int extend_prev_idx = -1; + coord_t dist_y_prev; + coord_t dist_y_next; + double arc_len_prev; + double arc_len_next; + + if (! graph.next_vertical(cp)){ + size_t i = cp.point_idx; + size_t j = next_idx_modulo(i, contour); + while (j != cp.next_on_contour->point_idx) { + //const Point &p1 = contour[i]; + const Point &p2 = contour[j]; + if (std::abs(p2.x() - pt.x()) > dist_max_x) + break; + i = j; + j = next_idx_modulo(j, contour); + } + if (i != cp.point_idx) { + const Point &p2 = contour[i]; + coord_t dist_y = p2.y() - pt.y(); + if (first) + dist_y = - dist_y; + if (dist_y > dist_min_y) { + arc_len_next = closed_contour_distance_ccw(contour_param[cp.point_idx], contour_param[i], contour_param.back()); + if (arc_len_next < cp.contour_not_taken_length_next) { + extend_next_idx = i; + dist_y_next = dist_y; + } + } + } + } + + if (! graph.prev_vertical(cp)) { + size_t i = cp.point_idx; + size_t j = prev_idx_modulo(i, contour); + while (j != cp.prev_on_contour->point_idx) { + //const Point &p1 = contour[i]; + const Point &p2 = contour[j]; + if (std::abs(p2.x() - pt.x()) > dist_max_x) + break; + i = j; + j = prev_idx_modulo(j, contour); + } + if (i != cp.point_idx) { + const Point &p2 = contour[i]; + coord_t dist_y = p2.y() - pt.y(); + if (first) + dist_y = - dist_y; + if (dist_y > dist_min_y) { + arc_len_prev = closed_contour_distance_ccw(contour_param[i], contour_param[cp.point_idx], contour_param.back()); + if (arc_len_prev < cp.contour_not_taken_length_prev) { + extend_prev_idx = i; + dist_y_prev = dist_y; + } + } + } + } + + if (extend_prev_idx >= 0 && extend_next_idx >= 0) + // Which side to move the point? + dist_y_prev < dist_y_next ? extend_prev_idx : extend_next_idx = -1; + + assert(cp.prev_trimmed == cp.prev_on_contour->next_trimmed); + assert(cp.next_trimmed == cp.next_on_contour->prev_trimmed); + Polyline &infill_line = infill[(&cp - graph.map_infill_end_point_to_boundary.data()) / 2]; + if (extend_prev_idx >= 0) { + if (first) + infill_line.reverse(); + take_cw_full(infill_line, contour, cp.point_idx, extend_prev_idx); + if (first) + infill_line.reverse(); + cp.point_idx = extend_prev_idx; + if (cp.prev_trimmed) + cp.contour_not_taken_length_prev -= arc_len_prev; + else + cp.contour_not_taken_length_prev = cp.prev_on_contour->contour_not_taken_length_next = + closed_contour_distance_ccw(contour_param[cp.prev_on_contour->point_idx], contour_param[cp.point_idx], contour_param.back()); + cp.trim_next(0); + cp.next_on_contour->prev_trimmed = true; + } else if (extend_next_idx >= 0) { + if (first) + infill_line.reverse(); + take_ccw_full(infill_line, contour, cp.point_idx, extend_next_idx); + if (first) + infill_line.reverse(); + cp.point_idx = extend_next_idx; + cp.trim_prev(0); + cp.prev_on_contour->next_trimmed = true; + if (cp.next_trimmed) + cp.contour_not_taken_length_next -= arc_len_next; + else + cp.contour_not_taken_length_next = cp.next_on_contour->contour_not_taken_length_prev = + closed_contour_distance_ccw(contour_param[cp.point_idx], contour_param[cp.next_on_contour->point_idx], contour_param.back()); + } + } +} + +// Called by Fill::connect_base_support() as part of the sparse support infill generator. +// Emit contour loops tracing the contour from tbegin to tend inside a band of (left, right). +// The contour is supposed to enter the "forbidden" zone outside of the (left, right) band at tbegin and also at tend. +static inline void emit_loops_in_band( + // Vertical band, which will trim the contour between tbegin and tend. + coord_t left, + coord_t right, + // Contour and its parametrization. + const Points &contour, + const std::vector &contour_params, + // Span of the parameters of an arch to trim with the vertical band. + double tbegin, + double tend, + // Minimum arch length to put into polylines_out. Shorter arches are not necessary to support a dense support infill. + double min_length, + Polylines &polylines_out) +{ + assert(left < right); + assert(contour.size() + 1 == contour_params.size()); + assert(contour.size() >= 3); +#ifndef NDEBUG + double contour_length = contour_params.back(); + assert(tbegin >= 0 && tbegin < contour_length); + assert(tend >= 0 && tend < contour_length); + assert(min_length > 0); +#endif // NDEBUG + + // Find iterators of the range of segments, where the first and last segment contains tbegin and tend. + size_t ibegin, iend; + { + auto it_begin = std::lower_bound(contour_params.begin(), contour_params.end(), tbegin); + auto it_end = std::lower_bound(contour_params.begin(), contour_params.end(), tend); + assert(it_begin != contour_params.end()); + assert(it_end != contour_params.end()); + if (*it_begin != tbegin) { + assert(it_begin != contour_params.begin()); + -- it_begin; + } + ibegin = it_begin - contour_params.begin(); + iend = it_end - contour_params.begin(); + } + + if (ibegin == contour.size()) + ibegin = 0; + if (iend == contour.size()) + iend = 0; + assert(ibegin != iend); + + // Trim the start and end segment to calculate start and end points. + Point pbegin, pend; + { + double t1 = contour_params[ibegin]; + double t2 = next_value_modulo(ibegin, contour_params); + pbegin = lerp(contour[ibegin], next_value_modulo(ibegin, contour), (tbegin - t1) / (t2 - t1)); + t1 = contour_params[iend]; + t2 = prev_value_modulo(iend, contour_params); + pend = lerp(contour[iend], prev_value_modulo(iend, contour), (tend - t1) / (t2 - t1)); + } + + // Trace the contour from ibegin to iend. + enum Side { + Left, + Right, + Mid, + Unknown + }; + + enum InOutBand { + Entering, + Leaving, + }; + + class State { + public: + State(coord_t left, coord_t right, double min_length, Polylines &polylines_out) : + m_left(left), m_right(right), m_min_length(min_length), m_polylines_out(polylines_out) {} + + void add_inner_point(const Point* p) + { + m_polyline.points.emplace_back(*p); + } + + void add_outer_point(const Point* p) + { + if (m_polyline_end > 0) + m_polyline.points.emplace_back(*p); + } + + void add_interpolated_point(const Point* p1, const Point* p2, Side side, InOutBand inout) + { + assert(side == Left || side == Right); + + coord_t x = side == Left ? m_left : m_right; + coord_t y = p1->y() + coord_t(double(x - p1->x()) * double(p2->y() - p1->y()) / double(p2->x() - p1->x())); + + if (inout == Leaving) { + assert(m_polyline_end == 0); + m_polyline_end = m_polyline.size(); + m_polyline.points.emplace_back(x, y); + } else { + assert(inout == Entering); + if (m_polyline_end > 0) { + if ((this->side1 == Left) == (y - m_polyline.points[m_polyline_end].y() < 0)) { + // Emit the vertical segment. Remove the point, where the source contour was split the last time at m_left / m_right. + m_polyline.points.erase(m_polyline.points.begin() + m_polyline_end); + } else { + // Don't emit the vertical segment, split the contour. + this->finalize(); + m_polyline.points.emplace_back(x, y); + } + m_polyline_end = 0; + } else + m_polyline.points.emplace_back(x, y); + } + }; + + void finalize() + { + m_polyline.points.erase(m_polyline.points.begin() + m_polyline_end, m_polyline.points.end()); + if (! m_polyline.empty()) { + if (! m_polylines_out.empty() && (m_polylines_out.back().points.back() - m_polyline.points.front()).cast().squaredNorm() < SCALED_EPSILON) + m_polylines_out.back().points.insert(m_polylines_out.back().points.end(), m_polyline.points.begin() + 1, m_polyline.points.end()); + else if (m_polyline.length() > m_min_length) + m_polylines_out.emplace_back(std::move(m_polyline)); + m_polyline.clear(); + } + }; + + private: + coord_t m_left; + coord_t m_right; + double m_min_length; + Polylines &m_polylines_out; + + Polyline m_polyline; + size_t m_polyline_end { 0 }; + Polyline m_overlapping; + + public: + Side side1 { Unknown }; + Side side2 { Unknown }; + }; + + State state { left, right, min_length, polylines_out }; + + const Point *p1 = &pbegin; + auto side = [left, right](const Point* p) { + coord_t x = p->x(); + return x < left ? Left : x > right ? Right : Mid; + }; + state.side1 = side(p1); + if (state.side1 == Mid) + state.add_inner_point(p1); + + for (size_t i = ibegin; i != iend; ) { + size_t inext = i + 1; + if (inext == contour.size()) + inext = 0; + const Point *p2 = inext == iend ? &pend : &contour[inext]; + state.side2 = side(p2); + if (state.side1 == Mid) { + if (state.side2 == Mid) { + // Inside the band. + state.add_inner_point(p2); + } else { + // From intisde the band to the outside of the band. + state.add_interpolated_point(p1, p2, state.side2, Leaving); + state.add_outer_point(p2); + } + } else if (state.side2 == Mid) { + // From outside the band into the band. + state.add_interpolated_point(p1, p2, state.side1, Entering); + state.add_inner_point(p2); + } else if (state.side1 != state.side2) { + // Both points outside the band. + state.add_interpolated_point(p1, p2, state.side1, Entering); + state.add_interpolated_point(p1, p2, state.side2, Leaving); + } else { + // Complete segment is outside. + assert((state.side1 == Left && state.side2 == Left) || (state.side1 == Right && state.side2 == Right)); + state.add_outer_point(p2); + } + state.side1 = state.side2; + p1 = p2; + i = inext; + } + state.finalize(); +} + +#ifdef INFILL_DEBUG_OUTPUT +static void export_partial_infill_to_svg(const std::string &path, const BoundaryInfillGraph &graph, const Polylines &infill, const Polylines &emitted) +{ + Polygons polygons; + for (const Points &pts : graph.boundary) + polygons.emplace_back(pts); + BoundingBox bbox = get_extents(polygons); + bbox.merge(get_extents(infill)); + ::Slic3r::SVG svg(path, bbox); + svg.draw(union_ex(polygons)); + svg.draw(infill, "blue"); + svg.draw(emitted, "darkblue"); + for (const ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) + svg.draw(graph.point(cp), cp.consumed ? "red" : "green", scaled(0.2)); + for (const ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + assert(cp.next_trimmed == cp.next_on_contour->prev_trimmed); + assert(cp.prev_trimmed == cp.prev_on_contour->next_trimmed); + if (cp.contour_not_taken_length_next > SCALED_EPSILON) { + Polyline pl { graph.point(cp) }; + take_ccw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.next_on_contour->point_idx, cp.contour_not_taken_length_next); + svg.draw(pl, cp.could_take_next() ? "lime" : "magenta", scaled(0.1)); + } + if (cp.contour_not_taken_length_prev > SCALED_EPSILON) { + Polyline pl { graph.point(cp) }; + take_cw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.prev_on_contour->point_idx, cp.contour_not_taken_length_prev); + svg.draw(pl, cp.could_take_prev() ? "lime" : "magenta", scaled(0.1)); + } + } +} +#endif // INFILL_DEBUG_OUTPUT + +// To classify perimeter segments connecting infill lines, whether they are required for structural stability of the supports. +struct SupportArcCost +{ + // Connecting one end of an infill line to the other end of the same infill line. + bool self_loop { false }; + // Some of the arc touches some infill line. + bool open { false }; + // How needed is this arch for support structural stability. + // Zero means don't take. The higher number, the more likely it is to take the arc. + double cost { 0 }; +}; + +static double evaluate_support_arch_cost(const Polyline &pl) +{ + Point front = pl.points.front(); + Point back = pl.points.back(); + + coord_t ymin = front.y(); + coord_t ymax = back.y(); + if (ymin > ymax) + std::swap(ymin, ymax); + + double dmax = 0; + // Maximum distance in Y axis out of the (ymin, ymax) band and from the (front, back) line. + struct Linef { Vec2d a, b; }; + Linef line { front.cast(), back.cast() }; + for (const Point pt : pl.points) + dmax = std::max(std::max(dmax, line_alg::distance_to(line, Vec2d(pt.cast()))), std::max(pt.y() - ymax, ymin - pt.y())); + return dmax; +} + +// Costs for prev / next arch of each infill line end point. +static inline std::vector evaluate_support_arches(Polylines &infill, BoundaryInfillGraph &graph, const double spacing, const FillParams ¶ms) +{ + std::vector arches(graph.map_infill_end_point_to_boundary.size() * 2); + + Polyline pl; + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + // Not a losed loop, such loops should already be consumed. + assert(cp.next_on_contour != &cp); + const size_t infill_line_idx = &cp - graph.map_infill_end_point_to_boundary.data(); + const bool first = (infill_line_idx & 1) == 0; + const ContourIntersectionPoint *other_end = first ? &cp + 1 : &cp - 1; + + SupportArcCost &out_prev = arches[infill_line_idx * 2]; + SupportArcCost &out_next = *(&out_prev + 1); + out_prev.self_loop = cp.prev_on_contour == other_end; + out_prev.open = cp.prev_trimmed; + out_next.self_loop = cp.next_on_contour == other_end; + out_next.open = cp.next_trimmed; + + if (cp.contour_not_taken_length_next > SCALED_EPSILON) { + pl.clear(); + pl.points.emplace_back(graph.point(cp)); + if (cp.next_trimmed) + take_ccw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.next_on_contour->point_idx, cp.contour_not_taken_length_next); + else + take_ccw_full(pl, graph.boundary[cp.contour_idx], cp.point_idx, cp.next_on_contour->point_idx); + out_next.cost = evaluate_support_arch_cost(pl); + } + + if (cp.contour_not_taken_length_prev > SCALED_EPSILON) { + pl.clear(); + pl.points.emplace_back(graph.point(cp)); + if (cp.prev_trimmed) + take_cw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.prev_on_contour->point_idx, cp.contour_not_taken_length_prev); + else + take_cw_full(pl, graph.boundary[cp.contour_idx], cp.point_idx, cp.prev_on_contour->point_idx); + out_prev.cost = evaluate_support_arch_cost(pl); + } + } + + return arches; +} + +// Both the poly_with_offset and polylines_out are rotated, so the infill lines are strictly vertical. +void Fill::connect_base_support(Polylines &&infill_ordered, const std::vector &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms) +{ +// assert(! infill_ordered.empty()); + assert(params.anchor_length >= 0.); + assert(params.anchor_length_max >= 0.01f); + assert(params.anchor_length_max >= params.anchor_length); + + BoundaryInfillGraph graph = create_boundary_infill_graph(infill_ordered, boundary_src, bbox, spacing); + +#ifdef INFILL_DEBUG_OUTPUT + static int iRun = 0; + ++ iRun; + export_partial_infill_to_svg(debug_out_path("connect_base_support-initial-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + const double line_half_width = 0.5 * scale_(spacing); + const double line_spacing = scale_(spacing) / params.density; + const double min_arch_length = 1.3 * line_spacing; + const double trim_length = line_half_width * 0.3; + +// After mark_boundary_segments_touching_infill() marks boundary segments overlapping trimmed infill lines, +// there are possibly some very short boundary segments unmarked, but overlapping the untrimmed infill lines fully. +// Mark those short boundary segments. + mark_boundary_segments_overlapping_infill(graph, infill_ordered, scale_(spacing)); + +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-marked-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + // Detect loops with zero infill end points connected. + // Extrude these loops as perimeters. + { + std::vector num_boundary_contour_infill_points(graph.boundary.size(), 0); + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) + ++ num_boundary_contour_infill_points[cp.contour_idx]; + for (size_t i = 0; i < num_boundary_contour_infill_points.size(); ++ i) + if (num_boundary_contour_infill_points[i] == 0 && graph.boundary_params[i].back() > trim_length + 0.5 * line_spacing) { + // Emit a perimeter. + Polyline pl(graph.boundary[i]); + pl.points.emplace_back(pl.points.front()); + pl.clip_end(trim_length); + if (pl.size() > 1) + polylines_out.emplace_back(std::move(pl)); + } + } + + // Before processing the boundary arches, emit those arches, which were trimmed by the infill lines at both sides, but which + // depart from the infill line at least once after touching the infill line. + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + if (cp.next_on_contour && cp.next_trimmed && cp.next_on_contour->prev_trimmed) { + // The arch is leaving one infill line to end up at the same infill line or at the neighbouring one. + // The arch is touching one of those infill lines at least once. + // Trace those arches and emit their parts, which are not attached to the end points and they are not overlapping with the two infill lines mentioned. + bool first = graph.first(cp); + coord_t left = graph.point(cp).x(); + coord_t right = left; + if (first) { + left += line_half_width; + right += line_spacing - line_half_width; + } else { + left -= line_spacing - line_half_width; + right -= line_half_width; + } + double param_start = cp.param + cp.contour_not_taken_length_next; + double param_end = cp.next_on_contour->param - cp.next_on_contour->contour_not_taken_length_prev; + double contour_length = graph.boundary_params[cp.contour_idx].back(); + if (param_start >= contour_length) + param_start -= contour_length; + if (param_end < 0) + param_end += contour_length; + // Verify that the interval (param_overlap1, param_overlap2) is inside the interval (ip_low->param, ip_high->param). + assert(cyclic_interval_inside_interval(cp.param, cp.next_on_contour->param, param_start, param_end, contour_length)); + emit_loops_in_band(left, right, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], param_start, param_end, 0.5 * line_spacing, polylines_out); + } + } +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-excess-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + base_support_extend_infill_lines(infill_ordered, graph, spacing, params); + +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-extended-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + std::vector merged_with(infill_ordered.size()); + std::iota(merged_with.begin(), merged_with.end(), 0); + auto get_and_update_merged_with = [&graph, &merged_with](const ContourIntersectionPoint *cp) -> size_t { + size_t polyline_idx = (cp - graph.map_infill_end_point_to_boundary.data()) / 2; + for (size_t last = polyline_idx;;) { + size_t lower = merged_with[last]; + assert(lower <= last); + if (lower == last) { + merged_with[polyline_idx] = last; + return last; + } + last = lower; + } + assert(false); + return std::numeric_limits::max(); + }; + + auto vertical = [](BoundaryInfillGraph::Direction dir) { + return dir == BoundaryInfillGraph::Up || dir == BoundaryInfillGraph::Down; + }; + // When both left / right arch connected to cp is vertical (ends up at the same vertical infill line), which one to take? + auto take_vertical_prev = [](const ContourIntersectionPoint &cp) { + return cp.prev_trimmed == cp.next_trimmed ? + // Both are either trimmed or not trimmed. Take the longer contour. + cp.contour_not_taken_length_prev > cp.contour_not_taken_length_next : + // One is trimmed, the other is not trimmed. Take the not trimmed. + ! cp.prev_trimmed && cp.next_trimmed; + }; + + // Connect infill lines at cp and cpo_next_on_contour. + // If the complete arch cannot be taken, then + // if (take_first) + // take the infill line at cp and an arc from cp towards cp.next_on_contour. + // else + // take the infill line at cp_next_on_contour and an arc from cp.next_on_contour towards cp. + // If cp1 == next_on_contour (a single infill line is connected to a contour, this is a valid case for contours with holes), + // then extrude the full circle. + // Nothing is done if the arch could no more be taken (one of it end points were consumed already). + auto take_next = [&graph, &infill_ordered, &merged_with, get_and_update_merged_with, line_half_width, trim_length](ContourIntersectionPoint &cp, bool take_first) { + // Indices of the polylines to be connected by a perimeter segment. + ContourIntersectionPoint *cp1 = &cp; + ContourIntersectionPoint *cp2 = cp.next_on_contour; + assert(cp1->next_trimmed == cp2->prev_trimmed); + //assert(cp1->next_trimmed || cp1->consumed == cp2->consumed); + if (take_first ? cp1->consumed : cp2->consumed) + return; + size_t polyline_idx1 = get_and_update_merged_with(cp1); + size_t polyline_idx2 = get_and_update_merged_with(cp2); + Polyline &polyline1 = infill_ordered[polyline_idx1]; + Polyline &polyline2 = infill_ordered[polyline_idx2]; + const Points &contour = graph.boundary[cp1->contour_idx]; + const std::vector &contour_params = graph.boundary_params[cp1->contour_idx]; + assert(cp1->consumed || contour[cp1->point_idx] == polyline1.points.front() || contour[cp1->point_idx] == polyline1.points.back()); + assert(cp2->consumed || contour[cp2->point_idx] == polyline2.points.front() || contour[cp2->point_idx] == polyline2.points.back()); + bool trimmed = take_first ? cp1->next_trimmed : cp2->prev_trimmed; + if (! trimmed) { + // Trim the end if closing a loop or making a T-joint. + trimmed = cp1 == cp2 || polyline_idx1 == polyline_idx2 || (take_first ? cp2->consumed : cp1->consumed); + if (! trimmed) { + const bool cp1_first = ((cp1 - graph.map_infill_end_point_to_boundary.data()) & 1) == 0; + const ContourIntersectionPoint* cp1_other = cp1_first ? cp1 + 1 : cp1 - 1; + // Self loop, connecting the end points of the same infill line. + trimmed = cp2 == cp1_other; + } + if (trimmed) /* [[unlikely]] */ { + // Single end point on a contour. This may happen on contours with holes. Extrude a loop. + // Or a self loop, connecting the end points of the same infill line. + // Or closing a chain of infill lines. This may happen if infilling a contour with a hole. + double len = cp1 == cp2 ? contour_params.back() : path_length_along_contour_ccw(cp1, cp2, contour_params.back()); + if (take_first) { + cp1->trim_next(std::max(0., len - trim_length - SCALED_EPSILON)); + cp2->trim_prev(0); + } else { + cp1->trim_next(0); + cp2->trim_prev(std::max(0., len - trim_length - SCALED_EPSILON)); + } + } + } + if (trimmed) { + if (take_first) + take_limited(polyline1, contour, contour_params, cp1, cp2, false, 1e10, line_half_width); + else + take_limited(polyline2, contour, contour_params, cp2, cp1, true, 1e10, line_half_width); + } else if (! cp1->consumed && ! cp2->consumed) { + if (contour[cp1->point_idx] == polyline1.points.front()) + polyline1.reverse(); + if (contour[cp2->point_idx] == polyline2.points.back()) + polyline2.reverse(); + take(polyline1, polyline2, contour, cp1, cp2, false); + // Mark the second polygon as merged with the first one. + if (polyline_idx2 < polyline_idx1) { + polyline2 = std::move(polyline1); + polyline1.points.clear(); + merged_with[polyline_idx1] = merged_with[polyline_idx2]; + } else { + polyline2.points.clear(); + merged_with[polyline_idx2] = merged_with[polyline_idx1]; + } + } + }; + + // Consume all vertical arches. If a vertical arch is touching a neighboring vertical infill line, thus the vertical arch is trimmed, + // only consume the trimmed part if it is longer than min_arch_length. + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + assert(cp.contour_idx != boundary_idx_unconnected); + if (cp.consumed) + continue; + const ContourIntersectionPoint &cp_other = graph.other(cp); + assert((cp.next_on_contour == &cp_other) == (cp_other.prev_on_contour == &cp)); + assert((cp.prev_on_contour == &cp_other) == (cp_other.next_on_contour == &cp)); + BoundaryInfillGraph::Direction dir_prev = graph.dir_prev(cp); + BoundaryInfillGraph::Direction dir_next = graph.dir_next(cp); + // Following code will also consume contours with just a single infill line attached. (cp1->next_on_contour == cp1). + assert((cp.next_on_contour == &cp) == (cp.prev_on_contour == &cp)); + bool can_take_prev = vertical(dir_prev) && ! cp.prev_on_contour->consumed && cp.prev_on_contour != &cp_other; + bool can_take_next = vertical(dir_next) && ! cp.next_on_contour->consumed && cp.next_on_contour != &cp_other; + if (can_take_prev && (! can_take_next || take_vertical_prev(cp))) { + if (! cp.prev_trimmed || cp.contour_not_taken_length_prev > min_arch_length) + // take previous + take_next(*cp.prev_on_contour, false); + } else if (can_take_next) { + if (! cp.next_trimmed || cp.contour_not_taken_length_next > min_arch_length) + // take next + take_next(cp, true); + } + } + +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-vertical-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + const std::vector arches = evaluate_support_arches(infill_ordered, graph, spacing, params); + static const double cost_low = line_spacing * 1.3; + static const double cost_high = line_spacing * 2.; + static const double cost_veryhigh = line_spacing * 3.; + + { + std::vector selected; + selected.reserve(graph.map_infill_end_point_to_boundary.size()); + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + if (cp.consumed) + continue; + const SupportArcCost &cost_prev = arches[(&cp - graph.map_infill_end_point_to_boundary.data()) * 2]; + const SupportArcCost &cost_next = *(&cost_prev + 1); + double cost_min = cost_prev.cost; + double cost_max = cost_next.cost; + if (cost_min > cost_max) + std::swap(cost_min, cost_max); + if (cost_max < cost_low || cost_min > cost_high) + // Don't take any of the prev / next arches now, take zig-zag instead. It does not matter which one will be taken. + continue; + const double cost_diff_relative = (cost_max - cost_min) / cost_max; + if (cost_diff_relative < 0.25) + // Don't take any of the prev / next arches now, take zig-zag instead. It does not matter which one will be taken. + continue; + if (cost_prev.cost > cost_low) + selected.emplace_back(&cost_prev); + if (cost_next.cost > cost_low) + selected.emplace_back(&cost_next); + } + // Take the longest arch first. + std::sort(selected.begin(), selected.end(), [](const auto *l, const auto *r) { return l->cost > r->cost; }); + // And connect along the arches. + for (const SupportArcCost *arc : selected) { + ContourIntersectionPoint &cp = graph.map_infill_end_point_to_boundary[(arc - arches.data()) / 2]; + if (! cp.consumed) { + bool prev = ((arc - arches.data()) & 1) == 0; + if (prev) + take_next(*cp.prev_on_contour, false); + else + take_next(cp, true); + } + } + } + +#if 0 + { + // Connect infill lines with long horizontal arches. Only take a horizontal arch, if it will not block + // the end caps (vertical arches) at the other side of the infill line. + struct Arc { + ContourIntersectionPoint *intersection; + double arc_length; + bool take_next; + }; + std::vector arches; + arches.reserve(graph.map_infill_end_point_to_boundary.size()); + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + if (cp.consumed) + continue; + // Not a losed loop, such loops should already be consumed. + assert(cp.next_on_contour != &cp); + const bool first = ((&cp - graph.map_infill_end_point_to_boundary.data()) & 1) == 0; + const ContourIntersectionPoint *other_end = first ? &cp + 1 : &cp - 1; + const bool loop_next = cp.next_on_contour == other_end; + if (! loop_next && cp.could_connect_next()) { + if (cp.contour_not_taken_length_next > min_arch_length) { + // Try both directions. This is useful to be able to close a loop back to the same line to take a long arch. + arches.push_back({ &cp, cp.contour_not_taken_length_next, true }); + arches.push_back({ cp.next_on_contour, cp.contour_not_taken_length_next, false }); + } + } else { + //bool first = ((&cp - graph.map_infill_end_point_to_boundary) & 1) == 0; + if (cp.prev_trimmed && cp.could_take_prev()) { + //FIXME trace the trimmed line to decide what priority to assign to it. + // Is the end point close to the current vertical line or to the other vertical line? + const Point &pt = graph.point(cp); + const Point &prev = graph.point(*cp.prev_on_contour); + if (std::abs(pt.x() - prev.x()) < coord_t(0.5 * line_spacing)) { + // End point on the same line. + // Measure maximum distance from the current vertical line. + if (cp.contour_not_taken_length_prev > 0.5 * line_spacing) + arches.push_back({ &cp, cp.contour_not_taken_length_prev, false }); + } else { + // End point on the other line. + if (cp.contour_not_taken_length_prev > min_arch_length) + arches.push_back({ &cp, cp.contour_not_taken_length_prev, false }); + } + } + if (cp.next_trimmed && cp.could_take_next()) { + //FIXME trace the trimmed line to decide what priority to assign to it. + const Point &pt = graph.point(cp); + const Point &next = graph.point(*cp.next_on_contour); + if (std::abs(pt.x() - next.x()) < coord_t(0.5 * line_spacing)) { + // End point on the same line. + // Measure maximum distance from the current vertical line. + if (cp.contour_not_taken_length_next > 0.5 * line_spacing) + arches.push_back({ &cp, cp.contour_not_taken_length_next, true }); + } else { + // End point on the other line. + if (cp.contour_not_taken_length_next > min_arch_length) + arches.push_back({ &cp, cp.contour_not_taken_length_next, true }); + } + } + } + } + // Take the longest arch first. + std::sort(arches.begin(), arches.end(), [](const auto &l, const auto &r) { return l.arc_length > r.arc_length; }); + // And connect along the arches. + for (Arc &arc : arches) + if (arc.take_next) + take_next(*arc.intersection, true); + else + take_next(*arc.intersection->prev_on_contour, false); + } +#endif + +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-arches-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + // Traverse the unconnected lines in a zig-zag fashion, left to right only. + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + assert(cp.contour_idx != boundary_idx_unconnected); + if (cp.consumed) + continue; + bool first = ((&cp - graph.map_infill_end_point_to_boundary.data()) & 1) == 0; + if (first) { + // Only connect if the two lines are not connected by the same line already. + if (get_and_update_merged_with(&cp) != get_and_update_merged_with(cp.next_on_contour)) + take_next(cp, true); + } else { + if (get_and_update_merged_with(&cp) != get_and_update_merged_with(cp.prev_on_contour)) + take_next(*cp.prev_on_contour, false); + } + } + +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-zigzag-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + // Add the left caps. + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + const bool first = ((&cp - graph.map_infill_end_point_to_boundary.data()) & 1) == 0; + const ContourIntersectionPoint *other_end = first ? &cp + 1 : &cp - 1; + const bool loop_next = cp.next_on_contour == other_end; + const bool loop_prev = other_end->next_on_contour == &cp; +#ifndef NDEBUG + const SupportArcCost &cost_prev = arches[(&cp - graph.map_infill_end_point_to_boundary.data()) * 2]; + const SupportArcCost &cost_next = *(&cost_prev + 1); + assert(cost_prev.self_loop == loop_prev); + assert(cost_next.self_loop == loop_next); +#endif // NDEBUG + if (loop_prev && cp.could_take_prev()) + take_next(*cp.prev_on_contour, false); + if (loop_next && cp.could_take_next()) + take_next(cp, true); + } + +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-caps-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + // Connect with T joints using long arches. Loops could be created only if a very long arc has to be added. + { + std::vector candidates; + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + if (cp.could_take_prev()) + candidates.emplace_back(&arches[(&cp - graph.map_infill_end_point_to_boundary.data()) * 2]); + if (cp.could_take_next()) + candidates.emplace_back(&arches[(&cp - graph.map_infill_end_point_to_boundary.data()) * 2 + 1]); + } + std::sort(candidates.begin(), candidates.end(), [](auto *c1, auto *c2) { return c1->cost > c2->cost; }); + for (const SupportArcCost *candidate : candidates) { + ContourIntersectionPoint &cp = graph.map_infill_end_point_to_boundary[(candidate - arches.data()) / 2]; + bool prev = ((candidate - arches.data()) & 1) == 0; + if (prev) { + if (cp.could_take_prev() && (get_and_update_merged_with(&cp) != get_and_update_merged_with(cp.prev_on_contour) || candidate->cost > cost_high)) + take_next(*cp.prev_on_contour, false); + } else { + if (cp.could_take_next() && (get_and_update_merged_with(&cp) != get_and_update_merged_with(cp.next_on_contour) || candidate->cost > cost_high)) + take_next(cp, true); + } + } + } + +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-Tjoints-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + // Add very long arches and reasonably long caps even if both of its end points were already consumed. + const double cap_cost = 0.5 * line_spacing; + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + const SupportArcCost &cost_prev = arches[(&cp - graph.map_infill_end_point_to_boundary.data()) * 2]; + const SupportArcCost &cost_next = *(&cost_prev + 1); + if (cp.contour_not_taken_length_prev > SCALED_EPSILON && + (cost_prev.self_loop ? + cost_prev.cost > cap_cost : + cost_prev.cost > cost_veryhigh)) { + assert(cp.consumed && (cp.prev_on_contour->consumed || cp.prev_trimmed)); + Polyline pl { graph.point(cp) }; + if (! cp.prev_trimmed) { + cp.trim_prev(cp.contour_not_taken_length_prev - line_half_width); + cp.prev_on_contour->trim_next(0); + } + if (cp.contour_not_taken_length_prev > SCALED_EPSILON) { + take_cw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.prev_on_contour->point_idx, cp.contour_not_taken_length_prev); + cp.trim_prev(0); + pl.clip_start(line_half_width); + polylines_out.emplace_back(std::move(pl)); + } + } + if (cp.contour_not_taken_length_next > SCALED_EPSILON && + (cost_next.self_loop ? + cost_next.cost > cap_cost : + cost_next.cost > cost_veryhigh)) { + assert(cp.consumed && (cp.next_on_contour->consumed || cp.next_trimmed)); + Polyline pl { graph.point(cp) }; + if (! cp.next_trimmed) { + cp.trim_next(cp.contour_not_taken_length_next - line_half_width); + cp.next_on_contour->trim_prev(0); + } + if (cp.contour_not_taken_length_next > SCALED_EPSILON) { + take_ccw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.next_on_contour->point_idx, cp.contour_not_taken_length_next); // line_half_width); + cp.trim_next(0); + pl.clip_start(line_half_width); + polylines_out.emplace_back(std::move(pl)); + } + } + } + +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-final-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + polylines_out.reserve(polylines_out.size() + std::count_if(infill_ordered.begin(), infill_ordered.end(), [](const Polyline &pl) { return ! pl.empty(); })); + for (Polyline &pl : infill_ordered) + if (! pl.empty()) + polylines_out.emplace_back(std::move(pl)); +} + +void Fill::connect_base_support(Polylines &&infill_ordered, const Polygons &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms) +{ + auto polygons_src = reserve_vector(boundary_src.size()); + for (const Polygon &polygon : boundary_src) + polygons_src.emplace_back(&polygon); + + connect_base_support(std::move(infill_ordered), polygons_src, bbox, polylines_out, spacing, params); +} + } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index c09b70bca0..eb9c4f1cc3 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -133,26 +133,10 @@ public: static void connect_infill(Polylines &&infill_ordered, const Polygons &boundary, const BoundingBox& bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms); static void connect_infill(Polylines &&infill_ordered, const std::vector &boundary, const BoundingBox &bbox, Polylines &polylines_out, double spacing, const FillParams ¶ms); - static coord_t _adjust_solid_spacing(const coord_t width, const coord_t distance); + static void connect_base_support(Polylines &&infill_ordered, const std::vector &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms); + static void connect_base_support(Polylines &&infill_ordered, const Polygons &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms); - // Align a coordinate to a grid. The coordinate may be negative, - // the aligned value will never be bigger than the original one. - static coord_t _align_to_grid(const coord_t coord, const coord_t spacing) { - // Current C++ standard defines the result of integer division to be rounded to zero, - // for both positive and negative numbers. Here we want to round down for negative - // numbers as well. - coord_t aligned = (coord < 0) ? - ((coord - spacing + 1) / spacing) * spacing : - (coord / spacing) * spacing; - assert(aligned <= coord); - return aligned; - } - static Point _align_to_grid(Point coord, Point spacing) - { return Point(_align_to_grid(coord(0), spacing(0)), _align_to_grid(coord(1), spacing(1))); } - static coord_t _align_to_grid(coord_t coord, coord_t spacing, coord_t base) - { return base + _align_to_grid(coord - base, spacing); } - static Point _align_to_grid(Point coord, Point spacing, Point base) - { return Point(_align_to_grid(coord(0), spacing(0), base(0)), _align_to_grid(coord(1), spacing(1), base(1))); } + static coord_t _adjust_solid_spacing(const coord_t width, const coord_t distance); }; } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillConcentric.cpp b/src/libslic3r/Fill/FillConcentric.cpp index 1b96c43a4d..785c93be3b 100644 --- a/src/libslic3r/Fill/FillConcentric.cpp +++ b/src/libslic3r/Fill/FillConcentric.cpp @@ -24,11 +24,11 @@ void FillConcentric::_fill_surface_single( this->spacing = unscale(distance); } - Polygons loops = to_polygons(std::move(expolygon)); - Polygons last = loops; + Polygons loops = to_polygons(expolygon); + ExPolygons last { std::move(expolygon) }; while (! last.empty()) { - last = offset2(last, -(distance + min_spacing/2), +min_spacing/2); - append(loops, last); + last = offset2_ex(last, -(distance + min_spacing/2), +min_spacing/2); + append(loops, to_polygons(last)); } // generate paths from the outermost to the innermost, to avoid diff --git a/src/libslic3r/Fill/FillGyroid.cpp b/src/libslic3r/Fill/FillGyroid.cpp index 5797c47a5c..ff2d049cfd 100644 --- a/src/libslic3r/Fill/FillGyroid.cpp +++ b/src/libslic3r/Fill/FillGyroid.cpp @@ -166,7 +166,7 @@ void FillGyroid::_fill_surface_single( coord_t distance = coord_t(scale_(this->spacing) / density_adjusted); // align bounding box to a multiple of our grid module - bb.merge(_align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance))); + bb.merge(align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance))); // generate pattern Polylines polylines = make_gyroid_waves( diff --git a/src/libslic3r/Fill/FillHoneycomb.cpp b/src/libslic3r/Fill/FillHoneycomb.cpp index 5e70000888..f7f79ae833 100644 --- a/src/libslic3r/Fill/FillHoneycomb.cpp +++ b/src/libslic3r/Fill/FillHoneycomb.cpp @@ -47,7 +47,7 @@ void FillHoneycomb::_fill_surface_single( // extend bounding box so that our pattern will be aligned with other layers // $bounding_box->[X1] and [Y1] represent the displacement between new bounding box offset and old one // The infill is not aligned to the object bounding box, but to a world coordinate system. Supposedly good enough. - bounding_box.merge(_align_to_grid(bounding_box.min, Point(m.hex_width, m.pattern_height))); + bounding_box.merge(align_to_grid(bounding_box.min, Point(m.hex_width, m.pattern_height))); } coord_t x = bounding_box.min(0); diff --git a/src/libslic3r/Fill/FillLine.cpp b/src/libslic3r/Fill/FillLine.cpp index 1cb9b2244d..f6431a3333 100644 --- a/src/libslic3r/Fill/FillLine.cpp +++ b/src/libslic3r/Fill/FillLine.cpp @@ -31,7 +31,7 @@ void FillLine::_fill_surface_single( } else { // extend bounding box so that our pattern will be aligned with other layers // Transform the reference point to the rotated coordinate system. - bounding_box.merge(_align_to_grid( + bounding_box.merge(align_to_grid( bounding_box.min, Point(this->_line_spacing, this->_line_spacing), direction.second.rotated(- direction.first))); @@ -58,7 +58,7 @@ void FillLine::_fill_surface_single( pts.push_back(it->a); pts.push_back(it->b); } - Polylines polylines = intersection_pl(polylines_src, offset(to_polygons(expolygon), scale_(0.02)), false); + Polylines polylines = intersection_pl(polylines_src, offset(expolygon, scale_(0.02)), false); // FIXME Vojtech: This is only performed for horizontal lines, not for the vertical lines! const float INFILL_OVERLAP_OVER_SPACING = 0.3f; diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index 07f41de383..baf57f4269 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -798,33 +798,44 @@ static std::vector slice_region_by_vertical_lines(con assert(l <= this_x); assert(r >= this_x); // Calculate the intersection position in y axis. x is known. - if (p1(0) == this_x) { - if (p2(0) == this_x) { + if (p1.x() == this_x) { + if (p2.x() == this_x) { // Ignore strictly vertical segments. continue; } - is.pos_p = p1(1); + const Point &p0 = prev_value_modulo(iPrev, contour); + if (int64_t(p0.x() - p1.x()) * int64_t(p2.x() - p1.x()) > 0) { + // Ignore points of a contour touching the infill line from one side. + continue; + } + is.pos_p = p1.y(); is.pos_q = 1; - } else if (p2(0) == this_x) { - is.pos_p = p2(1); + } else if (p2.x() == this_x) { + const Point &p3 = next_value_modulo(iSegment, contour); + if (int64_t(p3.x() - p2.x()) * int64_t(p1.x() - p2.x()) > 0) { + // Ignore points of a contour touching the infill line from one side. + continue; + } + is.pos_p = p2.y(); is.pos_q = 1; } else { // First calculate the intersection parameter 't' as a rational number with non negative denominator. - if (p2(0) > p1(0)) { - is.pos_p = this_x - p1(0); - is.pos_q = p2(0) - p1(0); + if (p2.x() > p1.x()) { + is.pos_p = this_x - p1.x(); + is.pos_q = p2.x() - p1.x(); } else { - is.pos_p = p1(0) - this_x; - is.pos_q = p1(0) - p2(0); + is.pos_p = p1.x() - this_x; + is.pos_q = p1.x() - p2.x(); } - assert(is.pos_p >= 0 && is.pos_p <= is.pos_q); + assert(is.pos_q > 1); + assert(is.pos_p > 0 && is.pos_p < is.pos_q); // Make an intersection point from the 't'. - is.pos_p *= int64_t(p2(1) - p1(1)); - is.pos_p += p1(1) * int64_t(is.pos_q); + is.pos_p *= int64_t(p2.y() - p1.y()); + is.pos_p += p1.y() * int64_t(is.pos_q); } // +-1 to take rounding into account. - assert(is.pos() + 1 >= std::min(p1(1), p2(1))); - assert(is.pos() <= std::max(p1(1), p2(1)) + 1); + assert(is.pos() + 1 >= std::min(p1.y(), p2.y())); + assert(is.pos() <= std::max(p1.y(), p2.y()) + 1); segs[i].intersections.push_back(is); } } @@ -844,55 +855,46 @@ static std::vector slice_region_by_vertical_lines(con size_t j = 0; for (size_t i = 0; i < sil.intersections.size(); ++ i) { // What is the orientation of the segment at the intersection point? - size_t iContour = sil.intersections[i].iContour; - const Points &contour = poly_with_offset.contour(iContour).points; - size_t iSegment = sil.intersections[i].iSegment; - size_t iPrev = ((iSegment == 0) ? contour.size() : iSegment) - 1; - coord_t dir = contour[iSegment](0) - contour[iPrev](0); - bool low = dir > 0; - sil.intersections[i].type = poly_with_offset.is_contour_outer(iContour) ? + SegmentIntersection &is = sil.intersections[i]; + const size_t iContour = is.iContour; + const Points &contour = poly_with_offset.contour(iContour).points; + const size_t iSegment = is.iSegment; + const size_t iPrev = prev_idx_modulo(iSegment, contour); + const coord_t dir = contour[iSegment].x() - contour[iPrev].x(); + const bool low = dir > 0; + is.type = poly_with_offset.is_contour_outer(iContour) ? (low ? SegmentIntersection::OUTER_LOW : SegmentIntersection::OUTER_HIGH) : (low ? SegmentIntersection::INNER_LOW : SegmentIntersection::INNER_HIGH); - if (j > 0 && sil.intersections[i].iContour == sil.intersections[j-1].iContour) { - // Two successive intersection points on a vertical line with the same contour. This may be a special case. - if (sil.intersections[i].pos() == sil.intersections[j-1].pos()) { - // Two successive segments meet exactly at the vertical line. - #ifdef SLIC3R_DEBUG - // Verify that the segments of sil.intersections[i] and sil.intersections[j-1] are adjoint. - size_t iSegment2 = sil.intersections[j-1].iSegment; - size_t iPrev2 = ((iSegment2 == 0) ? contour.size() : iSegment2) - 1; - assert(iSegment == iPrev2 || iSegment2 == iPrev); - #endif /* SLIC3R_DEBUG */ - if (sil.intersections[i].type == sil.intersections[j-1].type) { + bool take_next = true; + if (j > 0) { + SegmentIntersection &is2 = sil.intersections[j - 1]; + if (iContour == is2.iContour && is.pos_q == 1 && is2.pos_q == 1) { + // Two successive intersection points on a vertical line with the same contour, both points are end points of their respective contour segments. + if (is.pos_p == is2.pos_p) { + // Two successive segments meet exactly at the vertical line. + // Verify that the segments of sil.intersections[i] and sil.intersections[j-1] are adjoint. + assert(iSegment == prev_idx_modulo(is2.iSegment, contour) || is2.iSegment == iPrev); + assert(is.type == is2.type); // Two successive segments of the same direction (both to the right or both to the left) // meet exactly at the vertical line. // Remove the second intersection point. - } else { - // This is a loop returning to the same point. - // It may as well be a vertex of a loop touching this vertical line. - // Remove both the lines. - -- j; + take_next = false; + } else if (is.type == is2.type) { + // Two non successive segments of the same direction (both to the right or both to the left) + // meet exactly at the vertical line. That means there is a Z shaped path, where the center segment + // of the Z shaped path is aligned with this vertical line. + // Remove one of the intersection points while maximizing the vertical segment length. + if (low) { + // Remove the second intersection point, keep the first intersection point. + } else { + // Remove the first intersection point, keep the second intersection point. + sil.intersections[j-1] = sil.intersections[i]; + } + take_next = false; } - } else if (sil.intersections[i].type == sil.intersections[j-1].type) { - // Two non successive segments of the same direction (both to the right or both to the left) - // meet exactly at the vertical line. That means there is a Z shaped path, where the center segment - // of the Z shaped path is aligned with this vertical line. - // Remove one of the intersection points while maximizing the vertical segment length. - if (low) { - // Remove the second intersection point, keep the first intersection point. - } else { - // Remove the first intersection point, keep the second intersection point. - sil.intersections[j-1] = sil.intersections[i]; - } - } else { - // Vertical line intersects a contour segment at a general position (not at one of its end points). - // or the contour just touches this vertical line with a vertical segment or a sequence of vertical segments. - // Keep both intersection points. - if (j < i) - sil.intersections[j] = sil.intersections[i]; - ++ j; } - } else { + } + if (take_next) { // Vertical line intersects a contour segment at a general position (not at one of its end points). if (j < i) sil.intersections[j] = sil.intersections[i]; @@ -905,7 +907,13 @@ static std::vector slice_region_by_vertical_lines(con } // Verify the segments. If something is wrong, give up. -#define ASSERT_THROW(CONDITION) do { assert(CONDITION); if (! (CONDITION)) throw InfillFailedException(); } while (0) +#ifdef INFILL_DEBUG_OUTPUT + #define INFILL_DEBUG_ASSERT(CONDITION) + try { +#else // INFILL_DEBUG_OUTPUT + #define INFILL_DEBUG_ASSERT(CONDITION) assert(CONDITION) +#endif // INFILL_DEBUG_OUTPUT +#define ASSERT_THROW(CONDITION) do { INFILL_DEBUG_ASSERT(CONDITION); if (! (CONDITION)) throw InfillFailedException(); } while (0) for (size_t i_seg = 0; i_seg < segs.size(); ++ i_seg) { SegmentedIntersectionLine &sil = segs[i_seg]; // The intersection points have to be even. @@ -925,6 +933,56 @@ static std::vector slice_region_by_vertical_lines(con } } #undef ASSERT_THROW +#undef INFILL_DEBUG_ASSERT +#ifdef INFILL_DEBUG_OUTPUT + } catch (const InfillFailedException & /* ex */) { + // Export the buggy result into an SVG file. + static int iRun = 0; + BoundingBox bbox = get_extents(poly_with_offset.polygons_src); + bbox.offset(scale_(3.)); + ::Slic3r::SVG svg(debug_out_path("slice_region_by_vertical_lines-failed-%d.svg", iRun ++), bbox); + svg.draw(poly_with_offset.polygons_src); + svg.draw_outline(poly_with_offset.polygons_src, "green"); + svg.draw_outline(poly_with_offset.polygons_outer, "green"); + svg.draw_outline(poly_with_offset.polygons_inner, "green"); + for (size_t i_seg = 0; i_seg < segs.size(); ++i_seg) { + SegmentedIntersectionLine &sil = segs[i_seg]; + for (size_t i = 0; i < sil.intersections.size();) { + // An intersection segment crossing the bigger contour may cross the inner offsetted contour even number of times. + if (sil.intersections[i].type != SegmentIntersection::OUTER_LOW) { + svg.draw(Point(sil.pos, sil.intersections[i].pos()), "red"); + break; + } + size_t j = i + 1; + if (j == sil.intersections.size()) { + svg.draw(Point(sil.pos, sil.intersections[i].pos()), "magenta"); + break; + } + if (! (sil.intersections[j].type == SegmentIntersection::INNER_LOW || sil.intersections[j].type == SegmentIntersection::OUTER_HIGH)) { + svg.draw(Point(sil.pos, sil.intersections[j].pos()), "blue"); + break; + } + for (; j < sil.intersections.size() && sil.intersections[j].is_inner(); ++j); + if (j == sil.intersections.size()) { + svg.draw(Point(sil.pos, sil.intersections[j - 1].pos()), "magenta"); + break; + } + if ((j & 1) != 1 || sil.intersections[j].type != SegmentIntersection::OUTER_HIGH) { + svg.draw(Point(sil.pos, sil.intersections[j].pos()), "red"); + break; + } + if (! (i + 1 == j || sil.intersections[j - 1].type == SegmentIntersection::INNER_HIGH)) { + svg.draw(Point(sil.pos, sil.intersections[j].pos()), "red"); + break; + } + svg.draw(Line(Point(sil.pos, sil.intersections[i].pos()), Point(sil.pos, sil.intersections[j].pos())), "black"); + i = j + 1; + } + } + assert(false); + throw; + } +#endif //INFILL_DEBUG_OUTPUT return segs; } @@ -2714,10 +2772,10 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa // extend bounding box so that our pattern will be aligned with other layers // Transform the reference point to the rotated coordinate system. Point refpt = rotate_vector.second.rotated(- rotate_vector.first); - // _align_to_grid will not work correctly with positive pattern_shift. + // align_to_grid will not work correctly with positive pattern_shift. coord_t pattern_shift_scaled = coord_t(scale_(pattern_shift)) % line_spacing; refpt.x() -= (pattern_shift_scaled >= 0) ? pattern_shift_scaled : (line_spacing + pattern_shift_scaled); - bounding_box.merge(_align_to_grid( + bounding_box.merge(align_to_grid( bounding_box.min, Point(line_spacing, line_spacing), refpt)); @@ -2825,6 +2883,45 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa return true; } +void make_fill_lines(const ExPolygonWithOffset &poly_with_offset, Point refpt, double angle, coord_t x_margin, coord_t line_spacing, coord_t pattern_shift, Polylines &fill_lines) +{ + BoundingBox bounding_box = poly_with_offset.bounding_box_src(); + // Don't produce infill lines, which fully overlap with the infill perimeter. + coord_t x_min = bounding_box.min.x() + x_margin; + coord_t x_max = bounding_box.max.x() - x_margin; + // extend bounding box so that our pattern will be aligned with other layers + // align_to_grid will not work correctly with positive pattern_shift. + coord_t pattern_shift_scaled = pattern_shift % line_spacing; + refpt.x() -= (pattern_shift_scaled >= 0) ? pattern_shift_scaled : (line_spacing + pattern_shift_scaled); + bounding_box.merge(Slic3r::align_to_grid(bounding_box.min, Point(line_spacing, line_spacing), refpt)); + + // Intersect a set of euqally spaced vertical lines wiht expolygon. + // n_vlines = ceil(bbox_width / line_spacing) + const size_t n_vlines = (bounding_box.max.x() - bounding_box.min.x() + line_spacing - 1) / line_spacing; + const double cos_a = cos(angle); + const double sin_a = sin(angle); + for (const SegmentedIntersectionLine &vline : slice_region_by_vertical_lines(poly_with_offset, n_vlines, bounding_box.min.x(), line_spacing)) + if (vline.pos >= x_min) { + if (vline.pos > x_max) + break; + for (auto it = vline.intersections.begin(); it != vline.intersections.end();) { + auto it_low = it ++; + assert(it_low->type == SegmentIntersection::OUTER_LOW); + if (it_low->type != SegmentIntersection::OUTER_LOW) + continue; + auto it_high = it; + assert(it_high->type == SegmentIntersection::OUTER_HIGH); + if (it_high->type == SegmentIntersection::OUTER_HIGH) { + if (angle == 0.) + fill_lines.emplace_back(Point(vline.pos, it_low->pos()), Point(vline.pos, it_high->pos())); + else + fill_lines.emplace_back(Point(vline.pos, it_low->pos()).rotated(cos_a, sin_a), Point(vline.pos, it_high->pos()).rotated(cos_a, sin_a)); + ++ it; + } + } + } +} + bool FillRectilinear::fill_surface_by_multilines(const Surface *surface, FillParams params, const std::initializer_list &sweep_params, Polylines &polylines_out) { assert(sweep_params.size() > 1); @@ -2843,42 +2940,8 @@ bool FillRectilinear::fill_surface_by_multilines(const Surface *surface, FillPar std::pair rotate_vector = this->_infill_direction(surface); for (const SweepParams &sweep : sweep_params) { // Rotate polygons so that we can work with vertical lines here - double angle = rotate_vector.first + sweep.angle_base; - ExPolygonWithOffset poly_with_offset(poly_with_offset_base, - angle); - BoundingBox bounding_box = poly_with_offset.bounding_box_src(); - // Don't produce infill lines, which fully overlap with the infill perimeter. - coord_t x_min = bounding_box.min.x() + line_width + coord_t(SCALED_EPSILON); - coord_t x_max = bounding_box.max.x() - line_width - coord_t(SCALED_EPSILON); - // extend bounding box so that our pattern will be aligned with other layers - // Transform the reference point to the rotated coordinate system. - Point refpt = rotate_vector.second.rotated(- angle); - // _align_to_grid will not work correctly with positive pattern_shift. - coord_t pattern_shift_scaled = coord_t(scale_(sweep.pattern_shift)) % line_spacing; - refpt.x() -= (pattern_shift_scaled >= 0) ? pattern_shift_scaled : (line_spacing + pattern_shift_scaled); - bounding_box.merge(_align_to_grid(bounding_box.min, Point(line_spacing, line_spacing), refpt)); - - // Intersect a set of euqally spaced vertical lines wiht expolygon. - // n_vlines = ceil(bbox_width / line_spacing) - const size_t n_vlines = (bounding_box.max.x() - bounding_box.min.x() + line_spacing - 1) / line_spacing; - const double cos_a = cos(angle); - const double sin_a = sin(angle); - for (const SegmentedIntersectionLine &vline : slice_region_by_vertical_lines(poly_with_offset, n_vlines, bounding_box.min.x(), line_spacing)) - if (vline.pos > x_min) { - if (vline.pos >= x_max) - break; - for (auto it = vline.intersections.begin(); it != vline.intersections.end();) { - auto it_low = it ++; - assert(it_low->type == SegmentIntersection::OUTER_LOW); - if (it_low->type != SegmentIntersection::OUTER_LOW) - continue; - auto it_high = it; - assert(it_high->type == SegmentIntersection::OUTER_HIGH); - if (it_high->type == SegmentIntersection::OUTER_HIGH) { - fill_lines.emplace_back(Point(vline.pos, it_low->pos()).rotated(cos_a, sin_a), Point(vline.pos, it_high->pos()).rotated(cos_a, sin_a)); - ++ it; - } - } - } + float angle = rotate_vector.first + sweep.angle_base; + make_fill_lines(ExPolygonWithOffset(poly_with_offset_base, - angle), rotate_vector.second.rotated(-angle), angle, line_width + coord_t(SCALED_EPSILON), line_spacing, coord_t(scale_(sweep.pattern_shift)), fill_lines); } if (params.dont_connect() || fill_lines.size() <= 1) { @@ -2954,4 +3017,29 @@ Polylines FillCubic::fill_surface(const Surface *surface, const FillParams ¶ return polylines_out; } +Polylines FillSupportBase::fill_surface(const Surface *surface, const FillParams ¶ms) +{ + assert(! params.full_infill()); + + Polylines polylines_out; + std::pair rotate_vector = this->_infill_direction(surface); + ExPolygonWithOffset poly_with_offset(surface->expolygon, - rotate_vector.first, float(scale_(this->overlap - 0.5 * this->spacing))); + if (poly_with_offset.n_contours > 0) { + Polylines fill_lines; + coord_t line_spacing = coord_t(scale_(this->spacing) / params.density); + // Create infill lines, keep them vertical. + make_fill_lines(poly_with_offset, rotate_vector.second.rotated(- rotate_vector.first), 0, 0, line_spacing, 0, fill_lines); + // Both the poly_with_offset and polylines_out are rotated, so the infill lines are strictly vertical. + connect_base_support(std::move(fill_lines), poly_with_offset.polygons_outer, poly_with_offset.bounding_box_outer(), polylines_out, this->spacing, params); + // Rotate back by rotate_vector.first + const double cos_a = cos(rotate_vector.first); + const double sin_a = sin(rotate_vector.first); + for (Polyline &pl : polylines_out) + for (Point &pt : pl.points) + pt.rotate(cos_a, sin_a); + } + return polylines_out; +} + } // namespace Slic3r + diff --git a/src/libslic3r/Fill/FillRectilinear.hpp b/src/libslic3r/Fill/FillRectilinear.hpp index 692fba2bd1..ad32ad20f3 100644 --- a/src/libslic3r/Fill/FillRectilinear.hpp +++ b/src/libslic3r/Fill/FillRectilinear.hpp @@ -97,6 +97,17 @@ protected: float _layer_angle(size_t idx) const override { return 0.f; } }; +class FillSupportBase : public FillRectilinear +{ +public: + Fill* clone() const override { return new FillSupportBase(*this); } + ~FillSupportBase() override = default; + Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override; + +protected: + // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. + float _layer_angle(size_t idx) const override { return 0.f; } +}; } // namespace Slic3r diff --git a/src/libslic3r/Flow.cpp b/src/libslic3r/Flow.cpp index e5dcf07310..1645bf683a 100644 --- a/src/libslic3r/Flow.cpp +++ b/src/libslic3r/Flow.cpp @@ -122,20 +122,13 @@ double Flow::extrusion_width(const std::string& opt_key, const ConfigOptionResol // This constructor builds a Flow object from an extrusion width config setting // and other context properties. -Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio) +Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height) { - // we need layer height unless it's a bridge - if (height <= 0 && bridge_flow_ratio == 0) + if (height <= 0) throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_config_width()"); float w; - if (bridge_flow_ratio > 0) { - // If bridge flow was requested, calculate the bridge width. - height = w = (bridge_flow_ratio == 1.) ? - // optimization to avoid sqrt() - nozzle_diameter : - sqrt(bridge_flow_ratio) * nozzle_diameter; - } else if (! width.percent && width.value == 0.) { + if (! width.percent && width.value == 0.) { // If user left option to 0, calculate a sane default width. w = auto_extrusion_width(role, nozzle_diameter); } else { @@ -143,71 +136,89 @@ Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent w = float(width.get_abs_value(height)); } - return Flow(w, height, nozzle_diameter, bridge_flow_ratio > 0); + return Flow(w, height, rounded_rectangle_extrusion_spacing(w, height), nozzle_diameter, false); } -// This constructor builds a Flow object from a given centerline spacing. -Flow Flow::new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge) +// Adjust extrusion flow for new extrusion line spacing, maintaining the old spacing between extrusions. +Flow Flow::with_spacing(float new_spacing) const { - // we need layer height unless it's a bridge - if (height <= 0 && !bridge) - throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_spacing()"); - // Calculate width from spacing. - // For normal extrusons, extrusion width is wider than the spacing due to the rounding and squishing of the extrusions. - // For bridge extrusions, the extrusions are placed with a tiny BRIDGE_EXTRA_SPACING gaps between the threads. - float width = float(bridge ? - (spacing - BRIDGE_EXTRA_SPACING) : -#ifdef HAS_PERIMETER_LINE_OVERLAP - (spacing + PERIMETER_LINE_OVERLAP_FACTOR * height * (1. - 0.25 * PI)); -#else - (spacing + height * (1. - 0.25 * PI))); -#endif - return Flow(width, bridge ? width : height, nozzle_diameter, bridge); + Flow out = *this; + if (m_bridge) { + // Diameter of the rounded extrusion. + assert(m_width == m_height); + float gap = m_spacing - m_width; + auto new_diameter = new_spacing - gap; + out.m_width = out.m_height = new_diameter; + } else { + assert(m_width >= m_height); + out.m_width += new_spacing - m_spacing; + if (out.m_width < out.m_height) + throw Slic3r::InvalidArgument("Invalid spacing supplied to Flow::with_spacing()"); + } + out.m_spacing = new_spacing; + return out; } -// This method returns the centerline spacing between two adjacent extrusions -// having the same extrusion width (and other properties). -float Flow::spacing() const +// Adjust the width / height of a rounded extrusion model to reach the prescribed cross section area while maintaining extrusion spacing. +Flow Flow::with_cross_section(float area_new) const { -#ifdef HAS_PERIMETER_LINE_OVERLAP - if (this->bridge) - return this->width + BRIDGE_EXTRA_SPACING; - // rectangle with semicircles at the ends - float min_flow_spacing = this->width - this->height * (1. - 0.25 * PI); - float res = this->width - PERIMETER_LINE_OVERLAP_FACTOR * (this->width - min_flow_spacing); -#else - 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 FlowErrorNegativeSpacing(); - return res; + assert(! m_bridge); + assert(m_width >= m_height); + + // Adjust for bridge_flow_ratio, maintain the extrusion spacing. + float area = this->mm3_per_mm(); + if (area_new > area + EPSILON) { + // Increasing the flow rate. + float new_full_spacing = area_new / m_height; + if (new_full_spacing > m_spacing) { + // Filling up the spacing without an air gap. Grow the extrusion in height. + float height = area_new / m_spacing; + return Flow(rounded_rectangle_extrusion_width_from_spacing(m_spacing, height), height, m_spacing, m_nozzle_diameter, false); + } else { + return this->with_width(rounded_rectangle_extrusion_width_from_spacing(area / m_height, m_height)); + } + } else if (area_new < area - EPSILON) { + // Decreasing the flow rate. + float width_new = m_width - (area - area_new) / m_height; + assert(width_new > 0); + if (width_new > m_height) { + // Shrink the extrusion width. + return this->with_width(width_new); + } else { + // Create a rounded extrusion. + auto dmr = float(sqrt(area_new / M_PI)); + return Flow(dmr, dmr, m_spacing, m_nozzle_diameter, false); + } + } else + return *this; } -// This method returns the centerline spacing between an extrusion using this -// flow and another one using another flow. -// this->spacing(other) shall return the same value as other.spacing(*this) -float Flow::spacing(const Flow &other) const +float Flow::rounded_rectangle_extrusion_spacing(float width, float height) { - assert(this->height == other.height); - assert(this->bridge == other.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 FlowErrorNegativeSpacing(); - return res; + auto out = width - height * float(1. - 0.25 * PI); + if (out <= 0.f) + throw FlowErrorNegativeSpacing(); + return out; +} + +float Flow::rounded_rectangle_extrusion_width_from_spacing(float spacing, float height) +{ + return float(spacing + height * (1. - 0.25 * PI)); +} + +float Flow::bridge_extrusion_spacing(float dmr) +{ + return dmr + BRIDGE_EXTRA_SPACING; } // This method returns extrusion volume per head move unit. -double Flow::mm3_per_mm() const +double Flow::mm3_per_mm() const { - float res = this->bridge ? + float res = m_bridge ? // Area of a circle with dmr of this->width. - float((this->width * this->width) * 0.25 * PI) : + float((m_width * m_width) * 0.25 * PI) : // Rectangle with semicircles at the ends. ~ h (w - 0.215 h) - float(this->height * (this->width - this->height * (1. - 0.25 * PI))); + float(m_height * (m_width - m_height * (1. - 0.25 * PI))); //assert(res > 0.); if (res <= 0.) throw FlowErrorNegativeFlow(); @@ -222,9 +233,7 @@ Flow support_material_flow(const PrintObject *object, float layer_height) (object->config().support_material_extrusion_width.value > 0) ? object->config().support_material_extrusion_width : object->config().extrusion_width, // if object->config().support_material_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component. float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_extruder-1)), - (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value), - // bridge_flow_ratio - 0.f); + (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value)); } Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height) @@ -235,9 +244,7 @@ Flow support_material_1st_layer_flow(const PrintObject *object, float layer_heig // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution. (width.value > 0) ? width : object->config().extrusion_width, float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_extruder-1)), - (layer_height > 0.f) ? layer_height : float(object->config().first_layer_height.get_abs_value(object->config().layer_height.value)), - // bridge_flow_ratio - 0.f); + (layer_height > 0.f) ? layer_height : float(object->config().first_layer_height.get_abs_value(object->config().layer_height.value))); } Flow support_material_interface_flow(const PrintObject *object, float layer_height) @@ -248,9 +255,7 @@ Flow support_material_interface_flow(const PrintObject *object, float layer_heig (object->config().support_material_extrusion_width > 0) ? object->config().support_material_extrusion_width : object->config().extrusion_width, // if object->config().support_material_interface_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component. float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_interface_extruder-1)), - (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value), - // bridge_flow_ratio - 0.f); + (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value)); } } diff --git a/src/libslic3r/Flow.hpp b/src/libslic3r/Flow.hpp index 9e57ce9079..04ced3e13a 100644 --- a/src/libslic3r/Flow.hpp +++ b/src/libslic3r/Flow.hpp @@ -13,11 +13,6 @@ class PrintObject; // Extra spacing of bridge threads, in mm. #define BRIDGE_EXTRA_SPACING 0.05 -// Overlap factor of perimeter lines. Currently no overlap. -#ifdef HAS_PERIMETER_LINE_OVERLAP - #define PERIMETER_LINE_OVERLAP_FACTOR 1.0 -#endif - enum FlowRole { frExternalPerimeter, frPerimeter, @@ -56,26 +51,26 @@ public: class Flow { public: + Flow() = default; + Flow(float width, float height, float nozzle_diameter) : + Flow(width, height, rounded_rectangle_extrusion_spacing(width, height), nozzle_diameter, false) {} + // Non bridging flow: Maximum width of an extrusion with semicircles at the ends. // Bridging flow: Bridge thread diameter. - float width; + float width() const { return m_width; } + coord_t scaled_width() const { return coord_t(scale_(m_width)); } // Non bridging flow: Layer height. // Bridging flow: Bridge thread diameter = layer height. - float height; + float height() const { return m_height; } + // Spacing between the extrusion centerlines. + float spacing() const { return m_spacing; } + coord_t scaled_spacing() const { return coord_t(scale_(m_spacing)); } // Nozzle diameter. - float nozzle_diameter; + float nozzle_diameter() const { return m_nozzle_diameter; } // Is it a bridge? - bool bridge; - - Flow(float _w, float _h, float _nd, bool _bridge = false) : - width(_w), height(_h), nozzle_diameter(_nd), bridge(_bridge) {} - - float spacing() const; - float spacing(const Flow &other) const; - double mm3_per_mm() const; - coord_t scaled_width() const { return coord_t(scale_(this->width)); } - coord_t scaled_spacing() const { return coord_t(scale_(this->spacing())); } - coord_t scaled_spacing(const Flow &other) const { return coord_t(scale_(this->spacing(other))); } + bool bridge() const { return m_bridge; } + // Cross section area of the extrusion. + double mm3_per_mm() const; // Elephant foot compensation spacing to be used to detect narrow parts, where the elephant foot compensation cannot be applied. // To be used on frExternalPerimeter only. @@ -83,13 +78,32 @@ public: // Here an overlap of 0.2x external perimeter spacing is allowed for by the elephant foot compensation. coord_t scaled_elephant_foot_spacing() const { return coord_t(0.5f * float(this->scaled_width() + 0.6f * this->scaled_spacing())); } - bool operator==(const Flow &rhs) const { return this->width == rhs.width && this->height == rhs.height && this->nozzle_diameter == rhs.nozzle_diameter && this->bridge == rhs.bridge; } - - static Flow new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio); - // Create a flow from the spacing of extrusion lines. - // This method is used exclusively to calculate new flow of 100% infill, where the extrusion width was allowed to scale - // to fit a region with integer number of lines. - static Flow new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge); + bool operator==(const Flow &rhs) const { return m_width == rhs.m_width && m_height == rhs.m_height && m_nozzle_diameter == rhs.m_nozzle_diameter && m_bridge == rhs.m_bridge; } + + Flow with_width (float width) const { + assert(! m_bridge); + return Flow(width, m_height, rounded_rectangle_extrusion_spacing(width, m_height), m_nozzle_diameter, m_bridge); + } + Flow with_height(float height) const { + assert(! m_bridge); + return Flow(m_width, height, rounded_rectangle_extrusion_spacing(m_width, height), m_nozzle_diameter, m_bridge); + } + // Adjust extrusion flow for new extrusion line spacing, maintaining the old spacing between extrusions. + Flow with_spacing(float spacing) const; + // Adjust the width / height of a rounded extrusion model to reach the prescribed cross section area while maintaining extrusion spacing. + Flow with_cross_section(float area) const; + Flow with_flow_ratio(double ratio) const { return this->with_cross_section(this->mm3_per_mm() * ratio); } + + static Flow bridging_flow(float dmr, float nozzle_diameter) { return Flow { dmr, dmr, bridge_extrusion_spacing(dmr), nozzle_diameter, true }; } + + static Flow new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height); + + // Spacing of extrusions with rounded extrusion model. + static float rounded_rectangle_extrusion_spacing(float width, float height); + // Width of extrusions with rounded extrusion model. + static float rounded_rectangle_extrusion_width_from_spacing(float spacing, float height); + // Spacing of round thread extrusions. + static float bridge_extrusion_spacing(float dmr); // Sane extrusion width defautl based on nozzle diameter. // The defaults were derived from manual Prusa MK3 profiles. @@ -100,6 +114,20 @@ public: // on active extruder etc. Therefore the value calculated by this function shall be used as a hint only. static double extrusion_width(const std::string &opt_key, const ConfigOptionFloatOrPercent *opt, const ConfigOptionResolver &config, const unsigned int first_printing_extruder = 0); static double extrusion_width(const std::string &opt_key, const ConfigOptionResolver &config, const unsigned int first_printing_extruder = 0); + +private: + Flow(float width, float height, float spacing, float nozzle_diameter, bool bridge) : + m_width(width), m_height(height), m_spacing(spacing), m_nozzle_diameter(nozzle_diameter), m_bridge(bridge) + { + // Gap fill violates this condition. + //assert(width >= height); + } + + float m_width { 0 }; + float m_height { 0 }; + float m_spacing { 0 }; + float m_nozzle_diameter { 0 }; + bool m_bridge { false }; }; extern Flow support_material_flow(const PrintObject *object, float layer_height = 0.f); diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index e10b26f383..152d72079f 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -2014,9 +2014,10 @@ namespace Slic3r { typedef std::map IdToObjectDataMap; bool m_fullpath_sources{ true }; + bool m_zip64 { true }; public: - bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data = nullptr); + bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64); private: bool _save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data); @@ -2036,10 +2037,11 @@ namespace Slic3r { bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config); }; - bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data) + bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64) { clear_errors(); m_fullpath_sources = fullpath_sources; + m_zip64 = zip64; return _save_model_to_file(filename, model, config, thumbnail_data); } @@ -2233,9 +2235,13 @@ namespace Slic3r { { mz_zip_writer_staged_context context; if (!mz_zip_writer_add_staged_open(&archive, &context, MODEL_FILE.c_str(), - // Maximum expected and allowed 3MF file size is 16GiB. - // This switches the ZIP file to a 64bit mode, which adds a tiny bit of overhead to file records. - (uint64_t(1) << 30) * 16, + m_zip64 ? + // Maximum expected and allowed 3MF file size is 16GiB. + // This switches the ZIP file to a 64bit mode, which adds a tiny bit of overhead to file records. + (uint64_t(1) << 30) * 16 : + // Maximum expected 3MF file size is 4GB-1. This is a workaround for interoperability with Windows 10 3D model fixing API, see + // GH issue #6193. + (uint64_t(1) << 32) - 1, nullptr, nullptr, 0, MZ_DEFAULT_COMPRESSION, nullptr, 0, nullptr, 0)) { add_error("Unable to add model file to archive"); return false; @@ -2926,13 +2932,13 @@ bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool c return res; } -bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data) +bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64) { if (path == nullptr || model == nullptr) return false; _3MF_Exporter exporter; - bool res = exporter.save_model_to_file(path, *model, config, fullpath_sources, thumbnail_data); + bool res = exporter.save_model_to_file(path, *model, config, fullpath_sources, thumbnail_data, zip64); if (!res) exporter.log_errors(); diff --git a/src/libslic3r/Format/3mf.hpp b/src/libslic3r/Format/3mf.hpp index ccfd9356d8..a09a1b8344 100644 --- a/src/libslic3r/Format/3mf.hpp +++ b/src/libslic3r/Format/3mf.hpp @@ -33,7 +33,7 @@ namespace Slic3r { // 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 - extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data = nullptr); + extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data = nullptr, bool zip64 = true); } // namespace Slic3r diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index f048551826..64cb8b8154 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -87,7 +87,7 @@ PNGBuffer read_png(const mz_zip_archive_file_stat &entry, } ArchiveData extract_sla_archive(const std::string &zipfname, - const std::string &exclude) + const std::string &exclude) { ArchiveData arch; diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 26a2062475..a799408109 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -285,6 +286,7 @@ namespace Slic3r { 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)); + config.set_key_value("toolchange_z", new ConfigOptionFloat(z)); // config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); toolchange_gcode_str = gcodegen.placeholder_parser_process("toolchange_gcode", toolchange_gcode, new_extruder_id, &config); check_add_eol(toolchange_gcode_str); @@ -433,19 +435,18 @@ namespace Slic3r { { std::string gcode; assert(m_layer_idx >= 0); - if (!m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { + if (gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { if (m_layer_idx < (int)m_tool_changes.size()) { if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size())) throw Slic3r::RuntimeError("Wipe tower generation failed, possibly due to empty first layer."); - // Calculate where the wipe tower layer will be printed. -1 means that print z will not change, // resulting in a wipe tower with sparse layers. double wipe_tower_z = -1; bool ignore_sparse = false; if (gcodegen.config().wipe_tower_no_sparse_layers.value) { wipe_tower_z = m_last_wipe_tower_print_z; - ignore_sparse = (m_brim_done && m_tool_changes[m_layer_idx].size() == 1 && m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool); + ignore_sparse = (m_tool_changes[m_layer_idx].size() == 1 && m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool); if (m_tool_change_idx == 0 && !ignore_sparse) wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes[m_layer_idx].front().layer_height; } @@ -455,7 +456,6 @@ namespace Slic3r { m_last_wipe_tower_print_z = wipe_tower_z; } } - m_brim_done = true; } return gcode; } @@ -755,8 +755,16 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* re BOOST_LOG_TRIVIAL(debug) << "Start processing gcode, " << log_memory_info(); m_processor.process_file(path_tmp, true, [print]() { print->throw_if_canceled(); }); DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics); +#if ENABLE_GCODE_WINDOW + if (result != nullptr) { + *result = std::move(m_processor.extract_result()); + // set the filename to the correct value + result->filename = path; + } +#else if (result != nullptr) *result = std::move(m_processor.extract_result()); +#endif // ENABLE_GCODE_WINDOW BOOST_LOG_TRIVIAL(debug) << "Finished processing gcode, " << log_memory_info(); if (rename_file(path_tmp, path)) @@ -776,7 +784,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* re namespace DoExport { static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor, bool& silent_time_estimator_enabled) { - silent_time_estimator_enabled = (config.gcode_flavor == gcfMarlin) && config.silent_mode; + silent_time_estimator_enabled = (config.gcode_flavor == gcfMarlinLegacy || config.gcode_flavor == gcfMarlinFirmware) + && config.silent_mode; processor.reset(); processor.apply_config(config); processor.enable_stealth_time_estimator(silent_time_estimator_enabled); @@ -1104,15 +1113,15 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu const double layer_height = first_object->config().layer_height.value; const double first_layer_height = first_object->config().first_layer_height.get_abs_value(layer_height); for (const PrintRegion* region : print.regions()) { - _write_format(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(frExternalPerimeter, layer_height, false, false, -1., *first_object).width); - _write_format(file, "; perimeters extrusion width = %.2fmm\n", region->flow(frPerimeter, layer_height, false, false, -1., *first_object).width); - _write_format(file, "; infill extrusion width = %.2fmm\n", region->flow(frInfill, layer_height, false, false, -1., *first_object).width); - _write_format(file, "; solid infill extrusion width = %.2fmm\n", region->flow(frSolidInfill, layer_height, false, false, -1., *first_object).width); - _write_format(file, "; top infill extrusion width = %.2fmm\n", region->flow(frTopSolidInfill, layer_height, false, false, -1., *first_object).width); + _write_format(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(*first_object, frExternalPerimeter, layer_height).width()); + _write_format(file, "; perimeters extrusion width = %.2fmm\n", region->flow(*first_object, frPerimeter, layer_height).width()); + _write_format(file, "; infill extrusion width = %.2fmm\n", region->flow(*first_object, frInfill, layer_height).width()); + _write_format(file, "; solid infill extrusion width = %.2fmm\n", region->flow(*first_object, frSolidInfill, layer_height).width()); + _write_format(file, "; top infill extrusion width = %.2fmm\n", region->flow(*first_object, frTopSolidInfill, layer_height).width()); if (print.has_support_material()) - _write_format(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width); + _write_format(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width()); if (print.config().first_layer_extrusion_width.value > 0) - _write_format(file, "; first layer extrusion width = %.2fmm\n", region->flow(frPerimeter, first_layer_height, false, true, -1., *first_object).width); + _write_format(file, "; first layer extrusion width = %.2fmm\n", region->flow(*first_object, frPerimeter, first_layer_height, true).width()); _write_format(file, "\n"); } print.throw_if_canceled(); @@ -1128,6 +1137,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // 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(); + m_placeholder_parser_context.rng = std::mt19937(std::chrono::high_resolution_clock::now().time_since_epoch().count()); print.update_object_placeholders(m_placeholder_parser.config_writable(), ".gcode"); // Get optimal tool ordering to minimize tool switches of a multi-exruder print. @@ -1346,7 +1356,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu bbox_prime.offset(0.5f); bool overlap = bbox_prime.overlap(bbox_print); - if (print.config().gcode_flavor == gcfMarlin) { + if (print.config().gcode_flavor == gcfMarlinLegacy || print.config().gcode_flavor == gcfMarlinFirmware) { _write(file, this->retract()); _write(file, "M300 S800 P500\n"); // Beep for 500ms, tone 800Hz. if (overlap) { @@ -1549,7 +1559,8 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc // Do not process this piece of G-code by the time estimator, it already knows the values through another sources. void GCode::print_machine_envelope(FILE *file, Print &print) { - if (print.config().gcode_flavor.value == gcfMarlin && print.config().machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) { + if ((print.config().gcode_flavor.value == gcfMarlinLegacy || print.config().gcode_flavor.value == gcfMarlinFirmware) + && print.config().machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) { fprintf(file, "M201 X%d Y%d Z%d E%d ; sets maximum accelerations, mm/sec^2\n", int(print.config().machine_max_acceleration_x.values.front() + 0.5), int(print.config().machine_max_acceleration_y.values.front() + 0.5), @@ -1560,10 +1571,20 @@ void GCode::print_machine_envelope(FILE *file, Print &print) int(print.config().machine_max_feedrate_y.values.front() + 0.5), int(print.config().machine_max_feedrate_z.values.front() + 0.5), int(print.config().machine_max_feedrate_e.values.front() + 0.5)); + + // Now M204 - acceleration. This one is quite hairy thanks to how Marlin guys care about + // backwards compatibility: https://github.com/prusa3d/PrusaSlicer/issues/1089 + // Legacy Marlin should export travel acceleration the same as printing acceleration. + // MarlinFirmware has the two separated. + int travel_acc = print.config().gcode_flavor == gcfMarlinLegacy + ? int(print.config().machine_max_acceleration_extruding.values.front() + 0.5) + : int(print.config().machine_max_acceleration_travel.values.front() + 0.5); fprintf(file, "M204 P%d R%d T%d ; sets acceleration (P, T) and retract acceleration (R), mm/sec^2\n", int(print.config().machine_max_acceleration_extruding.values.front() + 0.5), int(print.config().machine_max_acceleration_retracting.values.front() + 0.5), - int(print.config().machine_max_acceleration_extruding.values.front() + 0.5)); + travel_acc); + + fprintf(file, "M205 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/sec\n", print.config().machine_max_jerk_x.values.front(), print.config().machine_max_jerk_y.values.front(), @@ -1705,7 +1726,9 @@ namespace ProcessLayer { static std::string emit_custom_gcode_per_print_z( + GCode &gcodegen, const CustomGCode::Item *custom_gcode, + unsigned int current_extruder_id, // ID of the first extruder printing this layer. unsigned int first_extruder_id, const PrintConfig &config) @@ -1746,13 +1769,19 @@ namespace ProcessLayer // && !MMU1 ) { //! FIXME_in_fw show message during print pause - gcode += config.pause_print_gcode;// pause print + DynamicConfig cfg; + cfg.set_key_value("color_change_extruder", new ConfigOptionInt(m600_extruder_before_layer)); + gcode += gcodegen.placeholder_parser_process("pause_print_gcode", config.pause_print_gcode, current_extruder_id, &cfg); gcode += "\n"; gcode += "M117 Change filament for Extruder " + std::to_string(m600_extruder_before_layer) + "\n"; } else { - gcode += config.color_change_gcode;//ColorChangeCode; + gcode += gcodegen.placeholder_parser_process("color_change_gcode", config.color_change_gcode, current_extruder_id); gcode += "\n"; + //FIXME Tell G-code writer that M600 filled the extruder, thus the G-code writer shall reset the extruder to unretracted state after + // return from M600. Thus the G-code generated by the following line is ignored. + // see GH issue #6362 + gcodegen.writer().unretract(); } } else { @@ -1767,7 +1796,7 @@ namespace ProcessLayer //! FIXME_in_fw show message during print pause if (!pause_print_msg.empty()) gcode += "M117 " + pause_print_msg + "\n"; - gcode += config.pause_print_gcode; + gcode += gcodegen.placeholder_parser_process("pause_print_gcode", config.pause_print_gcode, current_extruder_id); } else { // add tag for processor @@ -1776,8 +1805,8 @@ namespace ProcessLayer #else gcode += ";" + GCodeProcessor::Custom_Code_Tag + "\n"; #endif // ENABLE_VALIDATE_CUSTOM_GCODE - if (gcode_type == CustomGCode::Template) // Template Cistom Gcode - gcode += config.template_custom_gcode; + if (gcode_type == CustomGCode::Template) // Template Custom Gcode + gcode += gcodegen.placeholder_parser_process("template_custom_gcode", config.template_custom_gcode, current_extruder_id); else // custom Gcode gcode += custom_gcode->extra; @@ -1810,7 +1839,8 @@ namespace Skirt { // Extrude skirt at the print_z of the raft layers and normal object layers // not at the print_z of the interlaced support material layers. std::map> skirt_loops_per_extruder_out; - assert(skirt_done.empty()); + //For sequential print, the following test may fail when extruding the 2nd and other objects. + // assert(skirt_done.empty()); if (skirt_done.empty() && print.has_skirt() && ! print.skirt().entities.empty() && layer_tools.has_skirt) { skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out); skirt_done.emplace_back(layer_tools.print_z); @@ -1985,7 +2015,7 @@ void GCode::process_layer( if (single_object_instance_idx == size_t(-1)) { // Normal (non-sequential) print. - gcode += ProcessLayer::emit_custom_gcode_per_print_z(layer_tools.custom_gcode, first_extruder_id, print.config()); + gcode += ProcessLayer::emit_custom_gcode_per_print_z(*this, layer_tools.custom_gcode, m_writer.extruder()->id(), first_extruder_id, print.config()); } // Extrude skirt at the print_z of the raft layers and normal object layers // not at the print_z of the interlaced support material layers. @@ -2165,14 +2195,13 @@ void GCode::process_layer( const std::pair loops = loops_it->second; this->set_origin(0., 0.); m_avoid_crossing_perimeters.use_external_mp(); - Flow layer_skirt_flow(print.skirt_flow()); - layer_skirt_flow.height = float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2])); + Flow layer_skirt_flow = print.skirt_flow().with_height(float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2]))); double mm3_per_mm = layer_skirt_flow.mm3_per_mm(); for (size_t i = loops.first; i < loops.second; ++i) { // Adjust flow according to this layer's layer height. ExtrusionLoop loop = *dynamic_cast(print.skirt().entities[i]); for (ExtrusionPath &path : loop.paths) { - path.height = layer_skirt_flow.height; + path.height = layer_skirt_flow.height(); path.mm3_per_mm = mm3_per_mm; } //FIXME using the support_material_speed of the 1st object printed. @@ -2838,9 +2867,11 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string Polyline travel { this->last_pos(), point }; // check whether a straight travel move would need retraction - bool needs_retraction = this->needs_retraction(travel, role); + bool needs_retraction = this->needs_retraction(travel, role); // check whether wipe could be disabled without causing visible stringing - bool could_be_wipe_disabled = false; + bool could_be_wipe_disabled = false; + // Save state of use_external_mp_once for the case that will be needed to call twice m_avoid_crossing_perimeters.travel_to. + const bool used_external_mp_once = m_avoid_crossing_perimeters.used_external_mp_once(); // if a retraction would be needed, try to use avoid_crossing_perimeters to plan a // multi-hop travel path inside the configuration space @@ -2868,8 +2899,13 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string // Because of it, it is necessary to call avoid crossing perimeters again with new starting point after calling retraction() // FIXME Lukas H.: Try to predict if this second calling of avoid crossing perimeters will be needed or not. It could save computations. if (last_post_before_retract != this->last_pos() && m_config.avoid_crossing_perimeters) { - Polyline retract_travel = m_avoid_crossing_perimeters.travel_to(*this, point); - travel = std::move(retract_travel); + // If in the previous call of m_avoid_crossing_perimeters.travel_to was use_external_mp_once set to true restore this value for next call. + if (used_external_mp_once) + m_avoid_crossing_perimeters.use_external_mp_once(); + travel = m_avoid_crossing_perimeters.travel_to(*this, point); + // If state of use_external_mp_once was changed reset it to right value. + if (used_external_mp_once) + m_avoid_crossing_perimeters.reset_once_modifiers(); } } else // Reset the wipe path when traveling, so one would not wipe along an old path. @@ -2992,6 +3028,7 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) 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)); + config.set_key_value("toolchange_z", new ConfigOptionFloat(print_z)); config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); toolchange_gcode_parsed = placeholder_parser_process("toolchange_gcode", toolchange_gcode, extruder_id, &config); gcode += toolchange_gcode_parsed; diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 51621ed40b..08ab830024 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -75,8 +75,8 @@ public: m_tool_changes(tool_changes), m_final_purge(final_purge), m_layer_idx(-1), - m_tool_change_idx(0), - m_brim_done(false) {} + m_tool_change_idx(0) + {} std::string prime(GCode &gcodegen); void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; } @@ -105,8 +105,6 @@ private: // Current layer index. int m_layer_idx; int m_tool_change_idx; - bool m_brim_done; - bool i_have_brim = false; double m_last_wipe_tower_print_z = 0.f; }; @@ -373,7 +371,7 @@ private: void print_machine_envelope(FILE *file, Print &print); void _print_first_layer_bed_temperature(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); void _print_first_layer_extruder_temperatures(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); - // this flag triggers first layer speeds + // On the first printing layer. This flag triggers first layer speeds. bool on_first_layer() const { return m_layer != nullptr && m_layer->id() == 0; } friend ObjectByExtruder& object_by_extruder( diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index 03c420a32b..d178e3c894 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -18,6 +18,7 @@ public: // Routing around the objects vs. inside a single object. void use_external_mp(bool use = true) { m_use_external_mp = use; }; void use_external_mp_once() { m_use_external_mp_once = true; } + bool used_external_mp_once() { return m_use_external_mp_once; } void disable_once() { m_disabled_once = true; } bool disabled_once() const { return m_disabled_once; } void reset_once_modifiers() { m_use_external_mp_once = false; m_disabled_once = false; } diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index a4769af8e7..7a17909714 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -4,8 +4,14 @@ #include "GCodeProcessor.hpp" #include +#if ENABLE_VALIDATE_CUSTOM_GCODE +#include +#endif // ENABLE_VALIDATE_CUSTOM_GCODE #include #include +#if ENABLE_GCODE_WINDOW +#include +#endif // ENABLE_GCODE_WINDOW #include #include @@ -20,6 +26,7 @@ static const float INCHES_TO_MM = 25.4f; static const float MMMIN_TO_MMSEC = 1.0f / 60.0f; static const float DEFAULT_ACCELERATION = 1500.0f; // Prusa Firmware 1_75mm_MK2 +static const float DEFAULT_TRAVEL_ACCELERATION = 1250.0f; namespace Slic3r { @@ -184,8 +191,13 @@ void GCodeProcessor::TimeMachine::reset() enabled = false; acceleration = 0.0f; max_acceleration = 0.0f; + travel_acceleration = 0.0f; + max_travel_acceleration = 0.0f; extrude_factor_override_percentage = 1.0f; time = 0.0f; +#if ENABLE_EXTENDED_M73_LINES + stop_times = std::vector(); +#endif // ENABLE_EXTENDED_M73_LINES curr.reset(); prev.reset(); gcode_time.reset(); @@ -313,6 +325,13 @@ void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks) layers_time[block.layer_id - 1] += block_time; } g1_times_cache.push_back({ block.g1_line_id, time }); +#if ENABLE_EXTENDED_M73_LINES + // update times for remaining time to printer stop placeholders + auto it_stop_time = std::lower_bound(stop_times.begin(), stop_times.end(), block.g1_line_id, + [](const StopTime& t, unsigned int value) { return t.g1_line_id < value; }); + if (it_stop_time != stop_times.end() && it_stop_time->g1_line_id == block.g1_line_id) + it_stop_time->elapsed_time = time; +#endif // ENABLE_EXTENDED_M73_LINES } if (keep_last_n_blocks) @@ -355,7 +374,16 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) return int(::roundf(time_in_seconds / 60.0f)); }; +#if ENABLE_EXTENDED_M73_LINES + auto time_in_last_minute = [](float time_in_seconds) { + assert(time_in_seconds <= 60.0f); + return time_in_seconds / 60.0f; + }; + + auto format_line_M73_main = [](const std::string& mask, int percent, int time) { +#else auto format_line_M73 = [](const std::string& mask, int percent, int time) { +#endif // ENABLE_EXTENDED_M73_LINES char line_M73[64]; sprintf(line_M73, mask.c_str(), std::to_string(percent).c_str(), @@ -363,14 +391,47 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) return std::string(line_M73); }; +#if ENABLE_EXTENDED_M73_LINES + auto format_line_M73_stop_int = [](const std::string& mask, int time) { + char line_M73[64]; + sprintf(line_M73, mask.c_str(), std::to_string(time).c_str()); + return std::string(line_M73); + }; + + auto format_time_float = [](float time) { + char time_str[64]; + sprintf(time_str, "%.2f", time); + return std::string(time_str); + }; + + auto format_line_M73_stop_float = [format_time_float](const std::string& mask, float time) { + char line_M73[64]; + sprintf(line_M73, mask.c_str(), format_time_float(time).c_str()); + return std::string(line_M73); + }; +#endif // ENABLE_EXTENDED_M73_LINES + GCodeReader parser; std::string gcode_line; size_t g1_lines_counter = 0; // keeps track of last exported pair +#if ENABLE_EXTENDED_M73_LINES + std::array, static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count)> last_exported_main; + for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + last_exported_main[i] = { 0, time_in_minutes(machines[i].time) }; + } + + // keeps track of last exported remaining time to next printer stop + std::array(PrintEstimatedTimeStatistics::ETimeMode::Count)> last_exported_stop; + for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + last_exported_stop[i] = time_in_minutes(machines[i].time); + } +#else std::array, static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count)> last_exported; for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { last_exported[i] = { 0, time_in_minutes(machines[i].time) }; } +#endif // ENABLE_EXTENDED_M73_LINES // buffer line to export only when greater than 64K to reduce writing calls std::string export_line; @@ -393,12 +454,32 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { const TimeMachine& machine = machines[i]; if (machine.enabled) { +#if ENABLE_EXTENDED_M73_LINES + // export pair + ret += format_line_M73_main(machine.line_m73_main_mask.c_str(), + (line == reserved_tag(ETags::First_Line_M73_Placeholder)) ? 0 : 100, + (line == reserved_tag(ETags::First_Line_M73_Placeholder)) ? time_in_minutes(machine.time) : 0); +#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER + ++extra_lines_count; +#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER + + // export remaining time to next printer stop + if (line == reserved_tag(ETags::First_Line_M73_Placeholder) && !machine.stop_times.empty()) { + int to_export_stop = time_in_minutes(machine.stop_times.front().elapsed_time); + ret += format_line_M73_stop_int(machine.line_m73_stop_mask.c_str(), to_export_stop); + last_exported_stop[i] = to_export_stop; +#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER + ++extra_lines_count; +#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER + } +#else ret += format_line_M73(machine.line_m73_mask.c_str(), (line == reserved_tag(ETags::First_Line_M73_Placeholder)) ? 0 : 100, (line == reserved_tag(ETags::First_Line_M73_Placeholder)) ? time_in_minutes(machine.time) : 0); #if ENABLE_GCODE_LINES_ID_IN_H_SLIDER ++extra_lines_count; #endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER +#endif // ENABLE_EXTENDED_M73_LINES } } } @@ -467,11 +548,23 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { const TimeMachine& machine = machines[i]; if (machine.enabled) { + // export pair // Skip all machine.g1_times_cache below g1_lines_counter. auto& it = g1_times_cache_it[i]; while (it != machine.g1_times_cache.end() && it->id < g1_lines_counter) ++it; if (it != machine.g1_times_cache.end() && it->id == g1_lines_counter) { +#if ENABLE_EXTENDED_M73_LINES + std::pair to_export_main = { int(100.0f * it->elapsed_time / machine.time), + time_in_minutes(machine.time - it->elapsed_time) }; + if (last_exported_main[i] != to_export_main) { + export_line += format_line_M73_main(machine.line_m73_main_mask.c_str(), + to_export_main.first, to_export_main.second); + last_exported_main[i] = to_export_main; +#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER + ++exported_lines_count; +#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER +#else float elapsed_time = it->elapsed_time; std::pair to_export = { int(100.0f * elapsed_time / machine.time), time_in_minutes(machine.time - elapsed_time) }; @@ -482,7 +575,54 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) #if ENABLE_GCODE_LINES_ID_IN_H_SLIDER ++exported_lines_count; #endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER +#endif // ENABLE_EXTENDED_M73_LINES } +#if ENABLE_EXTENDED_M73_LINES + // export remaining time to next printer stop + auto it_stop = std::upper_bound(machine.stop_times.begin(), machine.stop_times.end(), it->elapsed_time, + [](float value, const TimeMachine::StopTime& t) { return value < t.elapsed_time; }); + if (it_stop != machine.stop_times.end()) { + int to_export_stop = time_in_minutes(it_stop->elapsed_time - it->elapsed_time); + if (last_exported_stop[i] != to_export_stop) { + if (to_export_stop > 0) { + if (last_exported_stop[i] != to_export_stop) { + export_line += format_line_M73_stop_int(machine.line_m73_stop_mask.c_str(), to_export_stop); + last_exported_stop[i] = to_export_stop; +#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER + ++exported_lines_count; +#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER + } + } + else { + bool is_last = false; + auto next_it = it + 1; + is_last |= (next_it == machine.g1_times_cache.end()); + + if (next_it != machine.g1_times_cache.end()) { + auto next_it_stop = std::upper_bound(machine.stop_times.begin(), machine.stop_times.end(), next_it->elapsed_time, + [](float value, const TimeMachine::StopTime& t) { return value < t.elapsed_time; }); + is_last |= (next_it_stop != it_stop); + + std::string time_float_str = format_time_float(time_in_last_minute(it_stop->elapsed_time - it->elapsed_time)); + std::string next_time_float_str = format_time_float(time_in_last_minute(it_stop->elapsed_time - next_it->elapsed_time)); + is_last |= (std::stof(time_float_str) > 0.0f && std::stof(next_time_float_str) == 0.0f); + } + + if (is_last) { + if (std::distance(machine.stop_times.begin(), it_stop) == static_cast(machine.stop_times.size() - 1)) + export_line += format_line_M73_stop_int(machine.line_m73_stop_mask.c_str(), to_export_stop); + else + export_line += format_line_M73_stop_float(machine.line_m73_stop_mask.c_str(), time_in_last_minute(it_stop->elapsed_time - it->elapsed_time)); + + last_exported_stop[i] = to_export_stop; +#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER + ++exported_lines_count; +#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER + } + } + } + } +#endif // ENABLE_EXTENDED_M73_LINES } } } @@ -594,12 +734,6 @@ const std::vector> GCodeProces unsigned int GCodeProcessor::s_result_id = 0; #if ENABLE_VALIDATE_CUSTOM_GCODE -static inline bool starts_with(const std::string_view comment, const std::string_view tag) -{ - size_t tag_len = tag.size(); - return comment.size() >= tag_len && comment.substr(0, tag_len) == tag; -} - bool GCodeProcessor::contains_reserved_tag(const std::string& gcode, std::string& found_tag) { bool ret = false; @@ -610,7 +744,7 @@ bool GCodeProcessor::contains_reserved_tag(const std::string& gcode, std::string if (comment.length() > 2 && comment.front() == ';') { comment = comment.substr(1); for (const std::string& s : Reserved_Tags) { - if (starts_with(comment, s)) { + if (boost::starts_with(comment, s)) { ret = true; found_tag = comment; parser.quit_parsing(); @@ -635,7 +769,7 @@ bool GCodeProcessor::contains_reserved_tags(const std::string& gcode, unsigned i if (comment.length() > 2 && comment.front() == ';') { comment = comment.substr(1); for (const std::string& s : Reserved_Tags) { - if (starts_with(comment, s)) { + if (boost::starts_with(comment, s)) { ret = true; found_tag.push_back(comment); if (found_tag.size() == max_count) { @@ -654,8 +788,15 @@ bool GCodeProcessor::contains_reserved_tags(const std::string& gcode, unsigned i GCodeProcessor::GCodeProcessor() { reset(); +#if ENABLE_EXTENDED_M73_LINES + m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].line_m73_main_mask = "M73 P%s R%s\n"; + m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].line_m73_stop_mask = "M73 C%s\n"; + m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].line_m73_main_mask = "M73 Q%s S%s\n"; + m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].line_m73_stop_mask = "M73 D%s\n"; +#else m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].line_m73_mask = "M73 P%s R%s\n"; m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].line_m73_mask = "M73 Q%s S%s\n"; +#endif // ENABLE_EXTENDED_M73_LINES } void GCodeProcessor::apply_config(const PrintConfig& config) @@ -678,13 +819,20 @@ void GCodeProcessor::apply_config(const PrintConfig& config) m_extruder_colors[i] = static_cast(i); } + m_extruder_temps.resize(extruders_count); + m_filament_diameters.resize(config.filament_diameter.values.size()); for (size_t i = 0; i < config.filament_diameter.values.size(); ++i) { m_filament_diameters[i] = static_cast(config.filament_diameter.values[i]); } - if (m_flavor == gcfMarlin && config.machine_limits_usage.value != MachineLimitsUsage::Ignore) + if ((m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) && config.machine_limits_usage.value != MachineLimitsUsage::Ignore) { m_time_processor.machine_limits = reinterpret_cast(config); + if (m_flavor == gcfMarlinLegacy) { + // Legacy Marlin does not have separate travel acceleration, it uses the 'extruding' value instead. + m_time_processor.machine_limits.machine_max_acceleration_travel = m_time_processor.machine_limits.machine_max_acceleration_extruding; + } + } // Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful. // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they @@ -702,6 +850,9 @@ void GCodeProcessor::apply_config(const PrintConfig& config) float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); m_time_processor.machines[i].max_acceleration = max_acceleration; m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; + float max_travel_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_travel, i); + m_time_processor.machines[i].max_travel_acceleration = max_travel_acceleration; + m_time_processor.machines[i].travel_acceleration = (max_travel_acceleration > 0.0f) ? max_travel_acceleration : DEFAULT_TRAVEL_ACCELERATION; } m_time_processor.export_remaining_time_enabled = config.remaining_times.value; @@ -776,6 +927,8 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) m_extruder_colors[i] = static_cast(i); } + m_extruder_temps.resize(m_result.extruders_count); + const ConfigOptionFloats* filament_load_time = config.option("filament_load_time"); if (filament_load_time != nullptr) { m_time_processor.filament_load_times.resize(filament_load_time->values.size()); @@ -792,7 +945,7 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) } } - if (m_flavor == gcfMarlin) { + if (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) { const ConfigOptionFloats* machine_max_acceleration_x = config.option("machine_max_acceleration_x"); if (machine_max_acceleration_x != nullptr) m_time_processor.machine_limits.machine_max_acceleration_x.values = machine_max_acceleration_x->values; @@ -849,6 +1002,15 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) if (machine_max_acceleration_retracting != nullptr) m_time_processor.machine_limits.machine_max_acceleration_retracting.values = machine_max_acceleration_retracting->values; + + // Legacy Marlin does not have separate travel acceleration, it uses the 'extruding' value instead. + const ConfigOptionFloats* machine_max_acceleration_travel = config.option(m_flavor == gcfMarlinLegacy + ? "machine_max_acceleration_extruding" + : "machine_max_acceleration_travel"); + if (machine_max_acceleration_travel != nullptr) + m_time_processor.machine_limits.machine_max_acceleration_travel.values = machine_max_acceleration_travel->values; + + const ConfigOptionFloats* machine_min_extruding_rate = config.option("machine_min_extruding_rate"); if (machine_min_extruding_rate != nullptr) m_time_processor.machine_limits.machine_min_extruding_rate.values = machine_min_extruding_rate->values; @@ -862,6 +1024,9 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); m_time_processor.machines[i].max_acceleration = max_acceleration; m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; + float max_travel_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_travel, i); + m_time_processor.machines[i].max_travel_acceleration = max_travel_acceleration; + m_time_processor.machines[i].travel_acceleration = (max_travel_acceleration > 0.0f) ? max_travel_acceleration : DEFAULT_TRAVEL_ACCELERATION; } if (m_time_processor.machine_limits.machine_max_acceleration_x.values.size() > 1) @@ -910,6 +1075,10 @@ void GCodeProcessor::reset() for (size_t i = 0; i < Min_Extruder_Count; ++i) { m_extruder_colors[i] = static_cast(i); } + m_extruder_temps.resize(Min_Extruder_Count); + for (size_t i = 0; i < Min_Extruder_Count; ++i) { + m_extruder_temps[i] = 0.0f; + } m_filament_diameters = std::vector(Min_Extruder_Count, 1.75f); m_extruded_last_z = 0.0f; @@ -966,9 +1135,14 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr config.load_from_gcode_file(filename, false); apply_config(config); } + else if (m_producer == EProducer::Simplify3D) + apply_config_simplify3d(filename); } // process gcode +#if ENABLE_GCODE_WINDOW + m_result.filename = filename; +#endif // ENABLE_GCODE_WINDOW m_result.id = ++s_result_id; // 1st move must be a dummy move m_result.moves.emplace_back(MoveVertex()); @@ -1081,6 +1255,59 @@ std::vector GCodeProcessor::get_layers_time(PrintEstimatedTimeStatistics: std::vector(); } +void GCodeProcessor::apply_config_simplify3d(const std::string& filename) +{ + struct BedSize + { + double x{ 0.0 }; + double y{ 0.0 }; + + bool is_defined() const { return x > 0.0 && y > 0.0; } + }; + + BedSize bed_size; + + m_parser.parse_file(filename, [this, &bed_size](GCodeReader& reader, const GCodeReader::GCodeLine& line) { + auto extract_float = [](const std::string& cmt, const std::string& key, double& out) { + size_t pos = cmt.find(key); + if (pos != cmt.npos) { + pos = cmt.find(',', pos); + if (pos != cmt.npos) { + out = std::stod(cmt.substr(pos + 1)); + return true; + } + } + return false; + }; + + const std::string& comment = line.raw(); + if (comment.length() > 2 && comment.front() == ';') { + if (bed_size.x == 0.0) + extract_float(comment, "strokeXoverride", bed_size.x); + if (bed_size.y == 0.0) + extract_float(comment, "strokeYoverride", bed_size.y); + + // check for early exit + if (bed_size.is_defined()) { +#if ENABLE_VALIDATE_CUSTOM_GCODE + m_parser.quit_parsing(); +#else + m_parser.quit_parsing_file(); +#endif // ENABLE_VALIDATE_CUSTOM_GCODE + } + } + }); + + if (bed_size.is_defined()) { + m_result.bed_shape = { + { 0.0, 0.0 }, + { bed_size.x, 0.0 }, + { bed_size.x, bed_size.y }, + { 0.0, bed_size.y } + }; + } +} + void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) { /* std::cout << line.raw() << std::endl; */ @@ -1109,6 +1336,7 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) case 21: { process_G21(line); break; } // Set Units to Millimeters case 22: { process_G22(line); break; } // Firmware controlled retract case 23: { process_G23(line); break; } // Firmware controlled unretract + case 28: { process_G28(line); break; } // Move to origin case 90: { process_G90(line); break; } // Set to Absolute Positioning case 91: { process_G91(line); break; } // Set to Relative Positioning case 92: { process_G92(line); break; } // Set Position @@ -1123,9 +1351,11 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) case 1: { process_M1(line); break; } // Sleep or Conditional stop case 82: { process_M82(line); break; } // Set extruder to absolute mode case 83: { process_M83(line); break; } // Set extruder to relative mode + case 104: { process_M104(line); break; } // Set extruder temperature case 106: { process_M106(line); break; } // Set fan speed case 107: { process_M107(line); break; } // Disable fan case 108: { process_M108(line); break; } // Set tool (Sailfish) + case 109: { process_M109(line); break; } // Set extruder temperature and wait case 132: { process_M132(line); break; } // Recall stored home offsets case 135: { process_M135(line); break; } // Set tool (MakerWare) case 201: { process_M201(line); break; } // Set max printing acceleration @@ -1158,14 +1388,6 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) } } -#if !ENABLE_VALIDATE_CUSTOM_GCODE -static inline bool starts_with(const std::string_view comment, const std::string_view tag) -{ - size_t tag_len = tag.size(); - return comment.size() >= tag_len && comment.substr(0, tag_len) == tag; -} -#endif // !ENABLE_VALIDATE_CUSTOM_GCODE - #if __has_include() template struct is_from_chars_convertible : std::false_type {}; @@ -1219,37 +1441,37 @@ void GCodeProcessor::process_tags(const std::string_view comment) #if ENABLE_VALIDATE_CUSTOM_GCODE // extrusion role tag - if (starts_with(comment, reserved_tag(ETags::Role))) { + if (boost::starts_with(comment, reserved_tag(ETags::Role))) { m_extrusion_role = ExtrusionEntity::string_to_role(comment.substr(reserved_tag(ETags::Role).length())); return; } // wipe start tag - if (starts_with(comment, reserved_tag(ETags::Wipe_Start))) { + if (boost::starts_with(comment, reserved_tag(ETags::Wipe_Start))) { m_wiping = true; return; } // wipe end tag - if (starts_with(comment, reserved_tag(ETags::Wipe_End))) { + if (boost::starts_with(comment, reserved_tag(ETags::Wipe_End))) { m_wiping = false; return; } #else // extrusion role tag - if (starts_with(comment, Extrusion_Role_Tag)) { + if (boost::starts_with(comment, Extrusion_Role_Tag)) { m_extrusion_role = ExtrusionEntity::string_to_role(comment.substr(Extrusion_Role_Tag.length())); return; } // wipe start tag - if (starts_with(comment, Wipe_Start_Tag)) { + if (boost::starts_with(comment, Wipe_Start_Tag)) { m_wiping = true; return; } // wipe end tag - if (starts_with(comment, Wipe_End_Tag)) { + if (boost::starts_with(comment, Wipe_End_Tag)) { m_wiping = false; return; } @@ -1258,26 +1480,26 @@ void GCodeProcessor::process_tags(const std::string_view comment) if (!m_producers_enabled || m_producer == EProducer::PrusaSlicer) { #if ENABLE_VALIDATE_CUSTOM_GCODE // height tag - if (starts_with(comment, reserved_tag(ETags::Height))) { + if (boost::starts_with(comment, reserved_tag(ETags::Height))) { if (!parse_number(comment.substr(reserved_tag(ETags::Height).size()), m_forced_height)) BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; return; } // width tag - if (starts_with(comment, reserved_tag(ETags::Width))) { + if (boost::starts_with(comment, reserved_tag(ETags::Width))) { if (!parse_number(comment.substr(reserved_tag(ETags::Width).size()), m_forced_width)) BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; return; } #else // height tag - if (starts_with(comment, Height_Tag)) { + if (boost::starts_with(comment, Height_Tag)) { if (!parse_number(comment.substr(Height_Tag.size()), m_forced_height)) BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; return; } // width tag - if (starts_with(comment, Width_Tag)) { + if (boost::starts_with(comment, Width_Tag)) { if (!parse_number(comment.substr(Width_Tag.size()), m_forced_width)) BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; return; @@ -1287,9 +1509,9 @@ void GCodeProcessor::process_tags(const std::string_view comment) #if ENABLE_VALIDATE_CUSTOM_GCODE // color change tag - if (starts_with(comment, reserved_tag(ETags::Color_Change))) { + if (boost::starts_with(comment, reserved_tag(ETags::Color_Change))) { unsigned char extruder_id = 0; - if (starts_with(comment.substr(reserved_tag(ETags::Color_Change).size()), ",T")) { + if (boost::starts_with(comment.substr(reserved_tag(ETags::Color_Change).size()), ",T")) { int eid; if (!parse_number(comment.substr(reserved_tag(ETags::Color_Change).size() + 2), eid) || eid < 0 || eid > 255) { BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Color_Change (" << comment << ")."; @@ -1333,9 +1555,9 @@ void GCodeProcessor::process_tags(const std::string_view comment) } #else // color change tag - if (starts_with(comment, Color_Change_Tag)) { + if (boost::starts_with(comment, Color_Change_Tag)) { unsigned char extruder_id = 0; - if (starts_with(comment.substr(Color_Change_Tag.size()), ",T")) { + if (boost::starts_with(comment.substr(Color_Change_Tag.size()), ",T")) { int eid; if (! parse_number(comment.substr(Color_Change_Tag.size() + 2), eid) || eid < 0 || eid > 255) { BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Color_Change (" << comment << ")."; @@ -1381,7 +1603,7 @@ void GCodeProcessor::process_tags(const std::string_view comment) #if ENABLE_GCODE_VIEWER_DATA_CHECKING // mm3_per_mm print tag - if (starts_with(comment, Mm3_Per_Mm_Tag)) { + if (boost::starts_with(comment, Mm3_Per_Mm_Tag)) { if (! parse_number(comment.substr(Mm3_Per_Mm_Tag.size()), m_mm3_per_mm_compare.last_tag_value)) BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Mm3_Per_Mm (" << comment << ")."; return; @@ -1447,23 +1669,23 @@ bool GCodeProcessor::process_cura_tags(const std::string_view comment) if (pos != comment.npos) { const std::string_view flavor = comment.substr(pos + tag.length()); if (flavor == "BFB") - m_flavor = gcfMarlin; // << ??????????????????????? + m_flavor = gcfMarlinLegacy; // is this correct ? else if (flavor == "Mach3") m_flavor = gcfMach3; else if (flavor == "Makerbot") m_flavor = gcfMakerWare; else if (flavor == "UltiGCode") - m_flavor = gcfMarlin; // << ??????????????????????? + m_flavor = gcfMarlinLegacy; // is this correct ? else if (flavor == "Marlin(Volumetric)") - m_flavor = gcfMarlin; // << ??????????????????????? + m_flavor = gcfMarlinLegacy; // is this correct ? else if (flavor == "Griffin") - m_flavor = gcfMarlin; // << ??????????????????????? + m_flavor = gcfMarlinLegacy; // is this correct ? else if (flavor == "Repetier") m_flavor = gcfRepetier; else if (flavor == "RepRap") m_flavor = gcfRepRapFirmware; else if (flavor == "Marlin") - m_flavor = gcfMarlin; + m_flavor = gcfMarlinLegacy; else BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown flavor: " << flavor; @@ -1485,89 +1707,109 @@ bool GCodeProcessor::process_simplify3d_tags(const std::string_view comment) { // extrusion roles + // in older versions the comments did not contain the key 'feature' + std::string_view cmt = comment; + size_t pos = cmt.find(" feature"); + if (pos == 0) + cmt.remove_prefix(8); + // ; skirt - size_t pos = comment.find(" skirt"); + pos = cmt.find(" skirt"); if (pos == 0) { m_extrusion_role = erSkirt; return true; } // ; outer perimeter - pos = comment.find(" outer perimeter"); + pos = cmt.find(" outer perimeter"); if (pos == 0) { m_extrusion_role = erExternalPerimeter; return true; } // ; inner perimeter - pos = comment.find(" inner perimeter"); + pos = cmt.find(" inner perimeter"); if (pos == 0) { m_extrusion_role = erPerimeter; return true; } // ; gap fill - pos = comment.find(" gap fill"); + pos = cmt.find(" gap fill"); if (pos == 0) { m_extrusion_role = erGapFill; return true; } // ; infill - pos = comment.find(" infill"); + pos = cmt.find(" infill"); if (pos == 0) { m_extrusion_role = erInternalInfill; return true; } // ; solid layer - pos = comment.find(" solid layer"); + pos = cmt.find(" solid layer"); if (pos == 0) { - m_extrusion_role = erNone; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + m_extrusion_role = erSolidInfill; return true; } // ; bridge - pos = comment.find(" bridge"); + pos = cmt.find(" bridge"); if (pos == 0) { m_extrusion_role = erBridgeInfill; return true; } // ; support - pos = comment.find(" support"); + pos = cmt.find(" support"); if (pos == 0) { m_extrusion_role = erSupportMaterial; return true; } + // ; dense support + pos = cmt.find(" dense support"); + if (pos == 0) { + m_extrusion_role = erSupportMaterialInterface; + return true; + } + // ; prime pillar - pos = comment.find(" prime pillar"); + pos = cmt.find(" prime pillar"); if (pos == 0) { m_extrusion_role = erWipeTower; return true; } // ; ooze shield - pos = comment.find(" ooze shield"); + pos = cmt.find(" ooze shield"); if (pos == 0) { - m_extrusion_role = erNone; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + m_extrusion_role = erNone; // Missing mapping return true; } // ; raft - pos = comment.find(" raft"); + pos = cmt.find(" raft"); if (pos == 0) { - m_extrusion_role = erSkirt; + m_extrusion_role = erSupportMaterial; + return true; + } + + // ; internal single extrusion + pos = cmt.find(" internal single extrusion"); + if (pos == 0) { + m_extrusion_role = erNone; // Missing mapping return true; } // geometry // ; tool std::string tag = " tool"; - pos = comment.find(tag); + pos = cmt.find(tag); if (pos == 0) { - const std::string_view data = comment.substr(pos + tag.length()); + const std::string_view data = cmt.substr(pos + tag.length()); std::string h_tag = "H"; size_t h_start = data.find(h_tag); size_t h_end = data.find_first_of(' ', h_start); @@ -1588,10 +1830,10 @@ bool GCodeProcessor::process_simplify3d_tags(const std::string_view comment) // ; layer tag = " layer"; - pos = comment.find(tag); + pos = cmt.find(tag); if (pos == 0) { // skip lines "; layer end" - const std::string_view data = comment.substr(pos + tag.length()); + const std::string_view data = cmt.substr(pos + tag.length()); size_t end_start = data.find("end"); if (end_start == data.npos) ++m_layer_id; @@ -2007,9 +2249,11 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) } // calculates block acceleration - float acceleration = is_extrusion_only_move(delta_pos) ? - get_retract_acceleration(static_cast(i)) : - get_acceleration(static_cast(i)); + float acceleration = + (type == EMoveType::Travel) ? get_travel_acceleration(static_cast(i)) : + (is_extrusion_only_move(delta_pos) ? + get_retract_acceleration(static_cast(i)) : + get_acceleration(static_cast(i))); for (unsigned char a = X; a <= E; ++a) { float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a)); @@ -2148,6 +2392,32 @@ void GCodeProcessor::process_G23(const GCodeReader::GCodeLine& line) store_move_vertex(EMoveType::Unretract); } +void GCodeProcessor::process_G28(const GCodeReader::GCodeLine& line) +{ + std::string_view cmd = line.cmd(); + std::string new_line_raw = { cmd.data(), cmd.size() }; + bool found = false; + if (line.has_x()) { + new_line_raw += " X0"; + found = true; + } + if (line.has_y()) { + new_line_raw += " Y0"; + found = true; + } + if (line.has_z()) { + new_line_raw += " Z0"; + found = true; + } + if (!found) + new_line_raw += " X0 Y0 Z0"; + + GCodeReader::GCodeLine new_gline; + GCodeReader reader; + reader.parse_line(new_line_raw, [&](GCodeReader& reader, const GCodeReader::GCodeLine& gline) { new_gline = gline; }); + process_G1(new_gline); +} + void GCodeProcessor::process_G90(const GCodeReader::GCodeLine& line) { m_global_positioning_type = EPositioningType::Absolute; @@ -2211,6 +2481,13 @@ void GCodeProcessor::process_M83(const GCodeReader::GCodeLine& line) m_e_local_positioning_type = EPositioningType::Relative; } +void GCodeProcessor::process_M104(const GCodeReader::GCodeLine& line) +{ + float new_temp; + if (line.has_value('S', new_temp)) + m_extruder_temps[m_extruder_id] = new_temp; +} + void GCodeProcessor::process_M106(const GCodeReader::GCodeLine& line) { if (!line.has('P')) { @@ -2243,6 +2520,21 @@ void GCodeProcessor::process_M108(const GCodeReader::GCodeLine& line) process_T(cmd.substr(pos)); } +void GCodeProcessor::process_M109(const GCodeReader::GCodeLine& line) +{ + float new_temp; + if (line.has_value('R', new_temp)) { + float val; + if (line.has_value('T', val)) { + size_t eid = static_cast(val); + if (eid < m_extruder_temps.size()) + m_extruder_temps[eid] = new_temp; + } + else + m_extruder_temps[m_extruder_id] = new_temp; + } +} + void GCodeProcessor::process_M132(const GCodeReader::GCodeLine& line) { // This command is used by Makerbot to load the current home position from EEPROM @@ -2308,7 +2600,7 @@ void GCodeProcessor::process_M203(const GCodeReader::GCodeLine& line) // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate // http://smoothieware.org/supported-g-codes - float factor = (m_flavor == gcfMarlin || m_flavor == gcfSmoothie) ? 1.0f : MMMIN_TO_MMSEC; + float factor = (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfSmoothie) ? 1.0f : MMMIN_TO_MMSEC; for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { if (static_cast(i) == PrintEstimatedTimeStatistics::ETimeMode::Normal || @@ -2335,10 +2627,11 @@ void GCodeProcessor::process_M204(const GCodeReader::GCodeLine& line) if (static_cast(i) == PrintEstimatedTimeStatistics::ETimeMode::Normal || m_time_processor.machine_envelope_processing_enabled) { if (line.has_value('S', value)) { - // Legacy acceleration format. This format is used by the legacy Marlin, MK2 or MK3 firmware, - // and it is also generated by Slic3r to control acceleration per extrusion type - // (there is a separate acceleration settings in Slicer for perimeter, first layer etc). + // Legacy acceleration format. This format is used by the legacy Marlin, MK2 or MK3 firmware + // It is also generated by PrusaSlicer to control acceleration per extrusion type + // (perimeters, first layer etc) when 'Marlin (legacy)' flavor is used. set_acceleration(static_cast(i), value); + set_travel_acceleration(static_cast(i), value); if (line.has_value('T', value)) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i, value); } @@ -2348,11 +2641,9 @@ void GCodeProcessor::process_M204(const GCodeReader::GCodeLine& line) set_acceleration(static_cast(i), value); if (line.has_value('R', value)) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i, value); - if (line.has_value('T', value)) { + if (line.has_value('T', value)) // Interpret the T value as the travel acceleration in the new Marlin format. - //FIXME Prusa3D firmware currently does not support travel acceleration value independent from the extruding acceleration value. - // set_travel_acceleration(value); - } + set_travel_acceleration(static_cast(i), value); } } } @@ -2482,7 +2773,7 @@ void GCodeProcessor::process_T(const std::string_view command) int eid = 0; if (! parse_number(command.substr(1), eid) || eid < 0 || eid > 255) { // Specific to the MMU2 V2 (see https://www.help.prusa3d.com/en/article/prusa-specific-g-codes_112173): - if (m_flavor == gcfMarlin && (command == "Tx" || command == "Tc" || command == "T?")) + if ((m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) && (command == "Tx" || command == "Tc" || command == "T?")) return; // T-1 is a valid gcode line for RepRap Firmwares (used to deselects all tools) see https://github.com/prusa3d/PrusaSlicer/issues/5677 @@ -2518,7 +2809,7 @@ void GCodeProcessor::store_move_vertex(EMoveType type) { MoveVertex vertex = { #if ENABLE_GCODE_LINES_ID_IN_H_SLIDER - m_line_id, + (type == EMoveType::Color_change || type == EMoveType::Pause_Print || type == EMoveType::Custom_GCode) ? m_line_id + 1 : m_line_id, #endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER type, m_extrusion_role, @@ -2531,9 +2822,23 @@ void GCodeProcessor::store_move_vertex(EMoveType type) m_height, m_mm3_per_mm, m_fan_speed, + m_extruder_temps[m_extruder_id], static_cast(m_result.moves.size()) }; m_result.moves.emplace_back(vertex); + +#if ENABLE_EXTENDED_M73_LINES + // stores stop time placeholders for later use + if (type == EMoveType::Color_change || type == EMoveType::Pause_Print) { + for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + TimeMachine& machine = m_time_processor.machines[i]; + if (!machine.enabled) + continue; + + machine.stop_times.push_back({ m_g1_line_id, 0.0f }); + } + } +#endif // ENABLE_EXTENDED_M73_LINES } float GCodeProcessor::minimum_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const @@ -2609,6 +2914,22 @@ void GCodeProcessor::set_acceleration(PrintEstimatedTimeStatistics::ETimeMode mo } } +float GCodeProcessor::get_travel_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const +{ + size_t id = static_cast(mode); + return (id < m_time_processor.machines.size()) ? m_time_processor.machines[id].travel_acceleration : DEFAULT_TRAVEL_ACCELERATION; +} + +void GCodeProcessor::set_travel_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value) +{ + size_t id = static_cast(mode); + if (id < m_time_processor.machines.size()) { + m_time_processor.machines[id].travel_acceleration = (m_time_processor.machines[id].max_travel_acceleration == 0.0f) ? value : + // Clamp the acceleration with the maximum. + std::min(value, m_time_processor.machines[id].max_travel_acceleration); + } +} + float GCodeProcessor::get_filament_load_time(size_t extruder_id) { return (m_time_processor.filament_load_times.empty() || m_time_processor.extruder_unloaded) ? diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index f884df8ecc..cf55bf86e7 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -121,6 +121,7 @@ namespace Slic3r { private: using AxisCoords = std::array; using ExtruderColors = std::vector; + using ExtruderTemps = std::vector; enum class EUnits : unsigned char { @@ -211,6 +212,7 @@ namespace Slic3r { float height{ 0.0f }; // mm float mm3_per_mm{ 0.0f }; float fan_speed{ 0.0f }; // percentage + float temperature{ 0.0f }; // Celsius degrees float time{ 0.0f }; // s float volumetric_rate() const { return feedrate * mm3_per_mm; } @@ -249,9 +251,23 @@ namespace Slic3r { float acceleration; // mm/s^2 // hard limit for the acceleration, to which the firmware will clamp. float max_acceleration; // mm/s^2 + float travel_acceleration; // mm/s^2 + // hard limit for the travel acceleration, to which the firmware will clamp. + float max_travel_acceleration; // mm/s^2 float extrude_factor_override_percentage; float time; // s +#if ENABLE_EXTENDED_M73_LINES + struct StopTime + { + unsigned int g1_line_id; + float elapsed_time; + }; + std::vector stop_times; + std::string line_m73_main_mask; + std::string line_m73_stop_mask; +#else std::string line_m73_mask; +#endif // ENABLE_EXTENDED_M73_LINES State curr; State prev; CustomGCodeTime gcode_time; @@ -320,6 +336,7 @@ namespace Slic3r { float height{ 0.0f }; // mm float mm3_per_mm{ 0.0f }; float fan_speed{ 0.0f }; // percentage + float temperature{ 0.0f }; // Celsius degrees float time{ 0.0f }; // s float volumetric_rate() const { return feedrate * mm3_per_mm; } @@ -334,13 +351,15 @@ namespace Slic3r { std::vector filament; std::string printer; - void reset() - { + void reset() { print = ""; filament = std::vector(); printer = ""; } }; +#if ENABLE_GCODE_WINDOW + std::string filename; +#endif // ENABLE_GCODE_WINDOW unsigned int id; std::vector moves; Pointfs bed_shape; @@ -468,6 +487,7 @@ namespace Slic3r { ExtrusionRole m_extrusion_role; unsigned char m_extruder_id; ExtruderColors m_extruder_colors; + ExtruderTemps m_extruder_temps; std::vector m_filament_diameters; float m_extruded_last_z; unsigned int m_g1_line_id; @@ -507,7 +527,6 @@ namespace Slic3r { GCodeProcessor(); void apply_config(const PrintConfig& config); - void apply_config(const DynamicPrintConfig& config); void enable_stealth_time_estimator(bool enabled); bool is_stealth_time_estimator_enabled() const { return m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].enabled; @@ -532,6 +551,8 @@ namespace Slic3r { std::vector get_layers_time(PrintEstimatedTimeStatistics::ETimeMode mode) const; private: + void apply_config(const DynamicPrintConfig& config); + void apply_config_simplify3d(const std::string& filename); void process_gcode_line(const GCodeReader::GCodeLine& line); // Process tags embedded into comments @@ -568,6 +589,9 @@ namespace Slic3r { // Firmware controlled Unretract void process_G23(const GCodeReader::GCodeLine& line); + // Move to origin + void process_G28(const GCodeReader::GCodeLine& line); + // Set to Absolute Positioning void process_G90(const GCodeReader::GCodeLine& line); @@ -586,6 +610,9 @@ namespace Slic3r { // Set extruder to relative mode void process_M83(const GCodeReader::GCodeLine& line); + // Set extruder temperature + void process_M104(const GCodeReader::GCodeLine& line); + // Set fan speed void process_M106(const GCodeReader::GCodeLine& line); @@ -595,6 +622,9 @@ namespace Slic3r { // Set tool (Sailfish) void process_M108(const GCodeReader::GCodeLine& line); + // Set extruder temperature and wait + void process_M109(const GCodeReader::GCodeLine& line); + // Recall stored home offsets void process_M132(const GCodeReader::GCodeLine& line); @@ -641,7 +671,9 @@ namespace Slic3r { float get_axis_max_jerk(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const; float get_retract_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const; float get_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const; - void set_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value); + void set_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value); + float get_travel_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const; + void set_travel_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value); float get_filament_load_time(size_t extruder_id); float get_filament_unload_time(size_t extruder_id); diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 8520cf35bd..c45e260158 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -329,6 +329,16 @@ void ToolOrdering::reorder_extruders(unsigned int last_extruder_id) lt.extruders.front() = last_extruder_id; break; } + + // On first layer with wipe tower, prefer a soluble extruder + // at the beginning, so it is not wiped on the first layer. + if (lt == m_layer_tools[0] && m_print_config_ptr && m_print_config_ptr->wipe_tower) { + for (size_t i = 0; ifilament_soluble.get_at(lt.extruders[i]-1)) { // 1-based... + std::swap(lt.extruders[i], lt.extruders.front()); + break; + } + } } last_extruder_id = lt.extruders.back(); } diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 24248e16c1..10c68d0766 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -9,17 +9,6 @@ #include "BoundingBox.hpp" -// Experimental "Peter's wipe tower" feature was partially implemented, inspired by -// PJR's idea of alternating two perpendicular wiping directions on a square tower. -// It is probably never going to be finished, there are multiple remaining issues -// and there is probably no need to go down this way. m_peters_wipe_tower variable -// turns this on, maybe it should just be removed. Anyway, the issues are -// - layer's are not exactly square -// - variable width for higher levels -// - make sure it is not too sparse (apply max_bridge_distance and make last wipe longer) -// - enable enhanced first layer adhesion - - namespace Slic3r { @@ -122,8 +111,10 @@ public: WipeTowerWriter& feedrate(float f) { - if (f != m_current_feedrate) + if (f != m_current_feedrate) { m_gcode += "G1" + set_format_F(f) + "\n"; + m_current_feedrate = f; + } return *this; } @@ -216,7 +207,7 @@ public: WipeTowerWriter& extrude(const Vec2f &dest, const float f = 0.f) { return extrude(dest.x(), dest.y(), f); } - + WipeTowerWriter& rectangle(const Vec2f& ld,float width,float height,const float f = 0.f) { Vec2f corners[4]; @@ -242,6 +233,14 @@ public: return (*this); } + WipeTowerWriter& rectangle(const WipeTower::box_coordinates& box, const float f = 0.f) + { + rectangle(Vec2f(box.ld.x(), box.ld.y()), + box.ru.x() - box.lu.x(), + box.ru.y() - box.rd.y(), f); + return (*this); + } + WipeTowerWriter& load(float e, float f = 0.f) { if (e == 0.f && (f == 0.f || f == m_current_feedrate)) @@ -343,7 +342,7 @@ public: WipeTowerWriter& speed_override_backup() { // This is only supported by Prusa at this point (https://github.com/prusa3d/PrusaSlicer/issues/3114) - if (m_gcode_flavor == gcfMarlin) + if (m_gcode_flavor == gcfMarlinLegacy || m_gcode_flavor == gcfMarlinFirmware) m_gcode += "M220 B\n"; return *this; } @@ -351,7 +350,7 @@ public: // Let the firmware restore the active speed override value. WipeTowerWriter& speed_override_restore() { - if (m_gcode_flavor == gcfMarlin) + if (m_gcode_flavor == gcfMarlinLegacy || m_gcode_flavor == gcfMarlinFirmware) m_gcode += "M220 R\n"; return *this; } @@ -520,15 +519,24 @@ WipeTower::WipeTower(const PrintConfig& config, const std::vector WipeTower::prime( if (m_set_extruder_trimpot) writer.set_extruder_trimpot(550); writer.speed_override_restore() - .feedrate(6000) + .feedrate(m_travel_speed * 60.f) .flush_planner_queue() .reset_extruder() .append("; CP PRIMING END\n" @@ -693,21 +702,14 @@ std::vector WipeTower::prime( 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) - // so that tool_change() will know to extrude the wipe tower brim: - m_print_brim = true; - return results; } WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool) { - if ( m_print_brim ) - return toolchange_Brim(); - size_t old_tool = m_current_tool; - float wipe_area = 0.f; - bool last_change_in_layer = false; + float wipe_area = 0.f; float wipe_volume = 0.f; // Finds this toolchange info @@ -715,9 +717,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool) { for (const auto &b : m_layer_info->tool_changes) if ( b.new_tool == tool ) { - wipe_volume = b.wipe_volume; - if (tool == m_layer_info->tool_changes.back().new_tool) - last_change_in_layer = true; + wipe_volume = b.wipe_volume; wipe_area = b.required_depth * m_layer_info->extra_spacing; break; } @@ -726,17 +726,17 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool) // Otherwise we are going to Unload only. And m_layer_info would be invalid. } - box_coordinates cleaning_box( + box_coordinates cleaning_box( 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.5f*m_perimeter_width + (tool != (unsigned int)(-1) ? wipe_area+m_depth_traversed-0.5f*m_perimeter_width : m_wipe_tower_depth-m_perimeter_width)); 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)) + .set_y_shift(m_y_shift + (tool!=(unsigned int)(-1) && (m_current_shape == SHAPE_REVERSED) ? 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 @@ -758,7 +758,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool) // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. if (tool != (unsigned int)-1){ // This is not the last change. toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, - m_is_first_layer ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature); + 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 @@ -769,24 +769,10 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool) m_depth_traversed += wipe_area; - if (last_change_in_layer) {// draw perimeter line - writer.set_y_shift(m_y_shift); - if (m_peters_wipe_tower) - writer.rectangle(Vec2f::Zero(), m_layer_info->depth + 3*m_perimeter_width, m_wipe_tower_depth); - else { - 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.add_wipe_point(writer.x(), writer.y()) - .add_wipe_point(writer.x()> m_wipe_tower_width / 2.f ? 0.f : m_wipe_tower_width, writer.y()); - - } - } - } - if (m_set_extruder_trimpot) writer.set_extruder_trimpot(550); // Reset the extruder current to a normal value. writer.speed_override_restore(); - writer.feedrate(6000) + writer.feedrate(m_travel_speed * 60.f) .flush_planner_queue() .reset_extruder() .append("; CP TOOLCHANGE END\n" @@ -800,62 +786,6 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool) return construct_tcr(writer, false, old_tool); } -WipeTower::ToolChangeResult WipeTower::toolchange_Brim(bool sideOnly, float y_offset) -{ - size_t old_tool = m_current_tool; - - const box_coordinates wipeTower_box( - Vec2f::Zero(), - m_wipe_tower_width, - m_wipe_tower_depth); - - 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"); - - 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 - 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. - box_coordinates box(wipeTower_box); - // the brim shall have 'normal' spacing with no extra void space - float spacing = m_perimeter_width - m_layer_height*float(1.-M_PI_4); - for (size_t i = 0; i < 4; ++ i) { - box.expand(spacing); - writer.travel (box.ld, 7000) - .extrude(box.lu, 2100).extrude(box.ru) - .extrude(box.rd ).extrude(box.ld); - } - - box.expand(-spacing); - writer.add_wipe_point(writer.x(), writer.y()) - .add_wipe_point(box.ld) - .add_wipe_point(box.rd); - - writer.append("; CP WIPE TOWER FIRST LAYER BRIM END\n" - ";-----------------------------------\n"); - - // Save actual brim width to be later passed to the Print object, which will use it - // for skirt calculation and pass it to GLCanvas for precise preview box - m_wipe_tower_brim_width = wipeTower_box.ld.x() - box.ld.x() + spacing/2.f; - - m_print_brim = false; // Mark the brim as extruded - - // 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(); - - return construct_tcr(writer, false, old_tool); -} - - // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. void WipeTower::toolchange_Unload( @@ -952,7 +882,7 @@ void WipeTower::toolchange_Unload( // be already set and there is no need to change anything. Also, the temperature could be changed // for wrong extruder. if (m_semm) { - if (new_temperature != 0 && (new_temperature != m_old_temperature || m_is_first_layer) ) { // Set the extruder temperature, but don't wait. + if (new_temperature != 0 && (new_temperature != m_old_temperature || 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). writer.set_extruder_temp(new_temperature, false); @@ -1012,7 +942,10 @@ void WipeTower::toolchange_Change( // gcode could have left the extruder somewhere, we cannot just start extruding. We should also inform the // postprocessor that we absolutely want to have this in the gcode, even if it thought it is the same as before. Vec2f current_pos = writer.pos_rotated(); - writer.append(std::string("G1 X") + std::to_string(current_pos.x()) + " Y" + std::to_string(current_pos.y()) + never_skip_tag() + "\n"); + writer.feedrate(m_travel_speed * 60.f) // see https://github.com/prusa3d/PrusaSlicer/issues/5483 + .append(std::string("G1 X") + std::to_string(current_pos.x()) + + " Y" + std::to_string(current_pos.y()) + + never_skip_tag() + "\n"); // The toolchange Tn command will be inserted later, only in case that the user does // not provide a custom toolchange gcode. @@ -1058,9 +991,8 @@ void WipeTower::toolchange_Wipe( float wipe_volume) { // Increase flow on first layer, slow down print. - writer.set_extrusion_flow(m_extrusion_flow * (m_is_first_layer ? 1.18f : 1.f)) + writer.set_extrusion_flow(m_extrusion_flow * (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(); @@ -1070,7 +1002,9 @@ void WipeTower::toolchange_Wipe( float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height); float dy = m_extra_spacing*m_perimeter_width; - float wipe_speed = 1600.f; + + const float target_speed = is_first_layer() ? m_first_layer_speed * 60.f : 4800.f; + float wipe_speed = 0.33f * target_speed; // if there is less than 2.5*m_perimeter_width to the edge, advance straightaway (there is likely a blob anyway) if ((m_left_to_right ? xr-writer.x() : writer.x()-xl) < 2.5f*m_perimeter_width) { @@ -1081,17 +1015,17 @@ void WipeTower::toolchange_Wipe( // now the wiping itself: for (int i = 0; true; ++i) { if (i!=0) { - if (wipe_speed < 1610.f) wipe_speed = 1800.f; - else if (wipe_speed < 1810.f) wipe_speed = 2200.f; - else if (wipe_speed < 2210.f) wipe_speed = 4200.f; - else wipe_speed = std::min(4800.f, wipe_speed + 50.f); + if (wipe_speed < 0.34f * target_speed) wipe_speed = 0.375f * target_speed; + else if (wipe_speed < 0.377 * target_speed) wipe_speed = 0.458f * target_speed; + else if (wipe_speed < 0.46f * target_speed) wipe_speed = 0.875f * target_speed; + else wipe_speed = std::min(target_speed, wipe_speed + 50.f); } float traversed_x = writer.x(); if (m_left_to_right) - writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5f*m_perimeter_width), writer.y(), wipe_speed * wipe_coeff); + writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5f*m_perimeter_width), writer.y(), wipe_speed); else - writer.extrude(xl + (i % 4 == 1 ? 0 : 1.5f*m_perimeter_width), writer.y(), wipe_speed * wipe_coeff); + writer.extrude(xl + (i % 4 == 1 ? 0 : 1.5f*m_perimeter_width), writer.y(), wipe_speed); if (writer.y()+float(EPSILON) > cleaning_box.lu.y()-0.5f*m_perimeter_width) break; // in case next line would not fit @@ -1107,17 +1041,16 @@ void WipeTower::toolchange_Wipe( m_left_to_right = !m_left_to_right; } - // this is neither priming nor not the last toolchange on this layer - we are - // going back to the model - wipe the nozzle. - if (m_layer_info != m_plan.end() && m_current_tool != m_layer_info->tool_changes.back().new_tool) { + // We may be going back to the model - wipe the nozzle. If this is followed + // by finish_layer, this wipe path will be overwritten. + writer.add_wipe_point(writer.x(), writer.y()) + .add_wipe_point(writer.x(), writer.y() - dy) + .add_wipe_point(! m_left_to_right ? m_wipe_tower_width : 0.f, writer.y() - dy); + + if (m_layer_info != m_plan.end() && m_current_tool != m_layer_info->tool_changes.back().new_tool) m_left_to_right = !m_left_to_right; - writer.add_wipe_point(writer.x(), writer.y()) - .add_wipe_point(writer.x(), writer.y() - dy) - .add_wipe_point(m_left_to_right ? m_wipe_tower_width : 0.f, writer.y() - dy); - } - - writer.set_extrusion_flow(m_extrusion_flow); // Reset the extrusion flow. + writer.set_extrusion_flow(m_extrusion_flow); // Reset the extrusion flow. } @@ -1125,9 +1058,8 @@ void WipeTower::toolchange_Wipe( 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()); + m_current_layer_finished = true; size_t old_tool = m_current_tool; @@ -1135,61 +1067,75 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_initial_tool(m_current_tool) - .set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED && !m_peters_wipe_tower ? m_layer_info->toolchanges_depth() : 0.f)) - .append(";--------------------\n" - "; CP EMPTY GRID START\n") - .comment_with_value(" layer #", m_num_layer_changes + 1); + .set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)); + // Slow down on the 1st layer. - float speed_factor = m_is_first_layer ? 0.5f : 1.f; + bool first_layer = is_first_layer(); + float feedrate = first_layer ? m_first_layer_speed * 60.f : 2900.f; float current_depth = m_layer_info->depth - m_layer_info->toolchanges_depth(); - 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); + box_coordinates fill_box(Vec2f(m_perimeter_width, m_layer_info->depth-(current_depth-m_perimeter_width)), + m_wipe_tower_width - 2 * m_perimeter_width, current_depth-m_perimeter_width); writer.set_initial_position((m_left_to_right ? fill_box.ru : fill_box.lu), // so there is never a diagonal travel m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); bool toolchanges_on_layer = m_layer_info->toolchanges_depth() > WT_EPSILON; - box_coordinates box = fill_box; - for (int i=0;i<2;++i) { - if (! toolchanges_on_layer) { - if (i==0) box.expand(m_perimeter_width); - 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); - } + box_coordinates wt_box(Vec2f(0.f, (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)), + m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); + + // inner perimeter of the sparse section, if there is space for it: + if (fill_box.ru.y() - fill_box.rd.y() > m_perimeter_width - WT_EPSILON) + writer.rectangle(fill_box.ld, fill_box.rd.x()-fill_box.ld.x(), fill_box.ru.y()-fill_box.rd.y(), feedrate); // 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 (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 - 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()); + // Extrude infill to support the material to be printed above. + const float dy = (fill_box.lu.y() - fill_box.ld.y() - m_perimeter_width); + float left = fill_box.lu.x() + 2*m_perimeter_width; + float right = fill_box.ru.x() - 2 * m_perimeter_width; + if (dy > m_perimeter_width) + { + writer.travel(fill_box.ld + Vec2f(m_perimeter_width * 2, 0.f)) + .append(";--------------------\n" + "; CP EMPTY GRID START\n") + .comment_with_value(" layer #", m_num_layer_changes + 1); + + // Is there a soluble filament wiped/rammed at the next layer? + // If so, the infill should not be sparse. + bool solid_infill = m_layer_info+1 == m_plan.end() + ? false + : std::any_of((m_layer_info+1)->tool_changes.begin(), + (m_layer_info+1)->tool_changes.end(), + [this](const WipeTowerInfo::ToolChange& tch) { + return m_filpar[tch.new_tool].is_soluble + || m_filpar[tch.old_tool].is_soluble; + }); + solid_infill |= first_layer && m_adhesion; + + if (solid_infill) { + float sparse_factor = 1.5f; // 1=solid, 2=every other line, etc. + if (first_layer) { // the infill should touch perimeters + left -= m_perimeter_width; + right += m_perimeter_width; + sparse_factor = 1.f; } - writer.add_wipe_point(writer.x(), writer.y()) - .add_wipe_point(box.rd.x()-m_perimeter_width/2.f,writer.y()); - } - 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; - if (dy > m_perimeter_width) - { - // Extrude an inverse U at the left of the region. - 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); + float y = fill_box.ld.y() + m_perimeter_width; + int n = dy / (m_perimeter_width * sparse_factor); + float spacing = (dy-m_perimeter_width)/(n-1); + int i=0; + for (i=0; itool_changes.size()==0) // we have no way to save anything on an empty layer continue; - for (const auto &toolchange : m_layer_info->tool_changes) + // Which toolchange will finish_layer extrusions be subtracted from? + int idx = first_toolchange_to_nonsoluble(m_layer_info->tool_changes); + + for (int i=0; itool_changes.size()); ++i) { + auto& toolchange = m_layer_info->tool_changes[i]; tool_change(toolchange.new_tool); - float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into - float length_to_save = 2*(m_wipe_tower_width+m_wipe_tower_depth) + (!layer_finished() ? finish_layer().total_extrusion_length_in_plane() : 0.f); - float length_to_wipe = volume_to_length(m_layer_info->tool_changes.back().wipe_volume, - m_perimeter_width,m_layer_info->height) - m_layer_info->tool_changes.back().first_wipe_line - length_to_save; + if (i == idx) { + float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into + float length_to_save = finish_layer().total_extrusion_length_in_plane(); + float length_to_wipe = volume_to_length(toolchange.wipe_volume, + m_perimeter_width, m_layer_info->height) - toolchange.first_wipe_line - length_to_save; - length_to_wipe = std::max(length_to_wipe,0.f); - float depth_to_wipe = m_perimeter_width * (std::floor(length_to_wipe/width) + ( length_to_wipe > 0.f ? 1.f : 0.f ) ) * m_extra_spacing; + length_to_wipe = std::max(length_to_wipe,0.f); + float depth_to_wipe = m_perimeter_width * (std::floor(length_to_wipe/width) + ( length_to_wipe > 0.f ? 1.f : 0.f ) ) * m_extra_spacing; - //depth += (int(length_to_extrude / width) + 1) * m_perimeter_width; - m_layer_info->tool_changes.back().required_depth = m_layer_info->tool_changes.back().ramming_depth + depth_to_wipe; + toolchange.required_depth = toolchange.ramming_depth + depth_to_wipe; + } + } } } + +// Return index of first toolchange that switches to non-soluble extruder +// ot -1 if there is no such toolchange. +int WipeTower::first_toolchange_to_nonsoluble( + const std::vector& tool_changes) const +{ + for (size_t idx=0; idx> &result) { if (m_plan.empty()) - return; m_extra_spacing = 1.f; @@ -1320,9 +1324,6 @@ void WipeTower::generate(std::vector> & plan_tower(); } - if (m_peters_wipe_tower) - make_wipe_tower_square(); - m_layer_info = m_plan.begin(); // we don't know which extruder to start with - we'll set it according to the first toolchange @@ -1341,65 +1342,41 @@ void WipeTower::generate(std::vector> & std::vector layer_result; for (auto layer : m_plan) { - set_layer(layer.z,layer.height,0,layer.z == m_plan.front().z,layer.z == m_plan.back().z); - if (m_peters_wipe_tower) - m_internal_rotation += 90.f; - else - m_internal_rotation += 180.f; + set_layer(layer.z, layer.height, 0, false/*layer.z == m_plan.front().z*/, layer.z == m_plan.back().z); + m_internal_rotation += 180.f; - if (!m_peters_wipe_tower && m_layer_info->depth < m_wipe_tower_depth - m_perimeter_width) + if (m_layer_info->depth < 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) - layer_result.emplace_back(tool_change(toolchange.new_tool)); + int idx = first_toolchange_to_nonsoluble(layer.tool_changes); + ToolChangeResult finish_layer_tcr; - if (! layer_finished()) { - auto finish_layer_toolchange = finish_layer(); - if ( ! layer.tool_changes.empty() ) { // we will merge it to the last toolchange - auto& last_toolchange = layer_result.back(); - if (last_toolchange.end_pos != finish_layer_toolchange.start_pos) { - char buf[2048]; // Add a travel move from tc1.end_pos to tc2.start_pos. - sprintf(buf, "G1 X%.3f Y%.3f F7200\n", finish_layer_toolchange.start_pos.x(), finish_layer_toolchange.start_pos.y()); - last_toolchange.gcode += buf; - } - last_toolchange.gcode += finish_layer_toolchange.gcode; - last_toolchange.extrusions.insert(last_toolchange.extrusions.end(), finish_layer_toolchange.extrusions.begin(), finish_layer_toolchange.extrusions.end()); - last_toolchange.end_pos = finish_layer_toolchange.end_pos; - last_toolchange.wipe_path = finish_layer_toolchange.wipe_path; - } + if (idx == -1) { + // if there is no toolchange switching to non-soluble, finish layer + // will be called at the very beginning. That's the last possibility + // where a nonsoluble tool can be. + finish_layer_tcr = finish_layer(); + } + + for (int i=0; i> &result); float get_depth() const { return m_wipe_tower_depth; } - float get_brim_width() const { return m_wipe_tower_brim_width; } + float get_brim_width() const { return m_wipe_tower_brim_width_real; } @@ -129,9 +160,8 @@ public: { 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_layer_finished = false; 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; @@ -175,7 +205,7 @@ public: // 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); + return m_current_layer_finished; } std::vector get_used_filament() const { return m_used_filament_length; } @@ -183,6 +213,7 @@ public: struct FilamentParameters { std::string material = "PLA"; + bool is_soluble = false; int temperature = 0; int first_layer_temperature = 0; float loading_speed = 0.f; @@ -208,7 +239,6 @@ private: 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; float filament_area() const { @@ -220,15 +250,18 @@ private: 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_brim_width = 0.f; // Width of brim (mm) + float m_wipe_tower_brim_width = 0.f; // Width of brim (mm) from config + float m_wipe_tower_brim_width_real = 0.f; // Width of brim (mm) after generation 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) + float m_travel_speed = 0.f; + float m_first_layer_speed = 0.f; + size_t m_first_layer_idx = size_t(-1); // G-code generator parameters. float m_cooling_tube_retraction = 0.f; @@ -267,9 +300,12 @@ private: const std::vector> wipe_volumes; float m_depth_traversed = 0.f; // Current y position at the wipe tower. + bool m_current_layer_finished = false; bool m_left_to_right = true; float m_extra_spacing = 1.f; + bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; } + // 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 { @@ -293,39 +329,7 @@ private: 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 + // to store information about tool changes for a given layer struct WipeTowerInfo{ struct ToolChange { size_t old_tool; @@ -355,11 +359,10 @@ private: // 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); + // Return index of first toolchange that switches to non-soluble extruder + // ot -1 if there is no such toolchange. + int first_toolchange_to_nonsoluble( + const std::vector& tool_changes) const; void toolchange_Unload( WipeTowerWriter &writer, @@ -385,6 +388,6 @@ private: -}; // namespace Slic3r +} // namespace Slic3r #endif // WipeTowerPrusaMM_hpp_ diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index 847398e69f..bc85331137 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -20,7 +20,8 @@ void GCodeWriter::apply_print_config(const PrintConfig &print_config) this->config.apply(print_config, true); m_extrusion_axis = this->config.get_extrusion_axis(); m_single_extruder_multi_material = print_config.single_extruder_multi_material.value; - m_max_acceleration = std::lrint((print_config.gcode_flavor.value == gcfMarlin && print_config.machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) ? + bool is_marlin = print_config.gcode_flavor.value == gcfMarlinLegacy || print_config.gcode_flavor.value == gcfMarlinFirmware; + m_max_acceleration = std::lrint((is_marlin && print_config.machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) ? print_config.machine_max_acceleration_extruding.values.front() : 0); } @@ -48,7 +49,8 @@ std::string GCodeWriter::preamble() } if (FLAVOR_IS(gcfRepRapSprinter) || FLAVOR_IS(gcfRepRapFirmware) || - FLAVOR_IS(gcfMarlin) || + FLAVOR_IS(gcfMarlinLegacy) || + FLAVOR_IS(gcfMarlinFirmware) || FLAVOR_IS(gcfTeacup) || FLAVOR_IS(gcfRepetier) || FLAVOR_IS(gcfSmoothie)) @@ -205,8 +207,12 @@ std::string GCodeWriter::set_acceleration(unsigned int acceleration) // M202: Set max travel acceleration gcode << "M202 X" << acceleration << " Y" << acceleration; } else if (FLAVOR_IS(gcfRepRapFirmware)) { - // M204: Set default acceleration - gcode << "M204 P" << acceleration; + // M204: Set default acceleration + gcode << "M204 P" << acceleration; + } else if (FLAVOR_IS(gcfMarlinFirmware)) { + // This is new MarlinFirmware with separated print/retraction/travel acceleration. + // Use M204 P, we don't want to override travel acc by M204 S (which is deprecated anyway). + gcode << "M204 P" << acceleration; } else { // M204: Set default acceleration gcode << "M204 S" << acceleration; diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 45730dd9f5..d5ef41125a 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -245,8 +245,7 @@ Polygon convex_hull(Points points) return hull; } -Pointf3s -convex_hull(Pointf3s points) +Pointf3s convex_hull(Pointf3s points) { assert(points.size() >= 3); // sort input points @@ -304,8 +303,7 @@ convex_hull(Pointf3s points) return hull; } -Polygon -convex_hull(const Polygons &polygons) +Polygon convex_hull(const Polygons &polygons) { Points pp; for (Polygons::const_iterator p = polygons.begin(); p != polygons.end(); ++p) { @@ -1089,11 +1087,13 @@ bool MedialAxis::validate_edge(const VD::edge_type* edge) { // prevent overflows and detect almost-infinite edges +#ifndef CLIPPERLIB_INT32 if (std::abs(edge->vertex0()->x()) > double(CLIPPER_MAX_COORD_UNSCALED) || std::abs(edge->vertex0()->y()) > double(CLIPPER_MAX_COORD_UNSCALED) || std::abs(edge->vertex1()->x()) > double(CLIPPER_MAX_COORD_UNSCALED) || std::abs(edge->vertex1()->y()) > double(CLIPPER_MAX_COORD_UNSCALED)) return false; +#endif // CLIPPERLIB_INT32 // construct the line representing this edge of the Voronoi diagram const Line line( diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 39b0abb609..3f89411e84 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -299,20 +299,23 @@ bool liang_barsky_line_clipping( // Ugly named variant, that accepts the squared line // Don't call me with a nearly zero length vector! +// sympy: +// factor(solve([a * x + b * y + c, x**2 + y**2 - r**2], [x, y])[0]) +// factor(solve([a * x + b * y + c, x**2 + y**2 - r**2], [x, y])[1]) template int ray_circle_intersections_r2_lv2_c(T r2, T a, T b, T lv2, T c, std::pair, Eigen::Matrix> &out) { - T d = r2 - c * c / lv2; - if (d < T(0)) + T d2 = r2 * lv2 - c * c; + if (d2 < T(0)) return 0; - T x0 = - a * c / lv2; - T y0 = - b * c / lv2; - T mult = sqrt(d / lv2); - out.first.x() = x0 + b * mult; - out.first.y() = y0 - a * mult; - out.second.x() = x0 - b * mult; - out.second.y() = y0 + a * mult; - return mult == T(0) ? 1 : 2; + T x0 = - a * c; + T y0 = - b * c; + T d = sqrt(d2); + out.first.x() = (x0 + b * d) / lv2; + out.first.y() = (y0 - a * d) / lv2; + out.second.x() = (x0 - b * d) / lv2; + out.second.y() = (y0 + a * d) / lv2; + return d == T(0) ? 1 : 2; } template int ray_circle_intersections(T r, T a, T b, T c, std::pair, Eigen::Matrix> &out) diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 9a3fe368d3..87296f8f1c 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -59,7 +59,10 @@ public: // (this collection contains only ExtrusionEntityCollection objects) ExtrusionEntityCollection fills; - Flow flow(FlowRole role, bool bridge = false, double width = -1) const; + Flow flow(FlowRole role) const; + Flow flow(FlowRole role, double layer_height) const; + Flow bridging_flow(FlowRole role) const; + void slices_to_fill_surfaces_clipped(); void prepare_fill_surfaces(); void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces); @@ -97,6 +100,7 @@ typedef std::vector LayerRegionPtrs; class Layer { public: + // Sequential index of this layer in PrintObject::m_layers, offsetted by the number of raft layers. size_t id() const { return m_id; } void set_id(size_t id) { m_id = id; } PrintObject* object() { return m_object; } @@ -112,7 +116,7 @@ public: // Collection of expolygons generated by slicing the possibly multiple meshes of the source geometry // (with possibly differing extruder ID and slicing parameters) and merged. - // For the first layer, if the ELephant foot compensation is applied, this lslice is uncompensated, therefore + // For the first layer, if the Elephant foot compensation is applied, this lslice is uncompensated, therefore // it includes the Elephant foot effect, thus it corresponds to the shape of the printed 1st layer. // These lslices aka islands are chained by the shortest traverse distance and this traversal // order will be applied by the G-code generator to the extrusions fitting into these lslices. @@ -167,7 +171,7 @@ protected: virtual ~Layer(); private: - // sequential number of layer, 0-based + // Sequential index of layer, 0-based, offsetted by number of raft layers. size_t m_id; PrintObject *m_object; LayerRegionPtrs m_regions; diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 1a0bd341c4..059d94e25b 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -15,16 +15,31 @@ namespace Slic3r { -Flow LayerRegion::flow(FlowRole role, bool bridge, double width) const +Flow LayerRegion::flow(FlowRole role) const { - return m_region->flow( - role, - m_layer->height, - bridge, - m_layer->id() == 0, - width, - *m_layer->object() - ); + return this->flow(role, m_layer->height); +} + +Flow LayerRegion::flow(FlowRole role, double layer_height) const +{ + return m_region->flow(*m_layer->object(), role, layer_height, m_layer->id() == 0); +} + +Flow LayerRegion::bridging_flow(FlowRole role) const +{ + const PrintRegion ®ion = *this->region(); + const PrintRegionConfig ®ion_config = region.config(); + if (this->layer()->object()->config().thick_bridges) { + // The old Slic3r way (different from all other slicers): Use rounded extrusions. + // Get the configured nozzle_diameter for the extruder associated to the flow role requested. + // Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right. + auto nozzle_diameter = float(region.print()->config().nozzle_diameter.get_at(region.extruder(role) - 1)); + // Applies default bridge spacing. + return Flow::bridging_flow(float(sqrt(region_config.bridge_flow_ratio)) * nozzle_diameter, nozzle_diameter); + } else { + // The same way as other slicers: Use normal extrusions. Apply bridge_flow_ratio while maintaining the original spacing. + return this->flow(role).with_flow_ratio(region_config.bridge_flow_ratio); + } } // Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces. @@ -59,6 +74,7 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec const PrintRegionConfig ®ion_config = this->region()->config(); // This needs to be in sync with PrintObject::_slice() slicing_mode_normal_below_layer! bool spiral_vase = print_config.spiral_vase && + //FIXME account for raft layers. (this->layer()->id() >= size_t(region_config.bottom_solid_layers.value) && this->layer()->print_z >= region_config.bottom_solid_min_thickness - EPSILON); @@ -84,7 +100,7 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec g.layer_id = (int)this->layer()->id(); g.ext_perimeter_flow = this->flow(frExternalPerimeter); - g.overhang_flow = this->region()->flow(frPerimeter, -1, true, false, -1, *this->layer()->object()); + g.overhang_flow = this->bridging_flow(frPerimeter); g.solid_infill_flow = this->flow(frSolidInfill); g.process(); @@ -200,7 +216,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly break; } // Grown by 3mm. - Polygons polys = offset(to_polygons(bridges[i].expolygon), margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS); + Polygons polys = offset(bridges[i].expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS); if (idx_island == -1) { BOOST_LOG_TRIVIAL(trace) << "Bridge did not fall into the source region!"; } else { @@ -266,11 +282,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly // would get merged into a single one while they need different directions // also, supply the original expolygon instead of the grown one, because in case // of very thin (but still working) anchors, the grown expolygon would go beyond them - BridgeDetector bd( - initial, - lower_layer->lslices, - this->flow(frInfill, true).scaled_width() - ); + BridgeDetector bd(initial, lower_layer->lslices, this->bridging_flow(frInfill).scaled_width()); #ifdef SLIC3R_DEBUG printf("Processing bridge at layer %zu:\n", this->layer()->id()); #endif diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index 555cfe5019..7b903f66c8 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -106,13 +106,8 @@ template bool all_of(const C &container) }); } -template struct remove_cvref -{ - using type = - typename std::remove_cv::type>::type; -}; - -template using remove_cvref_t = typename remove_cvref::type; +template +using remove_cvref_t = std::remove_reference_t>; /// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html template> diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index 6ffc5dec39..25ee3e4af8 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -110,33 +110,19 @@ struct CGALMesh { _EpicMesh m; }; // Converions from and to CGAL mesh // ///////////////////////////////////////////////////////////////////////////// -template void triangle_mesh_to_cgal(const TriangleMesh &M, _Mesh &out) +template +void triangle_mesh_to_cgal(const std::vector & V, + const std::vector &F, + _Mesh &out) { - using Index3 = std::array; - - if (M.empty()) return; - - std::vector points; - std::vector indices; - points.reserve(M.its.vertices.size()); - indices.reserve(M.its.indices.size()); - for (auto &v : M.its.vertices) points.emplace_back(v.x(), v.y(), v.z()); - for (auto &_f : M.its.indices) { - auto f = _f.cast(); - indices.emplace_back(Index3{f(0), f(1), f(2)}); - } + if (F.empty()) return; - CGALProc::orient_polygon_soup(points, indices); - CGALProc::polygon_soup_to_polygon_mesh(points, indices, out); - - // Number the faces because 'orient_to_bound_a_volume' needs a face <--> index map - unsigned index = 0; - for (auto face : out.faces()) face = CGAL::SM_Face_index(index++); - - if(CGAL::is_closed(out)) - CGALProc::orient_to_bound_a_volume(out); - else - throw Slic3r::RuntimeError("Mesh not watertight"); + for (auto &v : V) + out.add_vertex(typename _Mesh::Point{v.x(), v.y(), v.z()}); + + using VI = typename _Mesh::Vertex_index; + for (auto &f : F) + out.add_face(VI(f(0)), VI(f(1)), VI(f(2))); } inline Vec3d to_vec3d(const _EpicMesh::Point &v) @@ -164,22 +150,30 @@ template TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh) } for (auto &face : cgalmesh.faces()) { - auto vtc = cgalmesh.vertices_around_face(cgalmesh.halfedge(face)); - int i = 0; - Vec3i trface; - for (auto v : vtc) trface(i++) = static_cast(v); - facets.emplace_back(trface); + auto vtc = cgalmesh.vertices_around_face(cgalmesh.halfedge(face)); + + int i = 0; + Vec3i facet; + for (auto v : vtc) { + if (i > 2) { i = 0; break; } + facet(i++) = v; + } + + if (i == 3) + facets.emplace_back(facet); } TriangleMesh out{points, facets}; - out.require_shared_vertices(); + out.repair(); return out; } -std::unique_ptr triangle_mesh_to_cgal(const TriangleMesh &M) +std::unique_ptr +triangle_mesh_to_cgal(const std::vector &V, + const std::vector &F) { std::unique_ptr out(new CGALMesh{}); - triangle_mesh_to_cgal(M, out->m); + triangle_mesh_to_cgal(V, F, out->m); return out; } @@ -238,8 +232,8 @@ template void _mesh_boolean_do(Op &&op, TriangleMesh &A, const Triangl { CGALMesh meshA; CGALMesh meshB; - triangle_mesh_to_cgal(A, meshA.m); - triangle_mesh_to_cgal(B, meshB.m); + triangle_mesh_to_cgal(A.its.vertices, A.its.indices, meshA.m); + triangle_mesh_to_cgal(B.its.vertices, B.its.indices, meshB.m); _cgal_do(op, meshA, meshB); @@ -264,7 +258,7 @@ void intersect(TriangleMesh &A, const TriangleMesh &B) bool does_self_intersect(const TriangleMesh &mesh) { CGALMesh cgalm; - triangle_mesh_to_cgal(mesh, cgalm.m); + triangle_mesh_to_cgal(mesh.its.vertices, mesh.its.indices, cgalm.m); return CGALProc::does_self_intersect(cgalm.m); } diff --git a/src/libslic3r/MeshBoolean.hpp b/src/libslic3r/MeshBoolean.hpp index ce17a13286..8e88e25361 100644 --- a/src/libslic3r/MeshBoolean.hpp +++ b/src/libslic3r/MeshBoolean.hpp @@ -27,7 +27,19 @@ namespace cgal { struct CGALMesh; struct CGALMeshDeleter { void operator()(CGALMesh *ptr); }; -std::unique_ptr triangle_mesh_to_cgal(const TriangleMesh &M); +std::unique_ptr +triangle_mesh_to_cgal(const std::vector &V, + const std::vector &F); + +inline std::unique_ptr triangle_mesh_to_cgal(const indexed_triangle_set &M) +{ + return triangle_mesh_to_cgal(M.vertices, M.indices); +} +inline std::unique_ptr triangle_mesh_to_cgal(const TriangleMesh &M) +{ + return triangle_mesh_to_cgal(M.its); +} + TriangleMesh cgal_to_triangle_mesh(const CGALMesh &cgalmesh); // Do boolean mesh difference with CGAL bypassing igl. diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 5f806955ee..d48443181f 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1302,52 +1302,54 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b void ModelObject::split(ModelObjectPtrs* new_objects) { - if (this->volumes.size() > 1) { - // We can't split meshes if there's more than one volume, because - // we can't group the resulting meshes by object afterwards - new_objects->emplace_back(this); - return; - } - - ModelVolume* volume = this->volumes.front(); - TriangleMeshPtrs meshptrs = volume->mesh().split(); - size_t counter = 1; - for (TriangleMesh *mesh : meshptrs) { + for (ModelVolume* volume : this->volumes) { + if (volume->type() != ModelVolumeType::MODEL_PART) + continue; - // FIXME: crashes if not satisfied - if (mesh->facets_count() < 3) continue; + TriangleMeshPtrs meshptrs = volume->mesh().split(); + size_t counter = 1; + 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 + (meshptrs.size() > 1 ? "_" + std::to_string(counter++) : ""); + // FIXME: crashes if not satisfied + if (mesh->facets_count() < 3) continue; - // Don't copy the config's ID. - new_object->config.assign_config(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); - ModelVolume* new_vol = new_object->add_volume(*volume, std::move(*mesh)); + mesh->repair(); - for (ModelInstance* model_instance : new_object->instances) - { - Vec3d shift = model_instance->get_transformation().get_matrix(true) * new_vol->get_offset(); - model_instance->set_offset(model_instance->get_offset() + shift); + // 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(); + if (meshptrs.size() == 1) { + new_object->name = volume->name; + // Don't copy the config's ID. + new_object->config.assign_config(this->config.size() > 0 ? this->config : volume->config); + } + else { + new_object->name = this->name + (meshptrs.size() > 1 ? "_" + std::to_string(counter++) : ""); + // Don't copy the config's ID. + new_object->config.assign_config(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); + ModelVolume* new_vol = new_object->add_volume(*volume, std::move(*mesh)); + + for (ModelInstance* model_instance : new_object->instances) + { + Vec3d shift = model_instance->get_transformation().get_matrix(true) * new_vol->get_offset(); + model_instance->set_offset(model_instance->get_offset() + shift); + } + + new_vol->set_offset(Vec3d::Zero()); + // reset the source to disable reload from disk + new_vol->source = ModelVolume::Source(); + new_objects->emplace_back(new_object); + delete mesh; } - - new_vol->set_offset(Vec3d::Zero()); - // reset the source to disable reload from disk - new_vol->source = ModelVolume::Source(); - new_objects->emplace_back(new_object); - delete mesh; } - - return; } + void ModelObject::merge() { if (this->volumes.size() == 1) { @@ -1738,6 +1740,7 @@ size_t ModelVolume::split(unsigned int max_extruders) 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", auto_extruder_id(max_extruders, extruder_counter)); + this->object->volumes[ivolume]->m_is_splittable = 0; delete mesh; ++ idx; } diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index d5bac7249c..d17142bb2c 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -64,6 +64,7 @@ public: bool has_duplicate_points() const; // Remove exact duplicates, return true if any duplicate has been removed. bool remove_duplicate_points(); + void clear() { this->points.clear(); } void append(const Point &point) { this->points.push_back(point); } void append(const Points &src) { this->append(src.begin(), src.end()); } void append(const Points::const_iterator &begin, const Points::const_iterator &end) { this->points.insert(this->points.end(), begin, end); } diff --git a/src/libslic3r/MutablePolygon.cpp b/src/libslic3r/MutablePolygon.cpp new file mode 100644 index 0000000000..dc1d477314 --- /dev/null +++ b/src/libslic3r/MutablePolygon.cpp @@ -0,0 +1,360 @@ +#include "MutablePolygon.hpp" +#include "Line.hpp" +#include "libslic3r.h" + +namespace Slic3r { + +// Remove exact duplicate points. May reduce the polygon down to empty polygon. +void remove_duplicates(MutablePolygon &polygon) +{ + if (! polygon.empty()) { + auto begin = polygon.begin(); + auto it = begin; + for (++ it; it != begin;) { + auto prev = it.prev(); + if (*prev == *it) + it = it.remove(); + else + ++ it; + } + } +} + +// Remove nearly duplicate points. May reduce the polygon down to empty polygon. +void remove_duplicates(MutablePolygon &polygon, double eps) +{ + if (! polygon.empty()) { + auto eps2 = eps * eps; + auto begin = polygon.begin(); + auto it = begin; + for (++ it; it != begin;) { + auto prev = it.prev(); + if ((*it - *prev).cast().squaredNorm() < eps2) + it = it.remove(); + else + ++ it; + } + } +} + +// Adapted from Cura ConstPolygonRef::smooth_corner_complex() by Tim Kuipers. +// A concave corner at it1 with position p1 has been removed by the caller between it0 and it2, where |p2 - p0| < shortcut_length. +// Now try to close a concave crack by walking left from it0 and right from it2 as long as the new clipping edge is smaller than shortcut_length +// and the new clipping edge is still inside the polygon (it is a diagonal, it does not intersect polygon boundary). +// Once the traversal stops (always at a clipping edge shorter than shortcut_length), the final trapezoid is clipped with a new clipping edge of shortcut_length. +// Return true if a hole was completely closed (degenerated to an empty polygon) or a single CCW triangle was left, which is not to be simplified any further. +// it0, it2 are updated to the final clipping edge. +static bool clip_narrow_corner( + const Vec2i64 p1, + MutablePolygon::iterator &it0, + MutablePolygon::iterator &it2, + MutablePolygon::range &unprocessed_range, + int64_t dist2_current, + const int64_t shortcut_length) +{ + MutablePolygon &polygon = it0.polygon(); + assert(polygon.size() >= 2); + + const int64_t shortcut_length2 = sqr(shortcut_length); + + enum Status { + Free, + Blocked, + Far, + }; + Status forward = Free; + Status backward = Free; + + Vec2i64 p0 = it0->cast(); + Vec2i64 p2 = it2->cast(); + Vec2i64 p02; + Vec2i64 p22; + int64_t dist2_next = 0; + + // As long as there is at least a single triangle left in the polygon. + while (polygon.size() >= 3) { + assert(dist2_current <= shortcut_length2); + if (forward == Far && backward == Far) { + p02 = it0.prev()->cast(); + p22 = it2.next()->cast(); + auto d2 = (p22 - p02).squaredNorm(); + if (d2 <= shortcut_length2) { + // The region was narrow until now and it is still narrow. Trim at both sides. + it0 = unprocessed_range.remove_back(it0).prev(); + it2 = unprocessed_range.remove_front(it2); + if (polygon.size() <= 2) + // A hole degenerated to an empty polygon. + return true; + forward = Free; + backward = Free; + dist2_current = d2; + p0 = p02; + p2 = p22; + } else { + // The region is widening. Stop traversal and trim the final trapezoid. + dist2_next = d2; + break; + } + } else if (forward != Free && backward != Free) + // One of the corners is blocked, the other is blocked or too far. Stop traversal. + break; + // Try to proceed by flipping a diagonal. + // Progress by keeping the distance of the clipping edge end points equal to initial p1. + //FIXME This is an arbitrary condition, maybe a more local condition will be better (take a shorter diagonal?). + if (forward == Free && (backward != Free || (p2 - p1).squaredNorm() < (p0 - p1).cast().squaredNorm())) { + p22 = it2.next()->cast(); + if (cross2(p2 - p0, p22 - p0) > 0) + forward = Blocked; + else { + // New clipping edge lenght. + auto d2 = (p22 - p0).squaredNorm(); + if (d2 > shortcut_length2) { + forward = Far; + dist2_next = d2; + } else { + forward = Free; + // Make one step in the forward direction. + it2 = unprocessed_range.remove_front(it2); + p2 = p22; + dist2_current = d2; + } + } + } else { + assert(backward == Free); + p02 = it0.prev()->cast(); + if (cross2(p02 - p2, p0 - p2) > 0) + backward = Blocked; + else { + // New clipping edge lenght. + auto d2 = (p2 - p02).squaredNorm(); + if (d2 > shortcut_length2) { + backward = Far; + dist2_next = d2; + } else { + backward = Free; + // Make one step in the backward direction. + it0 = unprocessed_range.remove_back(it0).prev(); + p0 = p02; + dist2_current = d2; + } + } + } + } + + assert(dist2_current <= shortcut_length2); + assert(polygon.size() >= 2); + assert(polygon.size() == 2 || forward == Blocked || forward == Far); + assert(polygon.size() == 2 || backward == Blocked || backward == Far); + + if (polygon.size() <= 3) { + // A hole degenerated to an empty polygon, or a tiny triangle remained. +#ifndef NDEBUG + bool blocked = forward == Blocked || backward == Blocked; + assert(polygon.size() < 3 || + // Remaining triangle is CCW oriented. Both sides must be "blocked", but the other side may have not been + // updated after the the p02 / p22 became united into a single point. + blocked || + // Remaining triangle is concave, however both of its arms are long. + (forward == Far && backward == Far)); + if (polygon.size() == 3) { + // Verify that the remaining triangle is CCW or CW. + p02 = it0.prev()->cast(); + p22 = it2.next()->cast(); + assert(p02 == p22); + auto orient1 = cross2(p02 - p2, p0 - p2); + auto orient2 = cross2(p2 - p0, p22 - p0); + assert(orient1 > 0 == blocked); + assert(orient2 > 0 == blocked); + } +#endif // _NDEBUG + if (polygon.size() < 3 || (forward == Far && backward == Far)) { + polygon.clear(); + } else { + // The remaining triangle is CCW oriented, keep it. + assert(forward == Blocked || backward == Blocked); + } + return true; + } + + assert(dist2_current <= shortcut_length2); + if ((forward == Blocked && backward == Blocked) || dist2_current > sqr(shortcut_length - int64_t(SCALED_EPSILON))) { + // The crack is filled, keep the last clipping edge. + } else if (dist2_next < sqr(shortcut_length - int64_t(SCALED_EPSILON))) { + // To avoid creating tiny edges. + if (forward == Far) + it0 = unprocessed_range.remove_back(it0).prev(); + if (backward == Far) + it2 = unprocessed_range.remove_front(it2); + if (polygon.size() <= 2) + // A hole degenerated to an empty polygon. + return true; + } else if (forward == Blocked || backward == Blocked) { + // One side is far, the other blocked. + assert(forward == Far || backward == Far); + if (forward == Far) { + // Sort, so we will clip the 1st edge. + std::swap(p0, p2); + std::swap(p02, p22); + } + // Find point on (p0, p02) at distance shortcut_length from p2. + // Circle intersects a line at two points, however because |p2 - p0| < shortcut_length, + // only the second intersection is valid. Because |p2 - p02| > shortcut_length, such + // intersection should always be found on (p0, p02). +#ifndef NDEBUG + auto dfar2 = (p02 - p2).squaredNorm(); + assert(dfar2 >= shortcut_length2); +#endif // NDEBUG + const Vec2d v = (p02 - p0).cast(); + const Vec2d d = (p0 - p2).cast(); + const double a = v.squaredNorm(); + const double b = 2. * double(d.dot(v)); + double u = b * b - 4. * a * (d.squaredNorm() - shortcut_length2); + assert(u > 0.); + u = sqrt(u); + double t = (- b + u) / (2. * a); + assert(t > 0. && t < 1.); + (backward == Far ? *it2 : *it0) += (v.cast() * t).cast(); + } else { + // The trapezoid (it0.prev(), it0, it2, it2.next()) is widening. Trim it. + assert(forward == Far && backward == Far); + assert(dist2_next > shortcut_length2); + const double dcurrent = sqrt(double(dist2_current)); + double t = (shortcut_length - dcurrent) / (sqrt(double(dist2_next)) - dcurrent); + assert(t > 0. && t < 1.); + *it0 += ((p02 - p0).cast() * t).cast(); + *it2 += ((p22 - p2).cast() * t).cast(); + } + return false; +} + +// adapted from Cura ConstPolygonRef::smooth_outward() by Tim Kuipers. +void smooth_outward(MutablePolygon &polygon, coord_t clip_dist_scaled) +{ + remove_duplicates(polygon, scaled(0.01)); + + const auto clip_dist_scaled2 = sqr(clip_dist_scaled); + const auto clip_dist_scaled2eps = sqr(clip_dist_scaled + int64_t(SCALED_EPSILON)); + const auto foot_dist_min2 = sqr(SCALED_EPSILON); + + // Each source point will be visited exactly once. + MutablePolygon::range unprocessed_range(polygon); + while (! unprocessed_range.empty() && polygon.size() > 2) { + auto it1 = unprocessed_range.process_next(); + auto it0 = it1.prev(); + auto it2 = it1.next(); + const Point p0 = *it0; + const Point p1 = *it1; + const Point p2 = *it2; + const Vec2i64 v1 = (p0 - p1).cast(); + const Vec2i64 v2 = (p2 - p1).cast(); + if (cross2(v1, v2) > 0) { + // Concave corner. + int64_t dot = v1.dot(v2); + auto l2v1 = double(v1.squaredNorm()); + auto l2v2 = double(v2.squaredNorm()); + if (dot > 0 || Slic3r::sqr(double(dot)) * 2. < l2v1 * l2v2) { + // Angle between v1 and v2 bigger than 135 degrees. + // Simplify the sharp angle. + Vec2i64 v02 = (p2 - p0).cast(); + int64_t l2v02 = v02.squaredNorm(); + it1.remove(); + if (l2v02 < clip_dist_scaled2) { + // (p0, p2) is short. + // Clip a sharp concave corner by possibly expanding the trimming region left of it0 and right of it2. + // Updates it0, it2 and num_to_process. + if (clip_narrow_corner(p1.cast(), it0, it2, unprocessed_range, l2v02, clip_dist_scaled)) + // Trimmed down to an empty polygon or to a single CCW triangle. + return; + } else { + // Clip an obtuse corner. + if (l2v02 > clip_dist_scaled2eps) { + Vec2d v1d = v1.cast(); + Vec2d v2d = v2.cast(); + // Sort v1d, v2d, shorter first. + bool swap = l2v1 > l2v2; + if (swap) { + std::swap(v1d, v2d); + std::swap(l2v1, l2v2); + } + double lv1 = sqrt(l2v1); + double lv2 = sqrt(l2v2); + // Bisector between v1 and v2. + Vec2d bisector = v1d / lv1 + v2d / lv2; + double l2bisector = bisector.squaredNorm(); + // Squared distance of the end point of v1 to the bisector. + double d2 = l2v1 - sqr(v1d.dot(bisector)) / l2bisector; + if (d2 < foot_dist_min2) { + // Height of the p1, p0, p2 triangle is tiny. Just remove p1. + } else if (d2 < 0.25 * clip_dist_scaled2 + SCALED_EPSILON) { + // The shorter vector is too close to the bisector. Trim the shorter vector fully, + // trim the longer vector partially. + // Intersection of a circle at p2 of radius = clip_dist_scaled + // with a ray (p1, p0), take the intersection after the foot point. + // The intersection shall always exist because |p2 - p1| > clip_dist_scaled. + const double b = - 2. * v1d.cast().dot(v2d); + double u = b * b - 4. * l2v2 * (double(l2v1) - clip_dist_scaled2); + assert(u > 0.); + // Take the second intersection along v2. + double t = (- b + sqrt(u)) / (2. * l2v2); + assert(t > 0. && t < 1.); + Point pt_new = p1 + (t * v2d).cast(); +#ifndef NDEBUG + double d2new = (pt_new - (swap ? p2 : p0)).cast().squaredNorm(); + assert(std::abs(d2new - clip_dist_scaled2) < 1e-5 * clip_dist_scaled2); +#endif // NDEBUG + it2.insert(pt_new); + } else { + // Cut the corner with a line perpendicular to the bisector. + double t = sqrt(0.25 * clip_dist_scaled2 / d2); + double t2 = t * lv1 / lv2; + assert(t > 0. && t < 1.); + assert(t2 > 0. && t2 < 1.); + Point p0 = p1 + (v1d * t ).cast(); + Point p2 = p1 + (v2d * t2).cast(); + if (swap) + std::swap(p0, p2); + it2.insert(p2).insert(p0); + } + } else { + // Just remove p1. + assert(l2v02 >= clip_dist_scaled2 && l2v02 <= clip_dist_scaled2eps); + } + } + it1 = it2; + } else + ++ it1; + } else + ++ it1; + } + + if (polygon.size() == 3) { + // Check whether the last triangle is clockwise oriented (it is a hole) and its height is below clip_dist_scaled. + // If so, fill in the hole. + const Point p0 = *polygon.begin().prev(); + const Point p1 = *polygon.begin(); + const Point p2 = *polygon.begin().next(); + Vec2i64 v1 = (p0 - p1).cast(); + Vec2i64 v2 = (p2 - p1).cast(); + if (cross2(v1, v2) > 0) { + // CW triangle. Measure its height. + const Vec2i64 v3 = (p2 - p0).cast(); + int64_t l12 = v1.squaredNorm(); + int64_t l22 = v2.squaredNorm(); + int64_t l32 = v3.squaredNorm(); + if (l22 > l12 && l22 > l32) { + std::swap(v1, v2); + std::swap(l12, l22); + } else if (l32 > l12 && l32 > l22) { + v1 = v3; + l12 = l32; + } + auto h2 = l22 - sqr(double(v1.dot(v2))) / double(l12); + if (h2 < clip_dist_scaled2) + // CW triangle with a low height. Close the hole. + polygon.clear(); + } + } else if (polygon.size() < 3) + polygon.clear(); +} + +} // namespace Slic3r diff --git a/src/libslic3r/MutablePolygon.hpp b/src/libslic3r/MutablePolygon.hpp new file mode 100644 index 0000000000..ef2b612211 --- /dev/null +++ b/src/libslic3r/MutablePolygon.hpp @@ -0,0 +1,335 @@ +#ifndef slic3r_MutablePolygon_hpp_ +#define slic3r_MutablePolygon_hpp_ + +#include "Point.hpp" +#include "Polygon.hpp" + +namespace Slic3r { + +// Polygon implemented as a loop of double linked elements. +// All elements are allocated in a single std::vector<>, thus integer indices are used for +// referencing the previous and next element and inside iterators to survive reallocation +// of the vector. +class MutablePolygon +{ +public: + using IndexType = int32_t; + using PointType = Point; + class const_iterator { + public: + bool operator==(const const_iterator &rhs) const { assert(m_data == rhs.m_data); assert(this->valid()); return m_idx == rhs.m_idx; } + bool operator!=(const const_iterator &rhs) const { return ! (*this == rhs); } + const_iterator& operator--() { assert(this->valid()); m_idx = m_data->at(m_idx).prev; return *this; } + const_iterator operator--(int) { const_iterator result(*this); --(*this); return result; } + const_iterator& operator++() { assert(this->valid()); m_idx = m_data->at(m_idx).next; return *this; } + const_iterator operator++(int) { const_iterator result(*this); ++(*this); return result; } + const_iterator prev() const { assert(this->valid()); return { m_data, m_data->at(m_idx).prev }; } + const_iterator next() const { assert(this->valid()); return { m_data, m_data->at(m_idx).next }; } + bool valid() const { return m_idx >= 0; } + const PointType& operator*() const { return m_data->at(m_idx).point; } + const PointType* operator->() const { return &m_data->at(m_idx).point; } + const MutablePolygon& polygon() const { assert(this->valid()); return *m_data; } + IndexType size() const { assert(this->valid()); return m_data->size(); } + private: + const_iterator(const MutablePolygon *data, IndexType idx) : m_data(data), m_idx(idx) {} + friend class MutablePolygon; + const MutablePolygon *m_data; + IndexType m_idx; + }; + + class iterator { + public: + bool operator==(const iterator &rhs) const { assert(m_data == rhs.m_data); assert(this->valid()); return m_idx == rhs.m_idx; } + bool operator!=(const iterator &rhs) const { return !(*this == rhs); } + iterator& operator--() { assert(this->valid()); m_idx = m_data->at(m_idx).prev; return *this; } + iterator operator--(int) { iterator result(*this); --(*this); return result; } + iterator& operator++() { assert(this->valid()); m_idx = m_data->at(m_idx).next; return *this; } + iterator operator++(int) { iterator result(*this); ++(*this); return result; } + iterator prev() const { assert(this->valid()); return { m_data, m_data->at(m_idx).prev }; } + iterator next() const { assert(this->valid()); return { m_data, m_data->at(m_idx).next }; } + bool valid() const { return m_idx >= 0; } + PointType& operator*() const { return m_data->at(m_idx).point; } + PointType* operator->() const { return &m_data->at(m_idx).point; } + MutablePolygon& polygon() const { assert(this->valid()); return *m_data; } + IndexType size() const { assert(this->valid()); return m_data->size(); } + iterator& remove() { this->m_idx = m_data->remove(*this).m_idx; return *this; } + iterator insert(const PointType pt) const { return m_data->insert(*this, pt); } + private: + iterator(MutablePolygon *data, IndexType idx) : m_data(data), m_idx(idx) {} + friend class MutablePolygon; + MutablePolygon *m_data; + IndexType m_idx; + friend class range; + }; + + // Iterator range for maintaining a range of unprocessed items, see smooth_outward(). + class range + { + public: + range(MutablePolygon& poly) : range(poly.begin(), poly.end()) {} + range(MutablePolygon::iterator begin, MutablePolygon::iterator end) : m_begin(begin), m_end(end) {} + + // Start of a range, inclusive. If range is empty, then ! begin().valid(). + MutablePolygon::iterator begin() const { return m_begin; } + // End of a range, inclusive. If range is empty, then ! end().valid(). + MutablePolygon::iterator end() const { return m_end; } + // Is the range empty? + bool empty() const { return !m_begin.valid(); } + + // Return begin() and shorten the range by advancing front. + MutablePolygon::iterator process_next() { + assert(!this->empty()); + MutablePolygon::iterator out = m_begin; + this->advance_front(); + return out; + } + + void advance_front() { + assert(! this->empty()); + if (m_begin == m_end) + this->make_empty(); + else + ++ m_begin; + } + + void retract_back() { + assert(! this->empty()); + if (m_begin == m_end) + this->make_empty(); + else + -- m_end; + } + + MutablePolygon::iterator remove_front(MutablePolygon::iterator it) { + if (! this->empty() && m_begin == it) + this->advance_front(); + return it.remove(); + } + + MutablePolygon::iterator remove_back(MutablePolygon::iterator it) { + if (! this->empty() && m_end == it) + this->retract_back(); + return it.remove(); + } + + private: + // Range from begin to end, inclusive. + // If the range is valid, then both m_begin and m_end are invalid. + MutablePolygon::iterator m_begin; + MutablePolygon::iterator m_end; + + void make_empty() { + m_begin.m_idx = -1; + m_end.m_idx = -1; + } + }; + + MutablePolygon() = default; + MutablePolygon(const Polygon &rhs, size_t reserve = 0) : MutablePolygon(rhs.points.begin(), rhs.points.end(), reserve) {} + MutablePolygon(std::initializer_list rhs, size_t reserve = 0) : MutablePolygon(rhs.begin(), rhs.end(), reserve) {} + + template + MutablePolygon(IT begin, IT end, size_t reserve = 0) { + this->assign_inner(begin, end, reserve); + }; + + template + void assign(IT begin, IT end, size_t reserve = 0) { + m_data.clear(); + m_head = IndexType(-1); + m_head_free = { IndexType(-1) }; + this->assign_inner(begin, end, reserve); + }; + + void assign(const Polygon &rhs, size_t reserve = 0) { + assign(rhs.points.begin(), rhs.points.end(), reserve); + } + + void polygon(Polygon &out) const { + out.points.clear(); + if (this->valid()) { + out.points.reserve(this->size()); + auto it = this->cbegin(); + out.points.emplace_back(*it); + for (++ it; it != this->cbegin(); ++ it) + out.points.emplace_back(*it); + } + }; + + Polygon polygon() const { + Polygon out; + this->polygon(out); + return out; + }; + + bool empty() const { return this->m_size == 0; } + size_t size() const { return this->m_size; } + size_t capacity() const { return this->m_data.capacity(); } + bool valid() const { return this->m_size >= 3; } + void clear() { m_data.clear(); m_size = 0; m_head = IndexType(-1); m_head_free = IndexType(-1); } + + iterator begin() { return { this, m_head }; } + const_iterator cbegin() const { return { this, m_head }; } + const_iterator begin() const { return this->cbegin(); } + // End points to the last item before roll over. This is different from the usual end() concept! + iterator end() { return { this, this->empty() ? -1 : this->at(m_head).prev }; } + const_iterator cend() const { return { this, this->empty() ? -1 : this->at(m_head).prev }; } + const_iterator end() const { return this->cend(); } + + // Returns iterator following the removed element. Returned iterator will become invalid if last point is removed. + // If begin() is removed, then the next element will become the new begin(). + iterator remove(const iterator it) { assert(it.m_data == this); return { this, this->remove(it.m_idx) }; } + // Insert a new point before it. Returns iterator to the newly inserted point. + // begin() will not change, end() may point to the newly inserted point. + iterator insert(const iterator it, const PointType pt) { assert(it.m_data == this); return { this, this->insert(it.m_idx, pt) }; } + +private: + struct LinkedPoint { + // 8 bytes + PointType point; + // 4 bytes + IndexType prev; + // 4 bytes + IndexType next; + }; + std::vector m_data; + // Number of points in the linked list. + IndexType m_size { 0 }; + IndexType m_head { IndexType(-1) }; + // Head of the free list. + IndexType m_head_free { IndexType(-1) }; + + LinkedPoint& at(IndexType i) { return m_data[i]; } + const LinkedPoint& at(IndexType i) const { return m_data[i]; } + + template + void assign_inner(IT begin, IT end, size_t reserve) { + m_size = IndexType(end - begin); + if (m_size > 0) { + m_head = 0; + m_data.reserve(std::max(m_size, reserve)); + auto i = IndexType(-1); + auto j = IndexType(1); + for (auto it = begin; it != end; ++ it) + m_data.push_back({ *it, i ++, j ++ }); + m_data.front().prev = m_size - 1; + m_data.back ().next = 0; + } + }; + + IndexType remove(const IndexType i) { + assert(i >= 0); + assert(m_size > 0); + assert(m_head != -1); + LinkedPoint &lp = this->at(i); + IndexType prev = lp.prev; + IndexType next = lp.next; + lp.next = m_head_free; + m_head_free = i; + if (-- m_size == 0) + m_head = -1; + else if (m_head == i) + m_head = next; + assert(! this->empty() || (prev == i && next == i)); + if (this->empty()) + return IndexType(-1); + this->at(prev).next = next; + this->at(next).prev = prev; + return next; + } + + IndexType insert(const IndexType i, const Point pt) { + assert(i >= 0); + IndexType n; + IndexType j = this->at(i).prev; + if (m_head_free == -1) { + // Allocate a new item. + n = IndexType(m_data.size()); + m_data.push_back({ pt, j, i }); + } else { + n = m_head_free; + LinkedPoint &nlp = this->at(n); + m_head_free = nlp.next; + nlp = { pt, j, i }; + } + this->at(j).next = n; + this->at(i).prev = n; + ++ m_size; + return n; + } + + /* + IndexType insert(const IndexType i, const Point pt) { + assert(i >= 0); + if (this->at(i).point == pt) + return i; + IndexType j = this->at(i).next; + if (this->at(j).point == pt) + return i; + IndexType n; + if (m_head_free == -1) { + // Allocate a new item. + n = IndexType(m_data.size()); + m_data.push_back({ pt, i, j }); + } else { + LinkedPoint &nlp = this->at(m_head_free); + m_head_free = nlp.next; + nlp = { pt, i, j }; + } + this->at(i).next = n; + this->at(j).prev = n; + ++ m_size; + return n; + } + */ +}; + +inline bool operator==(const MutablePolygon &p1, const MutablePolygon &p2) +{ + if (p1.size() != p2.size()) + return false; + if (p1.empty()) + return true; + auto begin = p1.cbegin(); + auto it = begin; + auto it2 = p2.cbegin(); + for (;;) { + if (! (*it == *it2)) + return false; + if (++ it == begin) + return true; + ++ it2; + } +} + +inline bool operator!=(const MutablePolygon &p1, const MutablePolygon &p2) { return ! (p1 == p2); } + +// Remove exact duplicate points. May reduce the polygon down to empty polygon. +void remove_duplicates(MutablePolygon &polygon); +void remove_duplicates(MutablePolygon &polygon, double eps); + +void smooth_outward(MutablePolygon &polygon, coord_t clip_dist_scaled); + +inline Polygon smooth_outward(Polygon polygon, coord_t clip_dist_scaled) +{ + MutablePolygon mp(polygon, polygon.size() * 2); + smooth_outward(mp, clip_dist_scaled); + mp.polygon(polygon); + return polygon; +} + +inline Polygons smooth_outward(Polygons polygons, coord_t clip_dist_scaled) +{ + MutablePolygon mp; + for (Polygon &polygon : polygons) { + mp.assign(polygon, polygon.size() * 2); + smooth_outward(mp, clip_dist_scaled); + mp.polygon(polygon); + } + polygons.erase(std::remove_if(polygons.begin(), polygons.end(), [](const auto &p){ return p.empty(); }), polygons.end()); + return polygons; +} + +} + +#endif // slic3r_MutablePolygon_hpp_ diff --git a/src/libslic3r/OpenVDBUtils.cpp b/src/libslic3r/OpenVDBUtils.cpp index 0f5bfa157c..e09fceed7a 100644 --- a/src/libslic3r/OpenVDBUtils.cpp +++ b/src/libslic3r/OpenVDBUtils.cpp @@ -22,74 +22,54 @@ namespace Slic3r { class TriangleMeshDataAdapter { public: const TriangleMesh &mesh; - + float voxel_scale; + size_t polygonCount() const { return mesh.its.indices.size(); } size_t pointCount() const { return mesh.its.vertices.size(); } size_t vertexCount(size_t) const { return 3; } - + // Return position pos in local grid index space for polygon n and vertex v - void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const; + // The actual mesh will appear to openvdb as scaled uniformly by voxel_size + // And the voxel count per unit volume can be affected this way. + void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const + { + auto vidx = size_t(mesh.its.indices[n](Eigen::Index(v))); + Slic3r::Vec3d p = mesh.its.vertices[vidx].cast() * voxel_scale; + pos = {p.x(), p.y(), p.z()}; + } + + TriangleMeshDataAdapter(const TriangleMesh &m, float voxel_sc = 1.f) + : mesh{m}, voxel_scale{voxel_sc} {}; }; -class Contour3DDataAdapter { -public: - const sla::Contour3D &mesh; - - size_t polygonCount() const { return mesh.faces3.size() + mesh.faces4.size(); } - size_t pointCount() const { return mesh.points.size(); } - size_t vertexCount(size_t n) const { return n < mesh.faces3.size() ? 3 : 4; } - - // Return position pos in local grid index space for polygon n and vertex v - void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const; -}; - -void TriangleMeshDataAdapter::getIndexSpacePoint(size_t n, - size_t v, - openvdb::Vec3d &pos) const -{ - auto vidx = size_t(mesh.its.indices[n](Eigen::Index(v))); - Slic3r::Vec3d p = mesh.its.vertices[vidx].cast(); - pos = {p.x(), p.y(), p.z()}; -} - -void Contour3DDataAdapter::getIndexSpacePoint(size_t n, - size_t v, - openvdb::Vec3d &pos) const -{ - size_t vidx = 0; - if (n < mesh.faces3.size()) vidx = size_t(mesh.faces3[n](Eigen::Index(v))); - else vidx = size_t(mesh.faces4[n - mesh.faces3.size()](Eigen::Index(v))); - - Slic3r::Vec3d p = mesh.points[vidx]; - pos = {p.x(), p.y(), p.z()}; -} - - // TODO: Do I need to call initialize? Seems to work without it as well but the // docs say it should be called ones. It does a mutex lock-unlock sequence all // even if was called previously. -openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh, +openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh & mesh, const openvdb::math::Transform &tr, - float exteriorBandWidth, - float interiorBandWidth, - int flags) + float voxel_scale, + float exteriorBandWidth, + float interiorBandWidth, + int flags) { openvdb::initialize(); - TriangleMeshPtrs meshparts = mesh.split(); + TriangleMeshPtrs meshparts_raw = mesh.split(); + auto meshparts = reserve_vector>(meshparts_raw.size()); + for (auto *p : meshparts_raw) + meshparts.emplace_back(p); - auto it = std::remove_if(meshparts.begin(), meshparts.end(), - [](TriangleMesh *m){ - m->require_shared_vertices(); - return !m->is_manifold() || m->volume() < EPSILON; - }); + auto it = std::remove_if(meshparts.begin(), meshparts.end(), [](auto &m) { + m->require_shared_vertices(); + return m->volume() < EPSILON; + }); meshparts.erase(it, meshparts.end()); openvdb::FloatGrid::Ptr grid; - for (TriangleMesh *m : meshparts) { + for (auto &m : meshparts) { auto subgrid = openvdb::tools::meshToVolume( - TriangleMeshDataAdapter{*m}, tr, exteriorBandWidth, + TriangleMeshDataAdapter{*m, voxel_scale}, tr, exteriorBandWidth, interiorBandWidth, flags); if (grid && subgrid) openvdb::tools::csgUnion(*grid, *subgrid); @@ -106,19 +86,9 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh, interiorBandWidth, flags); } - return grid; -} + grid->insertMeta("voxel_scale", openvdb::FloatMetadata(voxel_scale)); -openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D &mesh, - const openvdb::math::Transform &tr, - float exteriorBandWidth, - float interiorBandWidth, - int flags) -{ - openvdb::initialize(); - return openvdb::tools::meshToVolume( - Contour3DDataAdapter{mesh}, tr, exteriorBandWidth, interiorBandWidth, - flags); + return grid; } template @@ -128,20 +98,25 @@ sla::Contour3D _volumeToMesh(const Grid &grid, bool relaxDisorientedTriangles) { openvdb::initialize(); - + std::vector points; std::vector triangles; std::vector quads; - + openvdb::tools::volumeToMesh(grid, points, triangles, quads, isovalue, adaptivity, relaxDisorientedTriangles); - + + float scale = 1.; + try { + scale = grid.template metaValue("voxel_scale"); + } catch (...) { } + sla::Contour3D ret; ret.points.reserve(points.size()); ret.faces3.reserve(triangles.size()); ret.faces4.reserve(quads.size()); - for (auto &v : points) ret.points.emplace_back(to_vec3d(v)); + for (auto &v : points) ret.points.emplace_back(to_vec3d(v) / scale); for (auto &v : triangles) ret.faces3.emplace_back(to_vec3i(v)); for (auto &v : quads) ret.faces4.emplace_back(to_vec4i(v)); @@ -166,9 +141,18 @@ sla::Contour3D grid_to_contour3d(const openvdb::FloatGrid &grid, relaxDisorientedTriangles); } -openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid, double iso, double er, double ir) +openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid, + double iso, + double er, + double ir) { - return openvdb::tools::levelSetRebuild(grid, float(iso), float(er), float(ir)); + auto new_grid = openvdb::tools::levelSetRebuild(grid, float(iso), + float(er), float(ir)); + + // Copies voxel_scale metadata, if it exists. + new_grid->insertMeta(*grid.deepCopyMeta()); + + return new_grid; } } // namespace Slic3r diff --git a/src/libslic3r/OpenVDBUtils.hpp b/src/libslic3r/OpenVDBUtils.hpp index aa4b5154a8..5df816abb1 100644 --- a/src/libslic3r/OpenVDBUtils.hpp +++ b/src/libslic3r/OpenVDBUtils.hpp @@ -21,14 +21,16 @@ inline Vec3d to_vec3d(const openvdb::Vec3s &v) { return to_vec3f(v).cast inline Vec3i to_vec3i(const openvdb::Vec3I &v) { return Vec3i{int(v[0]), int(v[1]), int(v[2])}; } inline Vec4i to_vec4i(const openvdb::Vec4I &v) { return Vec4i{int(v[0]), int(v[1]), int(v[2]), int(v[3])}; } +// Here voxel_scale defines the scaling of voxels which affects the voxel count. +// 1.0 value means a voxel for every unit cube. 2 means the model is scaled to +// be 2x larger and the voxel count is increased by the increment in the scaled +// volume, thus 4 times. This kind a sampling accuracy selection is not +// achievable through the Transform parameter. (TODO: or is it?) +// The resulting grid will contain the voxel_scale in its metadata under the +// "voxel_scale" key to be used in grid_to_mesh function. openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh & mesh, const openvdb::math::Transform &tr = {}, - float exteriorBandWidth = 3.0f, - float interiorBandWidth = 3.0f, - int flags = 0); - -openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D & mesh, - const openvdb::math::Transform &tr = {}, + float voxel_scale = 1.f, float exteriorBandWidth = 3.0f, float interiorBandWidth = 3.0f, int flags = 0); diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 7cfdc58473..6ec4dbf6b8 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -8,7 +8,7 @@ namespace Slic3r { -static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, Flow &flow, const float tolerance) +static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, const float tolerance) { ExtrusionPaths paths; ExtrusionPath path(role); @@ -62,15 +62,15 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thi path.polyline.append(line.b); // Convert from spacing to extrusion width based on the extrusion model // of a square extrusion ended with semi circles. - flow.width = unscale(w) + flow.height * float(1. - 0.25 * PI); + Flow new_flow = flow.with_width(unscale(w) + flow.height() * float(1. - 0.25 * PI)); #ifdef SLIC3R_DEBUG printf(" filling %f gap\n", flow.width); #endif - path.mm3_per_mm = flow.mm3_per_mm(); - path.width = flow.width; - path.height = flow.height; + path.mm3_per_mm = new_flow.mm3_per_mm(); + path.width = new_flow.width(); + path.height = new_flow.height(); } else { - thickness_delta = fabs(scale_(flow.width) - w); + thickness_delta = fabs(scale_(flow.width()) - w); if (thickness_delta <= tolerance) { // the width difference between this line and the current flow width is // within the accepted tolerance @@ -88,7 +88,7 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thi return paths; } -static void variable_width(const ThickPolylines& polylines, ExtrusionRole role, Flow flow, std::vector &out) +static void variable_width(const ThickPolylines& polylines, ExtrusionRole role, const Flow &flow, std::vector &out) { // This value determines granularity of adaptive width, as G-code does not allow // variable extrusion within a single move; this value shall only affect the amount @@ -205,8 +205,8 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime paths, intersection_pl({ polygon }, perimeter_generator.lower_slices_polygons()), role, - is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm(), - is_external ? perimeter_generator.ext_perimeter_flow.width : perimeter_generator.perimeter_flow.width, + is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm(), + is_external ? perimeter_generator.ext_perimeter_flow.width() : perimeter_generator.perimeter_flow.width(), (float)perimeter_generator.layer_height); // get overhang paths by checking what parts of this loop fall @@ -217,8 +217,8 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime diff_pl({ polygon }, perimeter_generator.lower_slices_polygons()), erOverhangPerimeter, perimeter_generator.mm3_per_mm_overhang(), - perimeter_generator.overhang_flow.width, - perimeter_generator.overhang_flow.height); + perimeter_generator.overhang_flow.width(), + perimeter_generator.overhang_flow.height()); // Reapply the nearest point search for starting point. // We allow polyline reversal because Clipper may have randomly reversed polylines during clipping. @@ -226,8 +226,8 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime } else { ExtrusionPath path(role); path.polyline = polygon.split_at_first_point(); - path.mm3_per_mm = is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm(); - path.width = is_external ? perimeter_generator.ext_perimeter_flow.width : perimeter_generator.perimeter_flow.width; + path.mm3_per_mm = is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm(); + path.width = is_external ? perimeter_generator.ext_perimeter_flow.width() : perimeter_generator.perimeter_flow.width(); path.height = (float)perimeter_generator.layer_height; paths.push_back(path); } @@ -286,7 +286,7 @@ void PerimeterGenerator::process() m_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm(); coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width(); coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing(); - coord_t ext_perimeter_spacing2 = this->ext_perimeter_flow.scaled_spacing(this->perimeter_flow); + coord_t ext_perimeter_spacing2 = scaled(0.5f * (this->ext_perimeter_flow.spacing() + this->perimeter_flow.spacing())); // overhang perimeters m_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm(); @@ -346,7 +346,7 @@ void PerimeterGenerator::process() if (this->config->thin_walls) { // the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width // (actually, something larger than that still may exist due to mitering or other causes) - coord_t min_width = coord_t(scale_(this->ext_perimeter_flow.nozzle_diameter / 3)); + coord_t min_width = coord_t(scale_(this->ext_perimeter_flow.nozzle_diameter() / 3)); ExPolygons expp = offset2_ex( // medial axis requires non-overlapping geometry diff_ex(to_polygons(last), diff --git a/src/libslic3r/Platform.cpp b/src/libslic3r/Platform.cpp new file mode 100644 index 0000000000..ae02c42b38 --- /dev/null +++ b/src/libslic3r/Platform.cpp @@ -0,0 +1,71 @@ +#include "Platform.hpp" + +#include +#include + +namespace Slic3r { + +static auto s_platform = Platform::Uninitialized; +static auto s_platform_flavor = PlatformFlavor::Uninitialized; + +void detect_platform() +{ +#if defined(_WIN32) + BOOST_LOG_TRIVIAL(info) << "Platform: Windows"; + s_platform = Platform::Windows; + s_platform_flavor = PlatformFlavor::Generic; +#elif defined(__APPLE__) + BOOST_LOG_TRIVIAL(info) << "Platform: OSX"; + s_platform = Platform::OSX; + s_platform_flavor = PlatformFlavor::Generic; +#elif defined(__linux__) + BOOST_LOG_TRIVIAL(info) << "Platform: Linux"; + s_platform = Platform::Linux; + s_platform_flavor = PlatformFlavor::GenericLinux; + // Test for Chromium. + { + FILE *f = ::fopen("/proc/version", "rt"); + if (f) { + char buf[4096]; + // Read the 1st line. + if (::fgets(buf, 4096, f)) { + if (strstr(buf, "Chromium OS") != nullptr) { + s_platform_flavor = PlatformFlavor::LinuxOnChromium; + BOOST_LOG_TRIVIAL(info) << "Platform flavor: LinuxOnChromium"; + } else if (strstr(buf, "microsoft") != nullptr || strstr(buf, "Microsoft") != nullptr) { + if (boost::filesystem::exists("/run/WSL") && getenv("WSL_INTEROP") != nullptr) { + BOOST_LOG_TRIVIAL(info) << "Platform flavor: WSL2"; + s_platform_flavor = PlatformFlavor::WSL2; + } else { + BOOST_LOG_TRIVIAL(info) << "Platform flavor: WSL"; + s_platform_flavor = PlatformFlavor::WSL; + } + } + } + ::fclose(f); + } + } +#elif defined(__OpenBSD__) + BOOST_LOG_TRIVIAL(info) << "Platform: OpenBSD"; + s_platform = Platform::BSDUnix; + s_platform_flavor = PlatformFlavor::OpenBSD; +#else + // This should not happen. + BOOST_LOG_TRIVIAL(info) << "Platform: Unknown"; + static_assert(false, "Unknown platform detected"); + s_platform = Platform::Unknown; + s_platform_flavor = PlatformFlavor::Unknown; +#endif +} + +Platform platform() +{ + return s_platform; +} + +PlatformFlavor platform_flavor() +{ + return s_platform_flavor; +} + +} // namespace Slic3r diff --git a/src/libslic3r/Platform.hpp b/src/libslic3r/Platform.hpp new file mode 100644 index 0000000000..735728e89a --- /dev/null +++ b/src/libslic3r/Platform.hpp @@ -0,0 +1,41 @@ +#ifndef SLIC3R_Platform_HPP +#define SLIC3R_Platform_HPP + +namespace Slic3r { + +enum class Platform +{ + Uninitialized, + Unknown, + Windows, + OSX, + Linux, + BSDUnix, +}; + +enum class PlatformFlavor +{ + Uninitialized, + Unknown, + // For Windows and OSX, until we need to be more specific. + Generic, + // For Platform::Linux + GenericLinux, + LinuxOnChromium, + // Microsoft's Windows on Linux (Linux kernel simulated on NTFS kernel) + WSL, + // Microsoft's Windows on Linux, version 2 (virtual machine) + WSL2, + // For Platform::BSDUnix + OpenBSD, +}; + +// To be called on program start-up. +void detect_platform(); + +Platform platform(); +PlatformFlavor platform_flavor(); + +} // namespace Slic3r + +#endif // SLIC3R_Platform_HPP diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index d11af8b58c..f38a7066b1 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -56,11 +56,21 @@ typedef Eigen::Transform Transform3d inline bool operator<(const Vec2d &lhs, const Vec2d &rhs) { return lhs(0) < rhs(0) || (lhs(0) == rhs(0) && lhs(1) < rhs(1)); } -// One likely does not want to perform the cross product with a 32bit accumulator. -//inline int32_t cross2(const Vec2i32 &v1, const Vec2i32 &v2) { return v1(0) * v2(1) - v1(1) * v2(0); } -inline int64_t cross2(const Vec2i64 &v1, const Vec2i64 &v2) { return v1(0) * v2(1) - v1(1) * v2(0); } -inline float cross2(const Vec2f &v1, const Vec2f &v2) { return v1(0) * v2(1) - v1(1) * v2(0); } -inline double cross2(const Vec2d &v1, const Vec2d &v2) { return v1(0) * v2(1) - v1(1) * v2(0); } +template +int32_t cross2(const Eigen::MatrixBase> &v1, const Eigen::MatrixBase> &v2) = delete; + +template +inline T cross2(const Eigen::MatrixBase> &v1, const Eigen::MatrixBase> &v2) +{ + return v1(0) * v2(1) - v1(1) * v2(0); +} + +template +inline typename Derived::Scalar cross2(const Eigen::MatrixBase &v1, const Eigen::MatrixBase &v2) +{ + static_assert(std::is_same::value, "cross2(): Scalar types of 1st and 2nd operand must be equal."); + return v1(0) * v2(1) - v1(1) * v2(0); +} template inline Eigen::Matrix perp(const Eigen::MatrixBase> &v) { return Eigen::Matrix(- v.y(), v.x()); } @@ -403,6 +413,25 @@ unscaled(const Eigen::Matrix &v) noexcept return v.template cast() * SCALING_FACTOR; } +// Align a coordinate to a grid. The coordinate may be negative, +// the aligned value will never be bigger than the original one. +inline coord_t align_to_grid(const coord_t coord, const coord_t spacing) { + // Current C++ standard defines the result of integer division to be rounded to zero, + // for both positive and negative numbers. Here we want to round down for negative + // numbers as well. + coord_t aligned = (coord < 0) ? + ((coord - spacing + 1) / spacing) * spacing : + (coord / spacing) * spacing; + assert(aligned <= coord); + return aligned; +} +inline Point align_to_grid(Point coord, Point spacing) + { return Point(align_to_grid(coord.x(), spacing.x()), align_to_grid(coord.y(), spacing.y())); } +inline coord_t align_to_grid(coord_t coord, coord_t spacing, coord_t base) + { return base + align_to_grid(coord - base, spacing); } +inline Point align_to_grid(Point coord, Point spacing, Point base) + { return Point(align_to_grid(coord.x(), spacing.x(), base.x()), align_to_grid(coord.y(), spacing.y(), base.y())); } + } // namespace Slic3r // start Boost diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index aa72b4244d..dd2d68d46f 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -11,7 +11,9 @@ namespace Slic3r { class Polygon; -typedef std::vector Polygons; +using Polygons = std::vector; +using PolygonPtrs = std::vector; +using ConstPolygonPtrs = std::vector; class Polygon : public MultiPoint { diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 6c500b84a6..7db61a20f1 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -427,17 +427,18 @@ const std::vector& Preset::print_options() "bridge_acceleration", "first_layer_acceleration", "default_acceleration", "skirts", "skirt_distance", "skirt_height", "draft_shield", "min_skirt_length", "brim_width", "brim_offset", "brim_type", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers", "raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion", - "support_material_pattern", "support_material_with_sheath", "support_material_spacing", - "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", - "support_material_interface_pattern", "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", + "support_material_pattern", "support_material_with_sheath", "support_material_spacing", "support_material_closing_radius", "support_material_style", + "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", "support_material_bottom_interface_layers", + "support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops", + "support_material_contact_distance", "support_material_bottom_contact_distance", + "support_material_buildplate_only", "dont_support_bridges", "thick_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", "infill_anchor", "infill_anchor_max", "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_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits" }; return s_opts; @@ -467,7 +468,7 @@ const std::vector& Preset::machine_limits_options() static std::vector s_opts; if (s_opts.empty()) { s_opts = { - "machine_max_acceleration_extruding", "machine_max_acceleration_retracting", + "machine_max_acceleration_extruding", "machine_max_acceleration_retracting", "machine_max_acceleration_travel", "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", @@ -1393,9 +1394,8 @@ const std::vector& PhysicalPrinter::printer_options() static std::vector s_opts; if (s_opts.empty()) { s_opts = { - "preset_name", + "preset_names", "printer_technology", -// "printer_model", "host_type", "print_host", "printhost_apikey", @@ -1453,11 +1453,10 @@ bool PhysicalPrinter::has_empty_config() const void PhysicalPrinter::update_preset_names_in_config() { if (!preset_names.empty()) { - std::string name; - for (auto el : preset_names) - name += el + ";"; - name.pop_back(); - config.set_key_value("preset_name", new ConfigOptionString(name)); + std::vector& values = config.option("preset_names")->values; + values.clear(); + for (auto preset : preset_names) + values.push_back(preset); } } @@ -1482,14 +1481,13 @@ void PhysicalPrinter::update_from_config(const DynamicPrintConfig& new_config) { config.apply_only(new_config, printer_options(), false); - std::string str = config.opt_string("preset_name"); - std::set values{}; - if (!str.empty()) { - boost::split(values, str, boost::is_any_of(";")); + const std::vector& values = config.option("preset_names")->values; + + if (values.empty()) + preset_names.clear(); + else for (const std::string& val : values) preset_names.emplace(val); - } - preset_names = values; } void PhysicalPrinter::reset_presets() @@ -1817,7 +1815,7 @@ bool PhysicalPrinterCollection::delete_preset_from_printers( const std::string& return true; } -// Get list of printers which have more than one preset and "preset_name" preset is one of them +// Get list of printers which have more than one preset and "preset_names" preset is one of them std::vector PhysicalPrinterCollection::get_printers_with_preset(const std::string& preset_name) { std::vector printers; @@ -1832,7 +1830,7 @@ std::vector PhysicalPrinterCollection::get_printers_with_preset(con return printers; } -// Get list of printers which has only "preset_name" preset +// Get list of printers which has only "preset_names" preset std::vector PhysicalPrinterCollection::get_printers_with_only_preset(const std::string& preset_name) { std::vector printers; diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index d81717f0e0..6e56ad911d 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -685,9 +685,9 @@ public: // returns true if all presets were deleted successfully. bool delete_preset_from_printers(const std::string& preset_name); - // Get list of printers which have more than one preset and "preset_name" preset is one of them + // Get list of printers which have more than one preset and "preset_names" preset is one of them std::vector get_printers_with_preset( const std::string &preset_name); - // Get list of printers which has only "preset_name" preset + // Get list of printers which has only "preset_names" preset std::vector get_printers_with_only_preset( const std::string &preset_name); // Return the selected preset, without the user modifications applied. diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 7d08c93592..e7f818d085 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -876,6 +876,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool this->filament_presets[i] = loaded->name; } } + // 4) Load the project config values (the per extruder wipe matrix etc). this->project_config.apply_only(config, s_project_options); diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 643cc9a970..ce5bf1b294 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -100,7 +100,6 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "filament_spool_weight", "first_layer_acceleration", "first_layer_bed_temperature", - "first_layer_speed", "gcode_comments", "gcode_label_objects", "infill_acceleration", @@ -139,7 +138,6 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "start_filament_gcode", "toolchange_gcode", "threads", - "travel_speed", "use_firmware_retraction", "use_relative_e_distances", "use_volumetric_e", @@ -182,7 +180,6 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n } else if ( opt_key == "complete_objects" || opt_key == "filament_type" - || opt_key == "filament_soluble" || opt_key == "first_layer_temperature" || opt_key == "filament_loading_speed" || opt_key == "filament_loading_speed_start" @@ -202,6 +199,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "temperature" || opt_key == "wipe_tower" || opt_key == "wipe_tower_width" + || opt_key == "wipe_tower_brim_width" || opt_key == "wipe_tower_bridging" || opt_key == "wipe_tower_no_sparse_layers" || opt_key == "wiping_volumes_matrix" @@ -209,9 +207,17 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "cooling_tube_retraction" || opt_key == "cooling_tube_length" || opt_key == "extra_loading_move" + || opt_key == "travel_speed" + || opt_key == "first_layer_speed" || opt_key == "z_offset") { steps.emplace_back(psWipeTower); steps.emplace_back(psSkirt); + } else if (opt_key == "filament_soluble") { + steps.emplace_back(psWipeTower); + // Soluble support interface / non-soluble base interface produces non-soluble interface layers below soluble interface layers. + // Thus switching between soluble / non-soluble interface layer material may require recalculation of supports. + //FIXME Killing supports on any change of "filament_soluble" is rough. We should check for each object whether that is necessary. + osteps.emplace_back(posSupportMaterial); } else if ( opt_key == "first_layer_extrusion_width" || opt_key == "min_layer_height" @@ -1245,7 +1251,7 @@ static inline bool sequential_print_vertical_clearance_valid(const Print &print) } // Precondition: Print::validate() requires the Print::apply() to be called its invocation. -std::string Print::validate() const +std::string Print::validate(std::string* warning) const { if (m_objects.empty()) return L("All objects are outside of the print volume."); @@ -1292,7 +1298,7 @@ std::string Print::validate() const } if (m_config.gcode_flavor != gcfRepRapSprinter && m_config.gcode_flavor != gcfRepRapFirmware && - m_config.gcode_flavor != gcfRepetier && m_config.gcode_flavor != gcfMarlin) + m_config.gcode_flavor != gcfRepetier && m_config.gcode_flavor != gcfMarlinLegacy && m_config.gcode_flavor != gcfMarlinFirmware) return L("The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter, RepRapFirmware 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)."); @@ -1325,7 +1331,8 @@ std::string Print::validate() const return L("The Wipe Tower is only supported for multiple objects if they have equal layer heights"); if (slicing_params.raft_layers() != slicing_params0.raft_layers()) return L("The Wipe Tower is only supported for multiple objects if they are printed over an equal number of raft layers"); - if (object->config().support_material_contact_distance != m_objects.front()->config().support_material_contact_distance) + if (slicing_params0.gap_object_support != slicing_params.gap_object_support || + slicing_params0.gap_support_object != slicing_params.gap_support_object) return L("The Wipe Tower is only supported for multiple objects if they are printed with the same support_material_contact_distance"); if (! equal_layering(slicing_params, slicing_params0)) return L("The Wipe Tower is only supported for multiple objects if they are sliced equally."); @@ -1440,7 +1447,22 @@ std::string Print::validate() const } } } - + + // Do we have custom support data that would not be used? + // Notify the user in that case. + if (! object->has_support() && warning) { + for (const ModelVolume* mv : object->model_object()->volumes) { + bool has_enforcers = mv->is_support_enforcer() + || (mv->is_model_part() + && ! mv->supported_facets.empty() + && ! mv->supported_facets.get_facets(*mv, EnforcerBlockerType::ENFORCER).indices.empty()); + if (has_enforcers) { + *warning = "_SUPPORTS_OFF"; + break; + } + } + } + // validate first_layer_height double first_layer_height = object->config().get_abs_value("first_layer_height"); double first_layer_min_nozzle_diameter; @@ -1561,9 +1583,7 @@ Flow Print::brim_flow() const frPerimeter, width, (float)m_config.nozzle_diameter.get_at(m_regions.front()->config().perimeter_extruder-1), - (float)this->skirt_first_layer_height(), - 0 - ); + (float)this->skirt_first_layer_height()); } Flow Print::skirt_flow() const @@ -1583,9 +1603,7 @@ Flow Print::skirt_flow() const frPerimeter, width, (float)m_config.nozzle_diameter.get_at(m_objects.front()->config().support_material_extruder-1), - (float)this->skirt_first_layer_height(), - 0 - ); + (float)this->skirt_first_layer_height()); } bool Print::has_support_material() const @@ -1802,7 +1820,7 @@ void Print::_make_skirt() ExtrusionPath( erSkirt, (float)mm3_per_mm, // this will be overridden at G-code export time - flow.width, + flow.width(), (float)first_layer_height // this will be overridden at G-code export time ))); eloop.paths.back().polyline = loop.split_at_first_point(); @@ -1894,16 +1912,15 @@ bool Print::has_wipe_tower() const m_config.nozzle_diameter.values.size() > 1; } -const WipeTowerData& Print::wipe_tower_data(size_t extruders_cnt, double first_layer_height, double nozzle_diameter) const +const WipeTowerData& Print::wipe_tower_data(size_t extruders_cnt) const { // If the wipe tower wasn't created yet, make sure the depth and brim_width members are set to default. if (! is_step_done(psWipeTower) && extruders_cnt !=0) { float width = float(m_config.wipe_tower_width); - float brim_spacing = float(nozzle_diameter * 1.25f - first_layer_height * (1. - M_PI_4)); const_cast(this)->m_wipe_tower_data.depth = (900.f/width) * float(extruders_cnt - 1); - const_cast(this)->m_wipe_tower_data.brim_width = 4.5f * brim_spacing; + const_cast(this)->m_wipe_tower_data.brim_width = m_config.wipe_tower_brim_width; } return m_wipe_tower_data; @@ -1975,9 +1992,7 @@ void Print::_make_wipe_tower() // 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, m_config); + 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)); @@ -2003,8 +2018,8 @@ void Print::_make_wipe_tower() 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((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); + wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, + current_extruder_id, extruder_id, volume_to_wipe); current_extruder_id = extruder_id; } } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index e8e81a5295..91f86d0105 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -65,7 +65,7 @@ public: const PrintRegionConfig& config() const { return m_config; } // 1-based extruder identifier for this region and role. unsigned int extruder(FlowRole role) const; - Flow flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const; + Flow flow(const PrintObject &object, FlowRole role, double layer_height, bool first_layer = false) const; // Average diameter of nozzles participating on extruding this region. coordf_t nozzle_dmr_avg(const PrintConfig &print_config) const; // Average diameter of nozzles participating on extruding this region. @@ -444,7 +444,7 @@ public: bool has_brim() const; // Returns an empty string if valid, otherwise returns an error message. - std::string validate() const override; + std::string validate(std::string* warning = nullptr) const override; double skirt_first_layer_height() const; Flow brim_flow() const; Flow skirt_flow() const; @@ -493,7 +493,7 @@ public: // Wipe tower support. bool has_wipe_tower() const; - const WipeTowerData& wipe_tower_data(size_t extruders_cnt = 0, double first_layer_height = 0., double nozzle_diameter = 0.) const; + const WipeTowerData& wipe_tower_data(size_t extruders_cnt = 0) const; const ToolOrdering& tool_ordering() const { return m_tool_ordering; } std::string output_filename(const std::string &filename_base = std::string()) const override; diff --git a/src/libslic3r/PrintBase.cpp b/src/libslic3r/PrintBase.cpp index 721741d4a8..d129aaad2b 100644 --- a/src/libslic3r/PrintBase.cpp +++ b/src/libslic3r/PrintBase.cpp @@ -94,12 +94,14 @@ std::string PrintBase::output_filepath(const std::string &path, const std::strin return path; } -void PrintBase::status_update_warnings(ObjectID object_id, int step, PrintStateBase::WarningLevel /* warning_level */, const std::string &message) +void PrintBase::status_update_warnings(int step, PrintStateBase::WarningLevel /* warning_level */, const std::string &message, const PrintObjectBase* print_object) { - if (this->m_status_callback) - m_status_callback(SlicingStatus(*this, step)); + if (this->m_status_callback) { + auto status = print_object ? SlicingStatus(*print_object, step) : SlicingStatus(*this, step); + m_status_callback(status); + } else if (! message.empty()) - printf("%s warning: %s\n", (object_id == this->id()) ? "print" : "print object", message.c_str()); + printf("%s warning: %s\n", print_object ? "print_object" : "print", message.c_str()); } tbb::mutex& PrintObjectBase::state_mutex(PrintBase *print) @@ -114,7 +116,7 @@ std::function PrintObjectBase::cancel_callback(PrintBase *print) void PrintObjectBase::status_update_warnings(PrintBase *print, int step, PrintStateBase::WarningLevel warning_level, const std::string &message) { - print->status_update_warnings(this->id(), step, warning_level, message); + print->status_update_warnings(step, warning_level, message, this); } } // namespace Slic3r diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index 8fca43319a..e0aa56ba5a 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -366,7 +366,7 @@ public: virtual std::vector print_object_ids() const = 0; // Validate the print, return empty string if valid, return error if process() cannot (or should not) be started. - virtual std::string validate() const { return std::string(); } + virtual std::string validate(std::string* warning = nullptr) const { return std::string(); } enum ApplyStatus { // No change after the Print::apply() call. @@ -481,7 +481,7 @@ protected: // Notify UI about a new warning of a milestone "step" on this PrintBase. // The UI will be notified by calling a status callback. // If no status callback is registered, the message is printed to console. - void status_update_warnings(ObjectID object_id, int step, PrintStateBase::WarningLevel warning_level, const std::string &message); + void status_update_warnings(int step, PrintStateBase::WarningLevel warning_level, const std::string &message, const PrintObjectBase* print_object = nullptr); // If the background processing stop was requested, throw CanceledException. // To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly. @@ -528,7 +528,7 @@ protected: PrintStateBase::TimeStamp set_done(PrintStepEnum step) { std::pair status = m_state.set_done(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); if (status.second) - this->status_update_warnings(this->id(), static_cast(step), PrintStateBase::WarningLevel::NON_CRITICAL, std::string()); + this->status_update_warnings(static_cast(step), PrintStateBase::WarningLevel::NON_CRITICAL, std::string()); return status.first; } bool invalidate_step(PrintStepEnum step) @@ -550,7 +550,7 @@ protected: std::pair active_step = m_state.active_step_add_warning(warning_level, message, message_id, this->state_mutex()); if (active_step.second) // Update UI. - this->status_update_warnings(this->id(), static_cast(active_step.first), warning_level, message); + this->status_update_warnings(static_cast(active_step.first), warning_level, message); } private: diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 480ce45901..5516b298d3 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1,4 +1,5 @@ #include "PrintConfig.hpp" +#include "Config.hpp" #include "I18N.hpp" #include @@ -66,7 +67,7 @@ void PrintConfigDef::init_common_params() def->label = L("G-code thumbnails"); def->tooltip = L("Picture sizes to be stored into a .gcode and .sl1 files, in the following format: \"XxY, XxY, ...\""); def->mode = comExpert; - def->gui_type = "one_string"; + def->gui_type = ConfigOptionDef::GUIType::one_string; def->set_default_value(new ConfigOptionPoints()); def = this->add("layer_height", coFloat); @@ -116,7 +117,7 @@ void PrintConfigDef::init_common_params() def = this->add("printhost_port", coString); def->label = L("Printer"); def->tooltip = L("Name of the printer"); - def->gui_type = "select_open"; + def->gui_type = ConfigOptionDef::GUIType::select_open; def->mode = comAdvanced; def->set_default_value(new ConfigOptionString("")); @@ -151,11 +152,11 @@ void PrintConfigDef::init_common_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionString("")); - def = this->add("preset_name", coString); - def->label = L("Printer preset name"); - def->tooltip = L("Related printer preset name"); + def = this->add("preset_names", coStrings); + def->label = L("Printer preset names"); + def->tooltip = L("Names of presets related to the physical printer"); def->mode = comAdvanced; - def->set_default_value(new ConfigOptionString("")); + def->set_default_value(new ConfigOptionStrings()); def = this->add("printhost_authorization_type", coEnum); def->label = L("Authorization Type"); @@ -568,7 +569,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionBool(true)); def = this->add("extruder", coInt); - def->gui_type = "i_enum_open"; + def->gui_type = ConfigOptionDef::GUIType::i_enum_open; def->label = L("Extruder"); def->category = L("Extruders"); def->tooltip = L("The extruder to use (unless more specific extruder settings are specified). " @@ -606,7 +607,7 @@ void PrintConfigDef::init_fff_params() def = this->add("extruder_colour", coStrings); def->label = L("Extruder Color"); def->tooltip = L("This is only used in the Slic3r interface as a visual help."); - def->gui_type = "color"; + def->gui_type = ConfigOptionDef::GUIType::color; // Empty string means no color assigned yet. def->set_default_value(new ConfigOptionStrings { "" }); @@ -668,7 +669,7 @@ void PrintConfigDef::init_fff_params() def = this->add("filament_colour", coStrings); def->label = L("Color"); def->tooltip = L("This is only used in the Slic3r interface as a visual help."); - def->gui_type = "color"; + def->gui_type = ConfigOptionDef::GUIType::color; def->set_default_value(new ConfigOptionStrings { "#29B2B2" }); def = this->add("filament_notes", coStrings); @@ -812,7 +813,7 @@ void PrintConfigDef::init_fff_params() def = this->add("filament_type", coStrings); def->label = L("Filament type"); def->tooltip = L("The filament material type for use in custom G-codes."); - def->gui_type = "f_enum_open"; + def->gui_type = ConfigOptionDef::GUIType::f_enum_open; def->gui_flags = "show_value"; def->enum_values.push_back("PLA"); def->enum_values.push_back("PET"); @@ -881,7 +882,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionFloat(45)); def = this->add("fill_density", coPercent); - def->gui_type = "f_enum_open"; + def->gui_type = ConfigOptionDef::GUIType::f_enum_open; def->gui_flags = "show_value"; def->label = L("Fill density"); def->category = L("Infill"); @@ -1102,6 +1103,7 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("teacup"); def->enum_values.push_back("makerware"); def->enum_values.push_back("marlin"); + def->enum_values.push_back("marlinfirmware"); def->enum_values.push_back("sailfish"); def->enum_values.push_back("mach3"); def->enum_values.push_back("machinekit"); @@ -1112,7 +1114,8 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back("Repetier"); def->enum_labels.push_back("Teacup"); def->enum_labels.push_back("MakerWare (MakerBot)"); - def->enum_labels.push_back("Marlin"); + def->enum_labels.push_back("Marlin (legacy)"); + def->enum_labels.push_back("Marlin Firmware"); def->enum_labels.push_back("Sailfish (MakerBot)"); def->enum_labels.push_back("Mach3/LinuxCNC"); def->enum_labels.push_back("Machinekit"); @@ -1168,7 +1171,7 @@ void PrintConfigDef::init_fff_params() "Set this parameter to zero to disable anchoring perimeters connected to a single infill line."); def->sidetext = L("mm or %"); def->ratio_over = "infill_extrusion_width"; - def->gui_type = "f_enum_open"; + def->gui_type = ConfigOptionDef::GUIType::f_enum_open; def->enum_values.push_back("0"); def->enum_values.push_back("1"); def->enum_values.push_back("2"); @@ -1466,21 +1469,34 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloats{ 0., 0. }); - // M204 S... [mm/sec^2] + // M204 P... [mm/sec^2] def = this->add("machine_max_acceleration_extruding", coFloats); def->full_label = L("Maximum acceleration when extruding"); def->category = L("Machine limits"); - def->tooltip = L("Maximum acceleration when extruding (M204 S)"); + def->tooltip = L("Maximum acceleration when extruding (M204 P)\n\n" + "Marlin (legacy) firmware flavor will use this also " + "as travel acceleration (M204 T)."); + def->sidetext = L("mm/s²"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats{ 1500., 1250. }); + + + // M204 R... [mm/sec^2] + def = this->add("machine_max_acceleration_retracting", coFloats); + def->full_label = L("Maximum acceleration when retracting"); + def->category = L("Machine limits"); + def->tooltip = L("Maximum acceleration when retracting (M204 R)"); def->sidetext = L("mm/s²"); def->min = 0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloats{ 1500., 1250. }); // M204 T... [mm/sec^2] - def = this->add("machine_max_acceleration_retracting", coFloats); - def->full_label = L("Maximum acceleration when retracting"); + def = this->add("machine_max_acceleration_travel", coFloats); + def->full_label = L("Maximum acceleration for travel moves"); def->category = L("Machine limits"); - def->tooltip = L("Maximum acceleration when retracting (M204 T)"); + def->tooltip = L("Maximum acceleration for travel moves (M204 T)"); def->sidetext = L("mm/s²"); def->min = 0; def->mode = comAdvanced; @@ -1798,8 +1814,8 @@ void PrintConfigDef::init_fff_params() def->category = L("Support material"); def->tooltip = L("Density of the first raft or support layer."); def->sidetext = L("%"); - def->min = 0; - def->max = 150; + def->min = 10; + def->max = 100; def->mode = comExpert; def->set_default_value(new ConfigOptionPercent(90)); @@ -1950,7 +1966,7 @@ void PrintConfigDef::init_fff_params() #if 0 def = this->add("seam_preferred_direction", coFloat); -// def->gui_type = "slider"; +// def->gui_type = ConfigOptionDef::GUIType::slider; def->label = L("Direction"); def->sidetext = L("°"); def->full_label = L("Preferred direction of the seam"); @@ -1960,7 +1976,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionFloat(0)); def = this->add("seam_preferred_direction_jitter", coFloat); -// def->gui_type = "slider"; +// def->gui_type = ConfigOptionDef::GUIType::slider; def->label = L("Jitter"); def->sidetext = L("°"); def->full_label = L("Seam preferred direction jitter"); @@ -2234,8 +2250,8 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionBool(false)); def = this->add("support_material_contact_distance", coFloat); - def->gui_type = "f_enum_open"; - def->label = L("Contact Z distance"); + def->gui_type = ConfigOptionDef::GUIType::f_enum_open; + def->label = L("Top contact Z distance"); def->category = L("Support material"); def->tooltip = L("The vertical distance between object and support material interface. " "Setting this to 0 will also prevent Slic3r from using bridge flow and speed " @@ -2243,12 +2259,31 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); // def->min = 0; def->enum_values.push_back("0"); + def->enum_values.push_back("0.1"); def->enum_values.push_back("0.2"); def->enum_labels.push_back(L("0 (soluble)")); + def->enum_labels.push_back(L("0.1 (detachable)")); def->enum_labels.push_back(L("0.2 (detachable)")); def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.2)); + def = this->add("support_material_bottom_contact_distance", coFloat); + def->gui_type = ConfigOptionDef::GUIType::f_enum_open; + def->label = L("Bottom contact Z distance"); + def->category = L("Support material"); + def->tooltip = L("The vertical distance between the object top surface and the support material interface. " + "If set to zero, support_material_contact_distance will be used for both top and bottom contact Z distances."); + def->sidetext = L("mm"); +// def->min = 0; + def->enum_values.push_back("0"); + def->enum_values.push_back("0.1"); + def->enum_values.push_back("0.2"); + def->enum_labels.push_back(L("same as top")); + def->enum_labels.push_back(L("0.1")); + def->enum_labels.push_back(L("0.2")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0)); + def = this->add("support_material_enforce_layers", coInt); def->label = L("Enforce support for the first"); def->category = L("Support material"); @@ -2298,15 +2333,49 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionInt(1)); - def = this->add("support_material_interface_layers", coInt); - def->label = L("Interface layers"); + auto support_material_interface_layers = def = this->add("support_material_interface_layers", coInt); + def->gui_type = ConfigOptionDef::GUIType::i_enum_open; + def->label = L("Top interface layers"); def->category = L("Support material"); def->tooltip = L("Number of interface layers to insert between the object(s) and support material."); def->sidetext = L("layers"); def->min = 0; + def->enum_values.push_back("0"); + def->enum_values.push_back("1"); + def->enum_values.push_back("2"); + def->enum_values.push_back("3"); + def->enum_labels.push_back(L("0 (off)")); + def->enum_labels.push_back(L("1 (light)")); + def->enum_labels.push_back(L("2 (default)")); + def->enum_labels.push_back(L("3 (heavy)")); def->mode = comAdvanced; def->set_default_value(new ConfigOptionInt(3)); + def = this->add("support_material_bottom_interface_layers", coInt); + def->gui_type = ConfigOptionDef::GUIType::i_enum_open; + def->label = L("Bottom interface layers"); + def->category = L("Support material"); + def->tooltip = L("Number of interface layers to insert between the object(s) and support material. " + "Set to -1 to use support_material_interface_layers"); + def->sidetext = L("layers"); + def->min = -1; + def->enum_values.push_back("-1"); + append(def->enum_values, support_material_interface_layers->enum_values); + def->enum_labels.push_back(L("same as top")); + append(def->enum_labels, support_material_interface_layers->enum_labels); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(-1)); + + def = this->add("support_material_closing_radius", coFloat); + def->label = L("Closing radius"); + def->category = L("Support material"); + def->tooltip = L("For snug supports, the support regions will be merged using morphological closing operation." + " Gaps smaller than the closing radius will be filled in."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(2)); + def = this->add("support_material_interface_spacing", coFloat); def->label = L("Interface pattern spacing"); def->category = L("Support material"); @@ -2375,6 +2444,20 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(60)); + def = this->add("support_material_style", coEnum); + def->label = L("Style"); + def->category = L("Support material"); + def->tooltip = L("Style and shape of the support towers. Projecting the supports into a regular grid " + "will create more stable supports, while snug support towers will save material and reduce " + "object scarring."); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("grid"); + def->enum_values.push_back("snug"); + def->enum_labels.push_back(L("Grid")); + def->enum_labels.push_back(L("Snug")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(smsGrid)); + def = this->add("support_material_synchronize_layers", coBool); def->label = L("Synchronize with object layers"); def->category = L("Support material"); @@ -2415,6 +2498,13 @@ void PrintConfigDef::init_fff_params() def->max = max_temp; def->set_default_value(new ConfigOptionInts { 200 }); + def = this->add("thick_bridges", coBool); + def->label = L("Thick bridges"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("Print bridges with round extrusions."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + def = this->add("thin_walls", coBool); def->label = L("Detect thin walls"); def->category = L("Layers and Perimeters"); @@ -2438,7 +2528,7 @@ void PrintConfigDef::init_fff_params() def = this->add("toolchange_gcode", coString); def->label = L("Tool change G-code"); def->tooltip = L("This custom code is inserted before every toolchange. Placeholder variables for all PrusaSlicer settings " - "as well as {previous_extruder} and {next_extruder} can be used. When a tool-changing command " + "as well as {toolchange_z}, {previous_extruder} and {next_extruder} can be used. When a tool-changing command " "which changes to the correct extruder is included (such as T{next_extruder}), PrusaSlicer will emit no other such command. " "It is therefore possible to script custom behaviour both before and after the toolchange."); def->multiline = true; @@ -2594,10 +2684,18 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.)); + def = this->add("wipe_tower_brim_width", coFloat); + def->label = L("Wipe tower brim width"); + def->tooltip = L("Wipe tower brim width"); + def->sidetext = L("mm"); + def->mode = comAdvanced; + def->min = 0.f; + def->set_default_value(new ConfigOptionFloat(2.)); + def = this->add("wipe_into_infill", coBool); 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. " + def->tooltip = L("Purging after toolchange will be done inside this object's infills. " "This lowers the amount of waste but may result in longer print time " " due to additional travel moves."); def->set_default_value(new ConfigOptionBool(false)); @@ -2815,7 +2913,7 @@ void PrintConfigDef::init_sla_params() def = this->add("material_type", coString); def->label = L("SLA material type"); def->tooltip = L("SLA material type"); - def->gui_type = "f_enum_open"; // TODO: ??? + def->gui_type = ConfigOptionDef::GUIType::f_enum_open; // TODO: ??? def->gui_flags = "show_value"; def->enum_values.push_back("Tough"); def->enum_values.push_back("Flexible"); @@ -3366,6 +3464,8 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va opt_key = "printhost_cafile"; } else if (opt_key == "octoprint_apikey") { opt_key = "printhost_apikey"; + } else if (opt_key == "preset_name") { + opt_key = "preset_names"; } // Ignore the following obsolete configuration keys: @@ -3555,7 +3655,8 @@ std::string FullPrintConfig::validate() this->gcode_flavor.value != gcfSmoothie && this->gcode_flavor.value != gcfRepRapSprinter && this->gcode_flavor.value != gcfRepRapFirmware && - this->gcode_flavor.value != gcfMarlin && + this->gcode_flavor.value != gcfMarlinLegacy && + this->gcode_flavor.value != gcfMarlinFirmware && this->gcode_flavor.value != gcfMachinekit && this->gcode_flavor.value != gcfRepetier) return "--use-firmware-retraction is only supported by Marlin, Smoothie, RepRapFirmware, Repetier and Machinekit firmware"; diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index c1e3f82cbe..aab5096624 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -24,7 +24,7 @@ namespace Slic3r { enum GCodeFlavor : unsigned char { - gcfRepRapSprinter, gcfRepRapFirmware, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlin, gcfSailfish, gcfMach3, gcfMachinekit, + gcfRepRapSprinter, gcfRepRapFirmware, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlinLegacy, gcfMarlinFirmware, gcfSailfish, gcfMach3, gcfMachinekit, gcfSmoothie, gcfNoExtrusion, }; @@ -51,7 +51,7 @@ enum class FuzzySkinType { enum InfillPattern : int { ipRectilinear, ipMonotonic, ipAlignedRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, - ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipCount, + ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipSupportBase, ipCount, }; enum class IroningType { @@ -65,6 +65,10 @@ enum SupportMaterialPattern { smpRectilinear, smpRectilinearGrid, smpHoneycomb, }; +enum SupportMaterialStyle { + smsGrid, smsSnug, +}; + enum SupportMaterialInterfacePattern { smipAuto, smipRectilinear, smipConcentric, }; @@ -116,7 +120,8 @@ template<> inline const t_config_enum_values& ConfigOptionEnum::get keys_map["repetier"] = gcfRepetier; keys_map["teacup"] = gcfTeacup; keys_map["makerware"] = gcfMakerWare; - keys_map["marlin"] = gcfMarlin; + keys_map["marlin"] = gcfMarlinLegacy; + keys_map["marlinfirmware"] = gcfMarlinFirmware; keys_map["sailfish"] = gcfSailfish; keys_map["smoothie"] = gcfSmoothie; keys_map["mach3"] = gcfMach3; @@ -211,6 +216,15 @@ template<> inline const t_config_enum_values& ConfigOptionEnum inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { + static t_config_enum_values keys_map; + if (keys_map.empty()) { + keys_map["grid"] = smsGrid; + keys_map["snug"] = smsSnug; + } + return keys_map; +} + template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { @@ -503,25 +517,31 @@ public: ConfigOptionFloat support_material_angle; ConfigOptionBool support_material_buildplate_only; ConfigOptionFloat support_material_contact_distance; + ConfigOptionFloat support_material_bottom_contact_distance; ConfigOptionInt support_material_enforce_layers; ConfigOptionInt support_material_extruder; ConfigOptionFloatOrPercent support_material_extrusion_width; ConfigOptionBool support_material_interface_contact_loops; ConfigOptionInt support_material_interface_extruder; ConfigOptionInt support_material_interface_layers; + ConfigOptionInt support_material_bottom_interface_layers; // Spacing between interface lines (the hatching distance). Set zero to get a solid interface. ConfigOptionFloat support_material_interface_spacing; ConfigOptionFloatOrPercent support_material_interface_speed; ConfigOptionEnum support_material_pattern; ConfigOptionEnum support_material_interface_pattern; + // Morphological closing of support areas. Only used for "sung" supports. + ConfigOptionFloat support_material_closing_radius; // Spacing between support material lines (the hatching distance). ConfigOptionFloat support_material_spacing; ConfigOptionFloat support_material_speed; + ConfigOptionEnum support_material_style; ConfigOptionBool support_material_synchronize_layers; // Overhang angle threshold. ConfigOptionInt support_material_threshold; ConfigOptionBool support_material_with_sheath; ConfigOptionFloatOrPercent support_material_xy_spacing; + ConfigOptionBool thick_bridges; ConfigOptionFloat xy_size_compensation; ConfigOptionBool wipe_into_objects; @@ -553,22 +573,27 @@ protected: OPT_PTR(support_material_angle); OPT_PTR(support_material_buildplate_only); OPT_PTR(support_material_contact_distance); + OPT_PTR(support_material_bottom_contact_distance); OPT_PTR(support_material_enforce_layers); OPT_PTR(support_material_interface_contact_loops); OPT_PTR(support_material_extruder); OPT_PTR(support_material_extrusion_width); OPT_PTR(support_material_interface_extruder); OPT_PTR(support_material_interface_layers); + OPT_PTR(support_material_bottom_interface_layers); + OPT_PTR(support_material_closing_radius); OPT_PTR(support_material_interface_spacing); OPT_PTR(support_material_interface_speed); OPT_PTR(support_material_pattern); OPT_PTR(support_material_interface_pattern); OPT_PTR(support_material_spacing); OPT_PTR(support_material_speed); + OPT_PTR(support_material_style); OPT_PTR(support_material_synchronize_layers); OPT_PTR(support_material_xy_spacing); OPT_PTR(support_material_threshold); OPT_PTR(support_material_with_sheath); + OPT_PTR(thick_bridges); OPT_PTR(xy_size_compensation); OPT_PTR(wipe_into_objects); } @@ -704,10 +729,12 @@ public: ConfigOptionFloats machine_max_feedrate_y; ConfigOptionFloats machine_max_feedrate_z; ConfigOptionFloats machine_max_feedrate_e; - // M204 S... [mm/sec^2] + + // M204 P... R... T...[mm/sec^2] ConfigOptionFloats machine_max_acceleration_extruding; - // M204 T... [mm/sec^2] ConfigOptionFloats machine_max_acceleration_retracting; + ConfigOptionFloats machine_max_acceleration_travel; + // M205 X... Y... Z... E... [mm/sec] ConfigOptionFloats machine_max_jerk_x; ConfigOptionFloats machine_max_jerk_y; @@ -732,6 +759,7 @@ protected: OPT_PTR(machine_max_feedrate_e); OPT_PTR(machine_max_acceleration_extruding); OPT_PTR(machine_max_acceleration_retracting); + OPT_PTR(machine_max_acceleration_travel); OPT_PTR(machine_max_jerk_x); OPT_PTR(machine_max_jerk_y); OPT_PTR(machine_max_jerk_z); @@ -962,6 +990,7 @@ public: ConfigOptionFloat wipe_tower_width; ConfigOptionFloat wipe_tower_per_color_wipe; ConfigOptionFloat wipe_tower_rotation_angle; + ConfigOptionFloat wipe_tower_brim_width; ConfigOptionFloat wipe_tower_bridging; ConfigOptionFloats wiping_volumes_matrix; ConfigOptionFloats wiping_volumes_extruders; @@ -1036,6 +1065,7 @@ protected: OPT_PTR(wipe_tower_width); OPT_PTR(wipe_tower_per_color_wipe); OPT_PTR(wipe_tower_rotation_angle); + OPT_PTR(wipe_tower_brim_width); OPT_PTR(wipe_tower_bridging); OPT_PTR(wiping_volumes_matrix); OPT_PTR(wiping_volumes_extruders); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index c963418c30..cbf3e71ab7 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -430,6 +430,24 @@ void PrintObject::generate_support_material() if (layer->empty()) throw Slic3r::SlicingError("Levitating objects cannot be printed without supports."); #endif + + // Do we have custom support data that would not be used? + // Notify the user in that case. + if (! this->has_support()) { + for (const ModelVolume* mv : this->model_object()->volumes) { + bool has_enforcers = mv->is_support_enforcer() + || (mv->is_model_part() + && ! mv->supported_facets.empty() + && ! mv->supported_facets.get_facets(*mv, EnforcerBlockerType::ENFORCER).indices.empty()); + if (has_enforcers) { + this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, + L("An object has custom support enforcers which will not be used " + "because supports are off. Consider turning them on.") + "\n" + + (L("Object name")) + ": " + this->model_object()->name); + break; + } + } + } } this->set_done(posSupportMaterial); } @@ -528,14 +546,9 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "extra_perimeters" || opt_key == "gap_fill_enabled" || opt_key == "gap_fill_speed" - || opt_key == "overhangs" || opt_key == "first_layer_extrusion_width" - || opt_key == "fuzzy_skin" - || opt_key == "fuzzy_skin_thickness" - || opt_key == "fuzzy_skin_point_dist" || opt_key == "perimeter_extrusion_width" || opt_key == "infill_overlap" - || opt_key == "thin_walls" || opt_key == "external_perimeters_first") { steps.emplace_back(posPerimeters); } else if ( @@ -567,14 +580,18 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "support_material_enforce_layers" || opt_key == "support_material_extruder" || opt_key == "support_material_extrusion_width" + || opt_key == "support_material_bottom_contact_distance" || opt_key == "support_material_interface_layers" + || opt_key == "support_material_bottom_interface_layers" || opt_key == "support_material_interface_pattern" || opt_key == "support_material_interface_contact_loops" || opt_key == "support_material_interface_extruder" || opt_key == "support_material_interface_spacing" || opt_key == "support_material_pattern" + || opt_key == "support_material_style" || opt_key == "support_material_xy_spacing" || opt_key == "support_material_spacing" + || opt_key == "support_material_closing_radius" || opt_key == "support_material_synchronize_layers" || opt_key == "support_material_threshold" || opt_key == "support_material_with_sheath" @@ -634,7 +651,13 @@ bool PrintObject::invalidate_state_by_config_options( steps.emplace_back(posPrepareInfill); } else if ( opt_key == "external_perimeter_extrusion_width" - || opt_key == "perimeter_extruder") { + || opt_key == "perimeter_extruder" + || opt_key == "fuzzy_skin" + || opt_key == "fuzzy_skin_thickness" + || opt_key == "fuzzy_skin_point_dist" + || opt_key == "overhangs" + || opt_key == "thin_walls" + || opt_key == "thick_bridges") { steps.emplace_back(posPerimeters); steps.emplace_back(posSupportMaterial); } else if (opt_key == "bridge_flow_ratio") { @@ -1438,26 +1461,18 @@ void PrintObject::bridge_over_infill() const PrintRegion ®ion = *m_print->regions()[region_id]; // skip bridging in case there are no voids - if (region.config().fill_density.value == 100) continue; - - // get bridge flow - Flow bridge_flow = region.flow( - frSolidInfill, - -1, // layer height, not relevant for bridge flow - true, // bridge - false, // first layer - -1, // custom width, not relevant for bridge flow - *this - ); - + if (region.config().fill_density.value == 100) + continue; + for (LayerPtrs::iterator layer_it = m_layers.begin(); layer_it != m_layers.end(); ++ layer_it) { // skip first layer if (layer_it == m_layers.begin()) continue; - Layer* layer = *layer_it; - LayerRegion* layerm = layer->m_regions[region_id]; - + Layer *layer = *layer_it; + LayerRegion *layerm = layer->m_regions[region_id]; + Flow bridge_flow = layerm->bridging_flow(frSolidInfill); + // extract the stInternalSolid surfaces that might be transformed into bridges Polygons internal_solid; layerm->fill_surfaces.filter_by_type(stInternalSolid, &internal_solid); @@ -1470,7 +1485,7 @@ void PrintObject::bridge_over_infill() Polygons to_bridge_pp = internal_solid; // iterate through lower layers spanned by bridge_flow - double bottom_z = layer->print_z - bridge_flow.height; + double bottom_z = layer->print_z - bridge_flow.height(); for (int i = int(layer_it - m_layers.begin()) - 1; i >= 0; --i) { const Layer* lower_layer = m_layers[i]; diff --git a/src/libslic3r/PrintRegion.cpp b/src/libslic3r/PrintRegion.cpp index 79eb647f66..837200984a 100644 --- a/src/libslic3r/PrintRegion.cpp +++ b/src/libslic3r/PrintRegion.cpp @@ -18,31 +18,25 @@ unsigned int PrintRegion::extruder(FlowRole role) const return extruder; } -Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const +Flow PrintRegion::flow(const PrintObject &object, FlowRole role, double layer_height, bool first_layer) const { ConfigOptionFloatOrPercent config_width; - if (width != -1) { - // use the supplied custom width, if any - config_width.value = width; - config_width.percent = false; + // Get extrusion width from configuration. + // (might be an absolute value, or a percent value, or zero for auto) + if (first_layer && m_print->config().first_layer_extrusion_width.value > 0) { + config_width = m_print->config().first_layer_extrusion_width; + } else if (role == frExternalPerimeter) { + config_width = m_config.external_perimeter_extrusion_width; + } else if (role == frPerimeter) { + config_width = m_config.perimeter_extrusion_width; + } else if (role == frInfill) { + config_width = m_config.infill_extrusion_width; + } else if (role == frSolidInfill) { + config_width = m_config.solid_infill_extrusion_width; + } else if (role == frTopSolidInfill) { + config_width = m_config.top_infill_extrusion_width; } else { - // otherwise, get extrusion width from configuration - // (might be an absolute value, or a percent value, or zero for auto) - if (first_layer && m_print->config().first_layer_extrusion_width.value > 0) { - config_width = m_print->config().first_layer_extrusion_width; - } else if (role == frExternalPerimeter) { - config_width = m_config.external_perimeter_extrusion_width; - } else if (role == frPerimeter) { - config_width = m_config.perimeter_extrusion_width; - } else if (role == frInfill) { - config_width = m_config.infill_extrusion_width; - } else if (role == frSolidInfill) { - config_width = m_config.solid_infill_extrusion_width; - } else if (role == frTopSolidInfill) { - config_width = m_config.top_infill_extrusion_width; - } else { - throw Slic3r::InvalidArgument("Unknown role"); - } + throw Slic3r::InvalidArgument("Unknown role"); } if (config_width.value == 0) @@ -50,8 +44,8 @@ Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool fir // Get the configured nozzle_diameter for the extruder associated to the flow role requested. // Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right. - double nozzle_diameter = m_print->config().nozzle_diameter.get_at(this->extruder(role) - 1); - return Flow::new_from_config_width(role, config_width, (float)nozzle_diameter, (float)layer_height, bridge ? (float)m_config.bridge_flow_ratio : 0.0f); + auto nozzle_diameter = float(m_print->config().nozzle_diameter.get_at(this->extruder(role) - 1)); + return Flow::new_from_config_width(role, config_width, nozzle_diameter, float(layer_height)); } coordf_t PrintRegion::nozzle_dmr_avg(const PrintConfig &print_config) const diff --git a/src/libslic3r/SLA/Concurrency.hpp b/src/libslic3r/SLA/Concurrency.hpp index b692914aca..7299101b31 100644 --- a/src/libslic3r/SLA/Concurrency.hpp +++ b/src/libslic3r/SLA/Concurrency.hpp @@ -1,15 +1,10 @@ #ifndef SLA_CONCURRENCY_H #define SLA_CONCURRENCY_H -#include -#include -#include -#include +// FIXME: Deprecated -#include -#include - -#include +#include +#include namespace Slic3r { namespace sla { @@ -22,116 +17,47 @@ template struct _ccr {}; template<> struct _ccr { - using SpinningMutex = tbb::spin_mutex; - using BlockingMutex = tbb::mutex; - - template - static IteratorOnly loop_(const tbb::blocked_range &range, Fn &&fn) - { - for (auto &el : range) fn(el); - } - - template - static IntegerOnly loop_(const tbb::blocked_range &range, Fn &&fn) - { - for (I i = range.begin(); i < range.end(); ++i) fn(i); - } + using SpinningMutex = execution::SpinningMutex; + using BlockingMutex = execution::BlockingMutex; template static void for_each(It from, It to, Fn &&fn, size_t granularity = 1) { - tbb::parallel_for(tbb::blocked_range{from, to, granularity}, - [&fn](const auto &range) { - loop_(range, std::forward(fn)); - }); + execution::for_each(ex_tbb, from, to, std::forward(fn), granularity); } - template - static T reduce(I from, - I to, - const T &init, - MergeFn &&mergefn, - AccessFn &&access, - size_t granularity = 1 - ) + template + static auto reduce(Args&&...args) { - return tbb::parallel_reduce( - tbb::blocked_range{from, to, granularity}, init, - [&](const auto &range, T subinit) { - T acc = subinit; - loop_(range, [&](auto &i) { acc = mergefn(acc, access(i)); }); - return acc; - }, - std::forward(mergefn)); + return execution::reduce(ex_tbb, std::forward(args)...); } - template - static IteratorOnly reduce(I from, - I to, - const T & init, - MergeFn &&mergefn, - size_t granularity = 1) + static size_t max_concurreny() { - return reduce( - from, to, init, std::forward(mergefn), - [](typename I::value_type &i) { return i; }, granularity); + return execution::max_concurrency(ex_tbb); } }; template<> struct _ccr { -private: - struct _Mtx { inline void lock() {} inline void unlock() {} }; - -public: - using SpinningMutex = _Mtx; - using BlockingMutex = _Mtx; - - template - static IteratorOnly loop_(It from, It to, Fn &&fn) - { - for (auto it = from; it != to; ++it) fn(*it); - } - - template - static IntegerOnly loop_(I from, I to, Fn &&fn) - { - for (I i = from; i < to; ++i) fn(i); - } + using SpinningMutex = execution::SpinningMutex; + using BlockingMutex = execution::BlockingMutex; template - static void for_each(It from, - It to, - Fn &&fn, - size_t /* ignore granularity */ = 1) + static void for_each(It from, It to, Fn &&fn, size_t granularity = 1) { - loop_(from, to, std::forward(fn)); + execution::for_each(ex_seq, from, to, std::forward(fn), granularity); } - template - static T reduce(I from, - I to, - const T & init, - MergeFn &&mergefn, - AccessFn &&access, - size_t /*granularity*/ = 1 - ) + template + static auto reduce(Args&&...args) { - T acc = init; - loop_(from, to, [&](auto &i) { acc = mergefn(acc, access(i)); }); - return acc; + return execution::reduce(ex_seq, std::forward(args)...); } - template - static IteratorOnly reduce(I from, - I to, - const T &init, - MergeFn &&mergefn, - size_t /*granularity*/ = 1 - ) + static size_t max_concurreny() { - return reduce(from, to, init, std::forward(mergefn), - [](typename I::value_type &i) { return i; }); + return execution::max_concurrency(ex_seq); } }; diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index 6df752fd36..b387845215 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -26,64 +26,99 @@ inline void _scale(S s, TriangleMesh &m) { m.scale(float(s)); } template> inline void _scale(S s, Contour3D &m) { for (auto &p : m.points) p *= s; } -static TriangleMesh _generate_interior(const TriangleMesh &mesh, - const JobController &ctl, - double min_thickness, - double voxel_scale, - double closing_dist) +struct Interior { + TriangleMesh mesh; + openvdb::FloatGrid::Ptr gridptr; + mutable std::optional accessor; + + double closing_distance = 0.; + double thickness = 0.; + double voxel_scale = 1.; + double nb_in = 3.; // narrow band width inwards + double nb_out = 3.; // narrow band width outwards + // Full narrow band is the sum of the two above values. + + void reset_accessor() const // This resets the accessor and its cache + // Not a thread safe call! + { + if (gridptr) + accessor = gridptr->getConstAccessor(); + } +}; + +void InteriorDeleter::operator()(Interior *p) +{ + delete p; +} + +TriangleMesh &get_mesh(Interior &interior) +{ + return interior.mesh; +} + +const TriangleMesh &get_mesh(const Interior &interior) +{ + return interior.mesh; +} + +static InteriorPtr generate_interior_verbose(const TriangleMesh & mesh, + const JobController &ctl, + double min_thickness, + double voxel_scale, + double closing_dist) { - TriangleMesh imesh{mesh}; - - _scale(voxel_scale, imesh); - double offset = voxel_scale * min_thickness; double D = voxel_scale * closing_dist; float out_range = 0.1f * float(offset); float in_range = 1.1f * float(offset + D); - + if (ctl.stopcondition()) return {}; else ctl.statuscb(0, L("Hollowing")); - - auto gridptr = mesh_to_grid(imesh, {}, out_range, in_range); - + + auto gridptr = mesh_to_grid(mesh, {}, voxel_scale, out_range, in_range); + assert(gridptr); - + if (!gridptr) { BOOST_LOG_TRIVIAL(error) << "Returned OpenVDB grid is NULL"; return {}; } - + if (ctl.stopcondition()) return {}; else ctl.statuscb(30, L("Hollowing")); - - if (closing_dist > .0) { - gridptr = redistance_grid(*gridptr, -(offset + D), double(in_range)); - } else { - D = -offset; - } - + + double iso_surface = D; + auto narrowb = double(in_range); + gridptr = redistance_grid(*gridptr, -(offset + D), narrowb, narrowb); + if (ctl.stopcondition()) return {}; else ctl.statuscb(70, L("Hollowing")); - - double iso_surface = D; + double adaptivity = 0.; - auto omesh = grid_to_mesh(*gridptr, iso_surface, adaptivity); - - _scale(1. / voxel_scale, omesh); - + InteriorPtr interior = InteriorPtr{new Interior{}}; + + interior->mesh = grid_to_mesh(*gridptr, iso_surface, adaptivity); + interior->gridptr = gridptr; + if (ctl.stopcondition()) return {}; else ctl.statuscb(100, L("Hollowing")); - - return omesh; + + interior->closing_distance = D; + interior->thickness = offset; + interior->voxel_scale = voxel_scale; + interior->nb_in = narrowb; + interior->nb_out = narrowb; + + return interior; } -std::unique_ptr generate_interior(const TriangleMesh & mesh, - const HollowingConfig &hc, - const JobController & ctl) +InteriorPtr generate_interior(const TriangleMesh & mesh, + const HollowingConfig &hc, + const JobController & ctl) { static const double MIN_OVERSAMPL = 3.; static const double MAX_OVERSAMPL = 8.; - + // I can't figure out how to increase the grid resolution through openvdb // API so the model will be scaled up before conversion and the result // scaled down. Voxels have a unit size. If I set voxelSize smaller, it @@ -92,26 +127,29 @@ std::unique_ptr generate_interior(const TriangleMesh & mesh, // // max 8x upscale, min is native voxel size auto voxel_scale = MIN_OVERSAMPL + (MAX_OVERSAMPL - MIN_OVERSAMPL) * hc.quality; - auto meshptr = std::make_unique( - _generate_interior(mesh, ctl, hc.min_thickness, voxel_scale, - hc.closing_distance)); - - if (meshptr && !meshptr->empty()) { - + + InteriorPtr interior = + generate_interior_verbose(mesh, ctl, hc.min_thickness, voxel_scale, + hc.closing_distance); + + if (interior && !interior->mesh.empty()) { + // This flips the normals to be outward facing... - meshptr->require_shared_vertices(); - indexed_triangle_set its = std::move(meshptr->its); - + interior->mesh.require_shared_vertices(); + indexed_triangle_set its = std::move(interior->mesh.its); + Slic3r::simplify_mesh(its); - + // flip normals back... for (stl_triangle_vertex_indices &ind : its.indices) std::swap(ind(0), ind(2)); - - *meshptr = Slic3r::TriangleMesh{its}; + + interior->mesh = Slic3r::TriangleMesh{its}; + interior->mesh.repaired = true; + interior->mesh.require_shared_vertices(); } - - return meshptr; + + return interior; } Contour3D DrainHole::to_mesh() const @@ -273,12 +311,264 @@ void cut_drainholes(std::vector & obj_slices, obj_slices[i] = diff_ex(obj_slices[i], hole_slices[i]); } -void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg) +void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg, int flags) { - std::unique_ptr inter_ptr = - Slic3r::sla::generate_interior(mesh); + InteriorPtr interior = generate_interior(mesh, cfg, JobController{}); + if (!interior) return; - if (inter_ptr) mesh.merge(*inter_ptr); + hollow_mesh(mesh, *interior, flags); +} + +void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags) +{ + if (mesh.empty() || interior.mesh.empty()) return; + + if (flags & hfRemoveInsideTriangles && interior.gridptr) + remove_inside_triangles(mesh, interior); + + mesh.merge(interior.mesh); + mesh.require_shared_vertices(); +} + +// Get the distance of p to the interior's zero iso_surface. Interior should +// have its zero isosurface positioned at offset + closing_distance inwards form +// the model surface. +static double get_distance_raw(const Vec3f &p, const Interior &interior) +{ + assert(interior.gridptr); + + if (!interior.accessor) interior.reset_accessor(); + + auto v = (p * interior.voxel_scale).cast(); + auto grididx = interior.gridptr->transform().worldToIndexCellCentered( + {v.x(), v.y(), v.z()}); + + return interior.accessor->getValue(grididx) ; +} + +struct TriangleBubble { Vec3f center; double R; }; + +// Return the distance of bubble center to the interior boundary or NaN if the +// triangle is too big to be measured. +static double get_distance(const TriangleBubble &b, const Interior &interior) +{ + double R = b.R * interior.voxel_scale; + double D = get_distance_raw(b.center, interior); + + return (D > 0. && R >= interior.nb_out) || + (D < 0. && R >= interior.nb_in) || + ((D - R) < 0. && 2 * R > interior.thickness) ? + std::nan("") : + // FIXME: Adding interior.voxel_scale is a compromise supposed + // to prevent the deletion of the triangles forming the interior + // itself. This has a side effect that a small portion of the + // bad triangles will still be visible. + D - interior.closing_distance /*+ 2 * interior.voxel_scale*/; +} + +double get_distance(const Vec3f &p, const Interior &interior) +{ + double d = get_distance_raw(p, interior) - interior.closing_distance; + return d / interior.voxel_scale; +} + +// A face that can be divided. Stores the indices into the original mesh if its +// part of that mesh and the vertices it consists of. +enum { NEW_FACE = -1}; +struct DivFace { + Vec3i indx; + std::array verts; + long faceid = NEW_FACE; + long parent = NEW_FACE; +}; + +// Divide a face recursively and call visitor on all the sub-faces. +template +void divide_triangle(const DivFace &face, Fn &&visitor) +{ + std::array edges = {(face.verts[0] - face.verts[1]), + (face.verts[1] - face.verts[2]), + (face.verts[2] - face.verts[0])}; + + std::array edgeidx = {0, 1, 2}; + + std::sort(edgeidx.begin(), edgeidx.end(), [&edges](size_t e1, size_t e2) { + return edges[e1].squaredNorm() > edges[e2].squaredNorm(); + }); + + DivFace child1, child2; + + child1.parent = face.faceid == NEW_FACE ? face.parent : face.faceid; + child1.indx(0) = -1; + child1.indx(1) = face.indx(edgeidx[1]); + child1.indx(2) = face.indx((edgeidx[1] + 1) % 3); + child1.verts[0] = (face.verts[edgeidx[0]] + face.verts[(edgeidx[0] + 1) % 3]) / 2.; + child1.verts[1] = face.verts[edgeidx[1]]; + child1.verts[2] = face.verts[(edgeidx[1] + 1) % 3]; + + if (visitor(child1)) + divide_triangle(child1, std::forward(visitor)); + + child2.parent = face.faceid == NEW_FACE ? face.parent : face.faceid; + child2.indx(0) = -1; + child2.indx(1) = face.indx(edgeidx[2]); + child2.indx(2) = face.indx((edgeidx[2] + 1) % 3); + child2.verts[0] = child1.verts[0]; + child2.verts[1] = face.verts[edgeidx[2]]; + child2.verts[2] = face.verts[(edgeidx[2] + 1) % 3]; + + if (visitor(child2)) + divide_triangle(child2, std::forward(visitor)); +} + +void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior, + const std::vector &exclude_mask) +{ + enum TrPos { posInside, posTouch, posOutside }; + + auto &faces = mesh.its.indices; + auto &vertices = mesh.its.vertices; + auto bb = mesh.bounding_box(); + + bool use_exclude_mask = faces.size() == exclude_mask.size(); + auto is_excluded = [&exclude_mask, use_exclude_mask](size_t face_id) { + return use_exclude_mask && exclude_mask[face_id]; + }; + + // TODO: Parallel mode not working yet + using exec_policy = ccr_seq; + + // Info about the needed modifications on the input mesh. + struct MeshMods { + + // Just a thread safe wrapper for a vector of triangles. + struct { + std::vector> data; + exec_policy::SpinningMutex mutex; + + void emplace_back(const std::array &pts) + { + std::lock_guard lk{mutex}; + data.emplace_back(pts); + } + + size_t size() const { return data.size(); } + const std::array& operator[](size_t idx) const + { + return data[idx]; + } + + } new_triangles; + + // A vector of bool for all faces signaling if it needs to be removed + // or not. + std::vector to_remove; + + MeshMods(const TriangleMesh &mesh): + to_remove(mesh.its.indices.size(), false) {} + + // Number of triangles that need to be removed. + size_t to_remove_cnt() const + { + return std::accumulate(to_remove.begin(), to_remove.end(), size_t(0)); + } + + } mesh_mods{mesh}; + + // Must return true if further division of the face is needed. + auto divfn = [&interior, bb, &mesh_mods](const DivFace &f) { + BoundingBoxf3 facebb { f.verts.begin(), f.verts.end() }; + + // Face is certainly outside the cavity + if (! facebb.intersects(bb) && f.faceid != NEW_FACE) { + return false; + } + + TriangleBubble bubble{facebb.center().cast(), facebb.radius()}; + + double D = get_distance(bubble, interior); + double R = bubble.R * interior.voxel_scale; + + if (std::isnan(D)) // The distance cannot be measured, triangle too big + return true; + + // Distance of the bubble wall to the interior wall. Negative if the + // bubble is overlapping with the interior + double bubble_distance = D - R; + + // The face is crossing the interior or inside, it must be removed and + // parts of it re-added, that are outside the interior + if (bubble_distance < 0.) { + if (f.faceid != NEW_FACE) + mesh_mods.to_remove[f.faceid] = true; + + if (f.parent != NEW_FACE) // Top parent needs to be removed as well + mesh_mods.to_remove[f.parent] = true; + + // If the outside part is between the interior end the exterior + // (inside the wall being invisible), no further division is needed. + if ((R + D) < interior.thickness) + return false; + + return true; + } else if (f.faceid == NEW_FACE) { + // New face completely outside needs to be re-added. + mesh_mods.new_triangles.emplace_back(f.verts); + } + + return false; + }; + + interior.reset_accessor(); + + exec_policy::for_each(size_t(0), faces.size(), [&] (size_t face_idx) { + const Vec3i &face = faces[face_idx]; + + // If the triangle is excluded, we need to keep it. + if (is_excluded(face_idx)) + return; + + std::array pts = + { vertices[face(0)], vertices[face(1)], vertices[face(2)] }; + + BoundingBoxf3 facebb { pts.begin(), pts.end() }; + + // Face is certainly outside the cavity + if (! facebb.intersects(bb)) return; + + DivFace df{face, pts, long(face_idx)}; + + if (divfn(df)) + divide_triangle(df, divfn); + + }, exec_policy::max_concurreny()); + + auto new_faces = reserve_vector(faces.size() + + mesh_mods.new_triangles.size()); + + for (size_t face_idx = 0; face_idx < faces.size(); ++face_idx) { + if (!mesh_mods.to_remove[face_idx]) + new_faces.emplace_back(faces[face_idx]); + } + + for(size_t i = 0; i < mesh_mods.new_triangles.size(); ++i) { + size_t o = vertices.size(); + vertices.emplace_back(mesh_mods.new_triangles[i][0]); + vertices.emplace_back(mesh_mods.new_triangles[i][1]); + vertices.emplace_back(mesh_mods.new_triangles[i][2]); + new_faces.emplace_back(int(o), int(o + 1), int(o + 2)); + } + + BOOST_LOG_TRIVIAL(info) + << "Trimming: " << mesh_mods.to_remove_cnt() << " triangles removed"; + BOOST_LOG_TRIVIAL(info) + << "Trimming: " << mesh_mods.new_triangles.size() << " triangles added"; + + faces.swap(new_faces); + new_faces = {}; + + mesh = TriangleMesh{mesh.its}; + mesh.repaired = true; mesh.require_shared_vertices(); } diff --git a/src/libslic3r/SLA/Hollowing.hpp b/src/libslic3r/SLA/Hollowing.hpp index 949cc23935..d9a77cd35e 100644 --- a/src/libslic3r/SLA/Hollowing.hpp +++ b/src/libslic3r/SLA/Hollowing.hpp @@ -19,23 +19,35 @@ struct HollowingConfig bool enabled = true; }; +enum HollowingFlags { hfRemoveInsideTriangles = 0x1 }; + +// All data related to a generated mesh interior. Includes the 3D grid and mesh +// and various metadata. No need to manipulate from outside. +struct Interior; +struct InteriorDeleter { void operator()(Interior *p); }; +using InteriorPtr = std::unique_ptr; + +TriangleMesh & get_mesh(Interior &interior); +const TriangleMesh &get_mesh(const Interior &interior); + struct DrainHole { Vec3f pos; Vec3f normal; float radius; float height; + bool failed = false; DrainHole() : pos(Vec3f::Zero()), normal(Vec3f::UnitZ()), radius(5.f), height(10.f) {} - DrainHole(Vec3f p, Vec3f n, float r, float h) - : pos(p), normal(n), radius(r), height(h) + DrainHole(Vec3f p, Vec3f n, float r, float h, bool fl = false) + : pos(p), normal(n), radius(r), height(h), failed(fl) {} DrainHole(const DrainHole& rhs) : - DrainHole(rhs.pos, rhs.normal, rhs.radius, rhs.height) {} + DrainHole(rhs.pos, rhs.normal, rhs.radius, rhs.height, rhs.failed) {} bool operator==(const DrainHole &sp) const; @@ -50,7 +62,7 @@ struct DrainHole template inline void serialize(Archive &ar) { - ar(pos, normal, radius, height); + ar(pos, normal, radius, height, failed); } static constexpr size_t steps = 32; @@ -60,11 +72,26 @@ using DrainHoles = std::vector; constexpr float HoleStickOutLength = 1.f; -std::unique_ptr generate_interior(const TriangleMesh &mesh, - const HollowingConfig & = {}, - const JobController &ctl = {}); +InteriorPtr generate_interior(const TriangleMesh &mesh, + const HollowingConfig & = {}, + const JobController &ctl = {}); -void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg); +// Will do the hollowing +void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg, int flags = 0); + +// Hollowing prepared in "interior", merge with original mesh +void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags = 0); + +void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior, + const std::vector &exclude_mask = {}); + +double get_distance(const Vec3f &p, const Interior &interior); + +template +FloatingOnly get_distance(const Vec<3, T> &p, const Interior &interior) +{ + return get_distance(Vec3f(p.template cast()), interior); +} void cut_drainholes(std::vector & obj_slices, const std::vector &slicegrid, diff --git a/src/libslic3r/SLA/IndexedMesh.cpp b/src/libslic3r/SLA/IndexedMesh.cpp index 485fa98ed8..2f47c63870 100644 --- a/src/libslic3r/SLA/IndexedMesh.cpp +++ b/src/libslic3r/SLA/IndexedMesh.cpp @@ -122,7 +122,7 @@ IndexedMesh::hit_result IndexedMesh::query_ray_hit(const Vec3d &s, const Vec3d &dir) const { assert(is_approx(dir.norm(), 1.)); - igl::Hit hit; + igl::Hit hit{-1, -1, 0.f, 0.f, 0.f}; hit.t = std::numeric_limits::infinity(); #ifdef SLIC3R_HOLE_RAYCASTER diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index 702690c196..b849212793 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -1,91 +1,110 @@ #include #include -#include + +#include +#include #include +#include #include "libslic3r/SLAPrint.hpp" #include "libslic3r/PrintConfig.hpp" #include -#include "Model.hpp" #include namespace Slic3r { namespace sla { -inline bool is_on_floor(const SLAPrintObject &mo) -{ - auto opt_elevation = mo.config().support_object_elevation.getFloat(); - auto opt_padaround = mo.config().pad_around_object.getBool(); +namespace { - return opt_elevation < EPSILON || opt_padaround; -} - -// Find transformed mesh ground level without copy and with parallel reduce. -double find_ground_level(const TriangleMesh &mesh, - const Transform3d & tr, - size_t threads) -{ - size_t vsize = mesh.its.vertices.size(); - - auto minfn = [](double a, double b) { return std::min(a, b); }; - - auto accessfn = [&mesh, &tr] (size_t vi) { - return (tr * mesh.its.vertices[vi].template cast()).z(); - }; - - double zmin = std::numeric_limits::max(); - size_t granularity = vsize / threads; - return ccr_par::reduce(size_t(0), vsize, zmin, minfn, accessfn, granularity); -} +inline const Vec3f DOWN = {0.f, 0.f, -1.f}; +constexpr double POINTS_PER_UNIT_AREA = 1.f; // Get the vertices of a triangle directly in an array of 3 points -std::array get_triangle_vertices(const TriangleMesh &mesh, +std::array get_triangle_vertices(const TriangleMesh &mesh, size_t faceidx) { const auto &face = mesh.its.indices[faceidx]; - return {Vec3d{mesh.its.vertices[face(0)].cast()}, - Vec3d{mesh.its.vertices[face(1)].cast()}, - Vec3d{mesh.its.vertices[face(2)].cast()}}; + return {mesh.its.vertices[face(0)], + mesh.its.vertices[face(1)], + mesh.its.vertices[face(2)]}; } -std::array get_transformed_triangle(const TriangleMesh &mesh, - const Transform3d & tr, +std::array get_transformed_triangle(const TriangleMesh &mesh, + const Transform3f & tr, size_t faceidx) { const auto &tri = get_triangle_vertices(mesh, faceidx); return {tr * tri[0], tr * tri[1], tr * tri[2]}; } +template Vec<3, T> normal(const std::array, 3> &tri) +{ + Vec<3, T> U = tri[1] - tri[0]; + Vec<3, T> V = tri[2] - tri[0]; + return U.cross(V).normalized(); +} + +template +T sum_score(AccessFn &&accessfn, size_t facecount, size_t Nthreads) +{ + T initv = 0.; + auto mergefn = [](T a, T b) { return a + b; }; + size_t grainsize = facecount / Nthreads; + size_t from = 0, to = facecount; + + return execution::reduce(ex_tbb, from, to, initv, mergefn, accessfn, grainsize); +} + +// Try to guess the number of support points needed to support a mesh +double get_misalginment_score(const TriangleMesh &mesh, const Transform3f &tr) +{ + if (mesh.its.vertices.empty()) return std::nan(""); + + auto accessfn = [&mesh, &tr](size_t fi) { + auto triangle = get_transformed_triangle(mesh, tr, fi); + Vec3f U = triangle[1] - triangle[0]; + Vec3f V = triangle[2] - triangle[0]; + Vec3f C = U.cross(V); + + // We should score against the alignment with the reference planes + return scaled(std::abs(C.dot(Vec3f::UnitX())) + + std::abs(C.dot(Vec3f::UnitY()))); + }; + + size_t facecount = mesh.its.indices.size(); + size_t Nthreads = std::thread::hardware_concurrency(); + double S = unscaled(sum_score(accessfn, facecount, Nthreads)); + + return S / facecount; +} + // Get area and normal of a triangle struct Facestats { - Vec3d normal; + Vec3f normal; double area; - explicit Facestats(const std::array &triangle) + explicit Facestats(const std::array &triangle) { - Vec3d U = triangle[1] - triangle[0]; - Vec3d V = triangle[2] - triangle[0]; - Vec3d C = U.cross(V); + Vec3f U = triangle[1] - triangle[0]; + Vec3f V = triangle[2] - triangle[0]; + Vec3f C = U.cross(V); normal = C.normalized(); area = 0.5 * C.norm(); } }; -inline const Vec3d DOWN = {0., 0., -1.}; -constexpr double POINTS_PER_UNIT_AREA = 1.; - // The score function for a particular face -inline double get_score(const Facestats &fc) +inline double get_supportedness_score(const Facestats &fc) { // Simply get the angle (acos of dot product) between the face normal and // the DOWN vector. - double phi = 1. - std::acos(fc.normal.dot(DOWN)) / PI; + float phi = 1. - std::acos(fc.normal.dot(DOWN)) / float(PI); - // Only consider faces that have have slopes below 90 deg: - phi = phi * (phi > 0.5); + // Only consider faces that have slopes below 90 deg: + phi = phi * (phi >= 0.5f); // Make the huge slopes more significant than the smaller slopes phi = phi * phi * phi; @@ -94,96 +113,92 @@ inline double get_score(const Facestats &fc) return fc.area * POINTS_PER_UNIT_AREA * phi; } -template -double sum_score(AccessFn &&accessfn, size_t facecount, size_t Nthreads) -{ - double initv = 0.; - auto mergefn = std::plus{}; - size_t grainsize = facecount / Nthreads; - size_t from = 0, to = facecount; - - return ccr_par::reduce(from, to, initv, mergefn, accessfn, grainsize); -} - // Try to guess the number of support points needed to support a mesh -double get_model_supportedness(const TriangleMesh &mesh, const Transform3d &tr) +double get_supportedness_score(const TriangleMesh &mesh, const Transform3f &tr) { if (mesh.its.vertices.empty()) return std::nan(""); auto accessfn = [&mesh, &tr](size_t fi) { Facestats fc{get_transformed_triangle(mesh, tr, fi)}; - return get_score(fc); + + return get_supportedness_score(fc); }; size_t facecount = mesh.its.indices.size(); size_t Nthreads = std::thread::hardware_concurrency(); - return sum_score(accessfn, facecount, Nthreads) / facecount; + double S = unscaled(sum_score(accessfn, facecount, Nthreads)); + + return S / facecount; } -double get_model_supportedness_onfloor(const TriangleMesh &mesh, - const Transform3d & tr) +// Find transformed mesh ground level without copy and with parallel reduce. +float find_ground_level(const TriangleMesh &mesh, + const Transform3f & tr, + size_t threads) +{ + size_t vsize = mesh.its.vertices.size(); + + auto minfn = [](float a, float b) { return std::min(a, b); }; + + auto accessfn = [&mesh, &tr] (size_t vi) { + return (tr * mesh.its.vertices[vi]).z(); + }; + + auto zmin = std::numeric_limits::max(); + size_t granularity = vsize / threads; + return execution::reduce(ex_tbb, size_t(0), vsize, zmin, minfn, accessfn, granularity); +} + +float get_supportedness_onfloor_score(const TriangleMesh &mesh, + const Transform3f & tr) { if (mesh.its.vertices.empty()) return std::nan(""); size_t Nthreads = std::thread::hardware_concurrency(); - double zmin = find_ground_level(mesh, tr, Nthreads); - double zlvl = zmin + 0.1; // Set up a slight tolerance from z level + float zmin = find_ground_level(mesh, tr, Nthreads); + float zlvl = zmin + 0.1f; // Set up a slight tolerance from z level auto accessfn = [&mesh, &tr, zlvl](size_t fi) { - std::array tri = get_transformed_triangle(mesh, tr, fi); + std::array tri = get_transformed_triangle(mesh, tr, fi); Facestats fc{tri}; if (tri[0].z() <= zlvl && tri[1].z() <= zlvl && tri[2].z() <= zlvl) return -fc.area * POINTS_PER_UNIT_AREA; - return get_score(fc); + return get_supportedness_score(fc); }; size_t facecount = mesh.its.indices.size(); - return sum_score(accessfn, facecount, Nthreads) / facecount; + double S = unscaled(sum_score(accessfn, facecount, Nthreads)); + + return S / facecount; } using XYRotation = std::array; // prepare the rotation transformation -Transform3d to_transform3d(const XYRotation &rot) +Transform3f to_transform3f(const XYRotation &rot) { - Transform3d rt = Transform3d::Identity(); - rt.rotate(Eigen::AngleAxisd(rot[1], Vec3d::UnitY())); - rt.rotate(Eigen::AngleAxisd(rot[0], Vec3d::UnitX())); + Transform3f rt = Transform3f::Identity(); + rt.rotate(Eigen::AngleAxisf(float(rot[1]), Vec3f::UnitY())); + rt.rotate(Eigen::AngleAxisf(float(rot[0]), Vec3f::UnitX())); + return rt; } -XYRotation from_transform3d(const Transform3d &tr) +XYRotation from_transform3f(const Transform3f &tr) { - Vec3d rot3d = Geometry::Transformation {tr}.get_rotation(); - return {rot3d.x(), rot3d.y()}; + Vec3d rot3 = Geometry::Transformation{tr.cast()}.get_rotation(); + return {rot3.x(), rot3.y()}; } -// Find the best score from a set of function inputs. Evaluate for every point. -template -std::array find_min_score(Fn &&fn, It from, It to, StopCond &&stopfn) +inline bool is_on_floor(const SLAPrintObjectConfig &cfg) { - std::array ret = {}; + auto opt_elevation = cfg.support_object_elevation.getFloat(); + auto opt_padaround = cfg.pad_around_object.getBool(); - double score = std::numeric_limits::max(); - - size_t Nthreads = std::thread::hardware_concurrency(); - size_t dist = std::distance(from, to); - std::vector scores(dist, score); - - ccr_par::for_each(size_t(0), dist, [&stopfn, &scores, &fn, &from](size_t i) { - if (stopfn()) return; - - scores[i] = fn(*(from + i)); - }, dist / Nthreads); - - auto it = std::min_element(scores.begin(), scores.end()); - - if (it != scores.end()) ret = *(from + std::distance(scores.begin(), it)); - - return ret; + return opt_elevation < EPSILON || opt_padaround; } // collect the rotations for each face of the convex hull @@ -214,8 +229,8 @@ std::vector get_chull_rotations(const TriangleMesh &mesh, size_t max Facestats fc{get_triangle_vertices(chull, fi)}; if (fc.area > area_threshold) { - auto q = Eigen::Quaterniond{}.FromTwoVectors(fc.normal, DOWN); - XYRotation rot = from_transform3d(Transform3d::Identity() * q); + auto q = Eigen::Quaternionf{}.FromTwoVectors(fc.normal, DOWN); + XYRotation rot = from_transform3f(Transform3f::Identity() * q); RotArea ra = {rot, fc.area}; auto it = std::lower_bound(inputs.begin(), inputs.end(), ra, rotcmp); @@ -238,10 +253,95 @@ std::vector get_chull_rotations(const TriangleMesh &mesh, size_t max return ret; } -Vec2d find_best_rotation(const SLAPrintObject & po, - float accuracy, - std::function statuscb, - std::function stopcond) +// Find the best score from a set of function inputs. Evaluate for every point. +template +std::array find_min_score(Fn &&fn, It from, It to, StopCond &&stopfn) +{ + std::array ret = {}; + + double score = std::numeric_limits::max(); + + size_t Nthreads = std::thread::hardware_concurrency(); + size_t dist = std::distance(from, to); + std::vector scores(dist, score); + + execution::for_each( + ex_tbb, size_t(0), dist, [&stopfn, &scores, &fn, &from](size_t i) { + if (stopfn()) return; + + scores[i] = fn(*(from + i)); + }, + dist / Nthreads); + + auto it = std::min_element(scores.begin(), scores.end()); + + if (it != scores.end()) + ret = *(from + std::distance(scores.begin(), it)); + + return ret; +} + +} // namespace + +Vec2d find_best_misalignment_rotation(const ModelObject & mo, + const RotOptimizeParams ¶ms) +{ + static constexpr unsigned MAX_TRIES = 1000; + + // return value + XYRotation rot; + + // We will use only one instance of this converted mesh to examine different + // rotations + TriangleMesh mesh = mo.raw_mesh(); + mesh.require_shared_vertices(); + + // To keep track of the number of iterations + int status = 0; + + // The maximum number of iterations + auto max_tries = unsigned(params.accuracy() * MAX_TRIES); + + auto &statuscb = params.statuscb(); + + // call status callback with zero, because we are at the start + statuscb(status); + + auto statusfn = [&statuscb, &status, &max_tries] { + // report status + statuscb(++status * 100.0/max_tries); + }; + + auto stopcond = [&statuscb] { + return ! statuscb(-1); + }; + + // Preparing the optimizer. + size_t gridsize = std::sqrt(max_tries); + opt::Optimizer solver(opt::StopCriteria{} + .max_iterations(max_tries) + .stop_condition(stopcond), + gridsize); + + // We are searching rotations around only two axes x, y. Thus the + // problem becomes a 2 dimensional optimization task. + // We can specify the bounds for a dimension in the following way: + auto bounds = opt::bounds({ {-PI/2, PI/2}, {-PI/2, PI/2} }); + + auto result = solver.to_max().optimize( + [&mesh, &statusfn] (const XYRotation &rot) + { + statusfn(); + return get_misalginment_score(mesh, to_transform3f(rot)); + }, opt::initvals({0., 0.}), bounds); + + rot = result.optimum; + + return {rot[0], rot[1]}; +} + +Vec2d find_least_supports_rotation(const ModelObject & mo, + const RotOptimizeParams ¶ms) { static const unsigned MAX_TRIES = 1000; @@ -250,14 +350,16 @@ Vec2d find_best_rotation(const SLAPrintObject & po, // We will use only one instance of this converted mesh to examine different // rotations - TriangleMesh mesh = po.model_object()->raw_mesh(); + TriangleMesh mesh = mo.raw_mesh(); mesh.require_shared_vertices(); // To keep track of the number of iterations unsigned status = 0; // The maximum number of iterations - auto max_tries = unsigned(accuracy * MAX_TRIES); + auto max_tries = unsigned(params.accuracy() * MAX_TRIES); + + auto &statuscb = params.statuscb(); // call status callback with zero, because we are at the start statuscb(status); @@ -267,8 +369,18 @@ Vec2d find_best_rotation(const SLAPrintObject & po, statuscb(unsigned(++status * 100.0/max_tries) ); }; + auto stopcond = [&statuscb] { + return ! statuscb(-1); + }; + + SLAPrintObjectConfig pocfg; + if (params.print_config()) + pocfg.apply(*params.print_config(), true); + + pocfg.apply(mo.config.get()); + // Different search methods have to be used depending on the model elevation - if (is_on_floor(po)) { + if (is_on_floor(pocfg)) { std::vector inputs = get_chull_rotations(mesh, max_tries); max_tries = inputs.size(); @@ -278,17 +390,19 @@ Vec2d find_best_rotation(const SLAPrintObject & po, auto objfn = [&mesh, &statusfn](const XYRotation &rot) { statusfn(); - Transform3d tr = to_transform3d(rot); - return get_model_supportedness_onfloor(mesh, tr); + Transform3f tr = to_transform3f(rot); + return get_supportedness_onfloor_score(mesh, tr); }; rot = find_min_score<2>(objfn, inputs.begin(), inputs.end(), stopcond); + } else { + // Preparing the optimizer. size_t gridsize = std::sqrt(max_tries); // 2D grid has gridsize^2 calls opt::Optimizer solver(opt::StopCriteria{} - .max_iterations(max_tries) - .stop_condition(stopcond), + .max_iterations(max_tries) + .stop_condition(stopcond), gridsize); // We are searching rotations around only two axes x, y. Thus the @@ -300,23 +414,14 @@ Vec2d find_best_rotation(const SLAPrintObject & po, [&mesh, &statusfn] (const XYRotation &rot) { statusfn(); - return get_model_supportedness(mesh, to_transform3d(rot)); + return get_supportedness_score(mesh, to_transform3f(rot)); }, opt::initvals({0., 0.}), bounds); - // Save the result and fck off + // Save the result rot = result.optimum; } return {rot[0], rot[1]}; } -double get_model_supportedness(const SLAPrintObject &po, const Transform3d &tr) -{ - TriangleMesh mesh = po.model_object()->raw_mesh(); - mesh.require_shared_vertices(); - - return is_on_floor(po) ? get_model_supportedness_onfloor(mesh, tr) : - get_model_supportedness(mesh, tr); -} - }} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/Rotfinder.hpp b/src/libslic3r/SLA/Rotfinder.hpp index 96561a890f..77a39016db 100644 --- a/src/libslic3r/SLA/Rotfinder.hpp +++ b/src/libslic3r/SLA/Rotfinder.hpp @@ -8,10 +8,39 @@ namespace Slic3r { +class ModelObject; class SLAPrintObject; +class TriangleMesh; +class DynamicPrintConfig; namespace sla { +using RotOptimizeStatusCB = std::function; + +class RotOptimizeParams { + float m_accuracy = 1.; + const DynamicPrintConfig *m_print_config = nullptr; + RotOptimizeStatusCB m_statuscb = [](int) { return true; }; + +public: + + RotOptimizeParams &accuracy(float a) { m_accuracy = a; return *this; } + RotOptimizeParams &print_config(const DynamicPrintConfig *c) + { + m_print_config = c; + return *this; + } + RotOptimizeParams &statucb(RotOptimizeStatusCB cb) + { + m_statuscb = std::move(cb); + return *this; + } + + float accuracy() const { return m_accuracy; } + const DynamicPrintConfig * print_config() const { return m_print_config; } + const RotOptimizeStatusCB &statuscb() const { return m_statuscb; } +}; + /** * The function should find the best rotation for SLA upside down printing. * @@ -19,23 +48,22 @@ namespace sla { * @param accuracy The optimization accuracy from 0.0f to 1.0f. Currently, * the nlopt genetic optimizer is used and the number of iterations is * accuracy * 100000. This can change in the future. - * @param statuscb A status indicator callback called with the unsigned + * @param statuscb A status indicator callback called with the int * argument spanning from 0 to 100. May not reach 100 if the optimization finds - * an optimum before max iterations are reached. - * @param stopcond A function that if returns true, the search process will be - * terminated and the best solution found will be returned. + * an optimum before max iterations are reached. It should return a boolean + * signaling if the operation may continue (true) or not (false). A status + * value lower than 0 shall not update the status but still return a valid + * continuation indicator. * * @return Returns the rotations around each axis (x, y, z) */ -Vec2d find_best_rotation( - const SLAPrintObject& modelobj, - float accuracy = 1.0f, - std::function statuscb = [] (unsigned) {}, - std::function stopcond = [] () { return false; } - ); +Vec2d find_best_misalignment_rotation(const ModelObject &modelobj, + const RotOptimizeParams & = {}); -double get_model_supportedness(const SLAPrintObject &mesh, - const Transform3d & tr); +Vec2d find_least_supports_rotation(const ModelObject &modelobj, + const RotOptimizeParams & = {}); + +double find_Z_fit_to_bed_rotation(const ModelObject &mo, const BoundingBox &bed); } // namespace sla } // namespace Slic3r diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 65fac73f34..42ed8b80fa 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -617,7 +617,7 @@ std::string SLAPrint::output_filename(const std::string &filename_base) const return this->PrintBase::output_filename(m_print_config.output_filename_format.value, ".sl1", filename_base, &config); } -std::string SLAPrint::validate() const +std::string SLAPrint::validate(std::string*) const { for(SLAPrintObject * po : m_objects) { @@ -1120,7 +1120,7 @@ TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const return this->pad_mesh(); case slaposDrillHoles: if (m_hollowing_data) - return m_hollowing_data->hollow_mesh_with_holes; + return get_mesh_to_print(); [[fallthrough]]; default: return TriangleMesh(); @@ -1149,8 +1149,9 @@ const TriangleMesh& SLAPrintObject::pad_mesh() const const TriangleMesh &SLAPrintObject::hollowed_interior_mesh() const { - if (m_hollowing_data && m_config.hollowing_enable.getBool()) - return m_hollowing_data->interior; + if (m_hollowing_data && m_hollowing_data->interior && + m_config.hollowing_enable.getBool()) + return sla::get_mesh(*m_hollowing_data->interior); return EMPTY_MESH; } diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 742670e2cb..adb80c29ac 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -85,6 +85,10 @@ public: // Get the mesh that is going to be printed with all the modifications // like hollowing and drilled holes. const TriangleMesh & get_mesh_to_print() const { + return (m_hollowing_data && is_step_done(slaposDrillHoles)) ? m_hollowing_data->hollow_mesh_with_holes_trimmed : transformed_mesh(); + } + + const TriangleMesh & get_mesh_to_slice() const { return (m_hollowing_data && is_step_done(slaposDrillHoles)) ? m_hollowing_data->hollow_mesh_with_holes : transformed_mesh(); } @@ -327,9 +331,10 @@ private: class HollowingData { public: - - TriangleMesh interior; + + sla::InteriorPtr interior; mutable TriangleMesh hollow_mesh_with_holes; // caching the complete hollowed mesh + mutable TriangleMesh hollow_mesh_with_holes_trimmed; }; std::unique_ptr m_hollowing_data; @@ -385,16 +390,25 @@ public: virtual void apply(const SLAPrinterConfig &cfg) = 0; // Fn have to be thread safe: void(sla::RasterBase& raster, size_t lyrid); - template void draw_layers(size_t layer_num, Fn &&drawfn) + template + void draw_layers( + size_t layer_num, + Fn && drawfn, + CancelFn cancelfn = []() { return false; }, + const EP & ep = {}) { m_layers.resize(layer_num); - sla::ccr::for_each(size_t(0), m_layers.size(), - [this, &drawfn] (size_t idx) { - sla::EncodedRaster& enc = m_layers[idx]; - auto rst = create_raster(); - drawfn(*rst, idx); - enc = rst->encode(get_encoder()); - }); + execution::for_each( + ep, size_t(0), m_layers.size(), + [this, &drawfn, &cancelfn](size_t idx) { + if (cancelfn()) return; + + sla::EncodedRaster &enc = m_layers[idx]; + auto rst = create_raster(); + drawfn(*rst, idx); + enc = rst->encode(get_encoder()); + }, + execution::max_concurrency(ep)); } }; @@ -458,7 +472,7 @@ public: const SLAPrintStatistics& print_statistics() const { return m_print_statistics; } - std::string validate() const override; + std::string validate(std::string* warning = nullptr) const override; // An aggregation of SliceRecord-s from all the print objects for each // occupied layer. Slice record levels dont have to match exactly. diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index d8bea62aea..c393eb295d 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -10,6 +12,7 @@ #include #include +#include #include @@ -84,17 +87,17 @@ SLAPrint::Steps::Steps(SLAPrint *print) void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin o) { if (o == soSupport && !po.m_supportdata) return; - + auto faded_lyrs = size_t(po.m_config.faded_layers.getInt()); double min_w = m_print->m_printer_config.elefant_foot_min_width.getFloat() / 2.; double start_efc = m_print->m_printer_config.elefant_foot_compensation.getFloat(); - + double doffs = m_print->m_printer_config.absolute_correction.getFloat(); coord_t clpr_offs = scaled(doffs); - + faded_lyrs = std::min(po.m_slice_index.size(), faded_lyrs); size_t faded_lyrs_efc = std::max(size_t(1), faded_lyrs - 1); - + auto efc = [start_efc, faded_lyrs_efc](size_t pos) { return (faded_lyrs_efc - pos) * start_efc / faded_lyrs_efc; }; @@ -102,13 +105,13 @@ void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin std::vector &slices = o == soModel ? po.m_model_slices : po.m_supportdata->support_slices; - + if (clpr_offs != 0) for (size_t i = 0; i < po.m_slice_index.size(); ++i) { size_t idx = po.m_slice_index[i].get_slice_idx(o); if (idx < slices.size()) slices[idx] = offset_ex(slices[idx], float(clpr_offs)); } - + if (start_efc > 0.) for (size_t i = 0; i < faded_lyrs; ++i) { size_t idx = po.m_slice_index[i].get_slice_idx(o); if (idx < slices.size()) @@ -124,28 +127,189 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!"; return; } - + BOOST_LOG_TRIVIAL(info) << "Performing hollowing step!"; double thickness = po.m_config.hollowing_min_thickness.getFloat(); double quality = po.m_config.hollowing_quality.getFloat(); double closing_d = po.m_config.hollowing_closing_distance.getFloat(); sla::HollowingConfig hlwcfg{thickness, quality, closing_d}; - auto meshptr = generate_interior(po.transformed_mesh(), hlwcfg); - if (meshptr->empty()) + sla::InteriorPtr interior = generate_interior(po.transformed_mesh(), hlwcfg); + + if (!interior || sla::get_mesh(*interior).empty()) BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!"; else { po.m_hollowing_data.reset(new SLAPrintObject::HollowingData()); - po.m_hollowing_data->interior = *meshptr; + po.m_hollowing_data->interior = std::move(interior); } } +struct FaceHash { + + // A hash is created for each triangle to be identifiable. The hash uses + // only the triangle's geometric traits, not the index in a particular mesh. + std::unordered_set facehash; + + static std::string facekey(const Vec3i &face, + const std::vector &vertices) + { + // Scale to integer to avoid floating points + std::array, 3> pts = { + scaled(vertices[face(0)]), + scaled(vertices[face(1)]), + scaled(vertices[face(2)]) + }; + + // Get the first two sides of the triangle, do a cross product and move + // that vector to the center of the triangle. This encodes all + // information to identify an identical triangle at the same position. + Vec<3, int64_t> a = pts[0] - pts[2], b = pts[1] - pts[2]; + Vec<3, int64_t> c = a.cross(b) + (pts[0] + pts[1] + pts[2]) / 3; + + // Return a concatenated string representation of the coordinates + return std::to_string(c(0)) + std::to_string(c(1)) + std::to_string(c(2)); + }; + + FaceHash(const indexed_triangle_set &its) + { + for (const Vec3i &face : its.indices) { + std::string keystr = facekey(face, its.vertices); + facehash.insert(keystr); + } + } + + bool find(const std::string &key) + { + auto it = facehash.find(key); + return it != facehash.end(); + } +}; + +// Create exclude mask for triangle removal inside hollowed interiors. +// This is necessary when the interior is already part of the mesh which was +// drilled using CGAL mesh boolean operation. Excluded will be the triangles +// originally part of the interior mesh and triangles that make up the drilled +// hole walls. +static std::vector create_exclude_mask( + const indexed_triangle_set &its, + const sla::Interior &interior, + const std::vector &holes) +{ + FaceHash interior_hash{sla::get_mesh(interior).its}; + + std::vector exclude_mask(its.indices.size(), false); + + std::vector< std::vector > neighbor_index = + create_neighbor_index(its); + + auto exclude_neighbors = [&neighbor_index, &exclude_mask](const Vec3i &face) + { + for (int i = 0; i < 3; ++i) { + const std::vector &neighbors = neighbor_index[face(i)]; + for (size_t fi_n : neighbors) exclude_mask[fi_n] = true; + } + }; + + for (size_t fi = 0; fi < its.indices.size(); ++fi) { + auto &face = its.indices[fi]; + + if (interior_hash.find(FaceHash::facekey(face, its.vertices))) { + exclude_mask[fi] = true; + continue; + } + + if (exclude_mask[fi]) { + exclude_neighbors(face); + continue; + } + + // Lets deal with the holes. All the triangles of a hole and all the + // neighbors of these triangles need to be kept. The neigbors were + // created by CGAL mesh boolean operation that modified the original + // interior inside the input mesh to contain the holes. + Vec3d tr_center = ( + its.vertices[face(0)] + + its.vertices[face(1)] + + its.vertices[face(2)] + ).cast() / 3.; + + // If the center is more than half a mm inside the interior, + // it cannot possibly be part of a hole wall. + if (sla::get_distance(tr_center, interior) < -0.5) + continue; + + Vec3f U = its.vertices[face(1)] - its.vertices[face(0)]; + Vec3f V = its.vertices[face(2)] - its.vertices[face(0)]; + Vec3f C = U.cross(V); + Vec3f face_normal = C.normalized(); + + for (const sla::DrainHole &dh : holes) { + if (dh.failed) continue; + + Vec3d dhpos = dh.pos.cast(); + Vec3d dhend = dhpos + dh.normal.cast() * dh.height; + + Linef3 holeaxis{dhpos, dhend}; + + double D_hole_center = line_alg::distance_to(holeaxis, tr_center); + double D_hole = std::abs(D_hole_center - dh.radius); + float dot = dh.normal.dot(face_normal); + + // Empiric tolerances for center distance and normals angle. + // For triangles that are part of a hole wall the angle of + // triangle normal and the hole axis is around 90 degrees, + // so the dot product is around zero. + double D_tol = dh.radius / sla::DrainHole::steps; + float normal_angle_tol = 1.f / sla::DrainHole::steps; + + if (D_hole < D_tol && std::abs(dot) < normal_angle_tol) { + exclude_mask[fi] = true; + exclude_neighbors(face); + } + } + } + + return exclude_mask; +} + +static indexed_triangle_set +remove_unconnected_vertices(const indexed_triangle_set &its) +{ + if (its.indices.empty()) {}; + + indexed_triangle_set M; + + std::vector vtransl(its.vertices.size(), -1); + int vcnt = 0; + for (auto &f : its.indices) { + + for (int i = 0; i < 3; ++i) + if (vtransl[size_t(f(i))] < 0) { + + M.vertices.emplace_back(its.vertices[size_t(f(i))]); + vtransl[size_t(f(i))] = vcnt++; + } + + std::array new_f = { + vtransl[size_t(f(0))], + vtransl[size_t(f(1))], + vtransl[size_t(f(2))] + }; + + M.indices.emplace_back(new_f[0], new_f[1], new_f[2]); + } + + return M; +} + // Drill holes into the hollowed/original mesh. void SLAPrint::Steps::drill_holes(SLAPrintObject &po) { bool needs_drilling = ! po.m_model_object->sla_drain_holes.empty(); - bool is_hollowed = (po.m_hollowing_data && ! po.m_hollowing_data->interior.empty()); + bool is_hollowed = + (po.m_hollowing_data && po.m_hollowing_data->interior && + !sla::get_mesh(*po.m_hollowing_data->interior).empty()); if (! is_hollowed && ! needs_drilling) { // In this case we can dump any data that might have been @@ -163,44 +327,101 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) // holes that are no longer on the frontend. TriangleMesh &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes; hollowed_mesh = po.transformed_mesh(); - if (! po.m_hollowing_data->interior.empty()) { - hollowed_mesh.merge(po.m_hollowing_data->interior); - hollowed_mesh.require_shared_vertices(); - } + if (is_hollowed) + sla::hollow_mesh(hollowed_mesh, *po.m_hollowing_data->interior); + + TriangleMesh &mesh_view = po.m_hollowing_data->hollow_mesh_with_holes_trimmed; if (! needs_drilling) { + mesh_view = po.transformed_mesh(); + + if (is_hollowed) + sla::hollow_mesh(mesh_view, *po.m_hollowing_data->interior, + sla::hfRemoveInsideTriangles); + BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes)."; return; } - + BOOST_LOG_TRIVIAL(info) << "Drilling drainage holes."; sla::DrainHoles drainholes = po.transformed_drainhole_points(); - + + auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( + hollowed_mesh.its.vertices, + hollowed_mesh.its.indices + ); + std::uniform_real_distribution dist(0., float(EPSILON)); - auto holes_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal({}); - for (sla::DrainHole holept : drainholes) { + auto holes_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal({}, {}); + indexed_triangle_set part_to_drill = hollowed_mesh.its; + + bool hole_fail = false; + for (size_t i = 0; i < drainholes.size(); ++i) { + sla::DrainHole holept = drainholes[i]; + holept.normal += Vec3f{dist(m_rng), dist(m_rng), dist(m_rng)}; holept.normal.normalize(); holept.pos += Vec3f{dist(m_rng), dist(m_rng), dist(m_rng)}; TriangleMesh m = sla::to_triangle_mesh(holept.to_mesh()); m.require_shared_vertices(); - auto cgal_m = MeshBoolean::cgal::triangle_mesh_to_cgal(m); - MeshBoolean::cgal::plus(*holes_mesh_cgal, *cgal_m); + + part_to_drill.indices.clear(); + auto bb = m.bounding_box(); + Eigen::AlignedBox ebb{bb.min.cast(), + bb.max.cast()}; + + AABBTreeIndirect::traverse( + tree, + AABBTreeIndirect::intersecting(ebb), + [&part_to_drill, &hollowed_mesh](size_t faceid) + { + part_to_drill.indices.emplace_back(hollowed_mesh.its.indices[faceid]); + }); + + auto cgal_meshpart = MeshBoolean::cgal::triangle_mesh_to_cgal( + remove_unconnected_vertices(part_to_drill)); + + if (MeshBoolean::cgal::does_self_intersect(*cgal_meshpart)) { + BOOST_LOG_TRIVIAL(error) << "Failed to drill hole"; + + hole_fail = drainholes[i].failed = + po.model_object()->sla_drain_holes[i].failed = true; + + continue; + } + + auto cgal_hole = MeshBoolean::cgal::triangle_mesh_to_cgal(m); + MeshBoolean::cgal::plus(*holes_mesh_cgal, *cgal_hole); } - + if (MeshBoolean::cgal::does_self_intersect(*holes_mesh_cgal)) throw Slic3r::SlicingError(L("Too many overlapping holes.")); - + auto hollowed_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal(hollowed_mesh); - + try { MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_mesh_cgal); + hollowed_mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*hollowed_mesh_cgal); + mesh_view = hollowed_mesh; + + if (is_hollowed) { + auto &interior = *po.m_hollowing_data->interior; + std::vector exclude_mask = + create_exclude_mask(mesh_view.its, interior, drainholes); + + sla::remove_inside_triangles(mesh_view, interior, exclude_mask); + } + } catch (const std::runtime_error &) { throw Slic3r::SlicingError(L( "Drilling holes into the mesh failed. " "This is usually caused by broken model. Try to fix it first.")); } + + if (hole_fail) + po.active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, + L("Failed to drill some holes into the model")); } // The slicing will be performed on an imaginary 1D grid which starts from @@ -212,11 +433,11 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) // of it. In any case, the model and the supports have to be sliced in the // same imaginary grid (the height vector argument to TriangleMeshSlicer). void SLAPrint::Steps::slice_model(SLAPrintObject &po) -{ - const TriangleMesh &mesh = po.get_mesh_to_print(); +{ + const TriangleMesh &mesh = po.get_mesh_to_slice(); // We need to prepare the slice index... - + double lhd = m_print->m_objects.front()->m_config.layer_height.getFloat(); float lh = float(lhd); coord_t lhs = scaled(lhd); @@ -226,43 +447,49 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) 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, 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 Slic3r::RuntimeError( 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()); for(auto it = slindex_it; it != po.m_slice_index.end(); ++it) po.m_model_height_levels.emplace_back(it->slice_level()); - + TriangleMeshSlicer slicer(&mesh); - + po.m_model_slices.clear(); float closing_r = float(po.config().slice_closing_radius.value); auto thr = [this]() { m_print->throw_if_canceled(); }; auto &slice_grid = po.m_model_height_levels; slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &po.m_model_slices, thr); - - if (po.m_hollowing_data && ! po.m_hollowing_data->interior.empty()) { - po.m_hollowing_data->interior.repair(true); - TriangleMeshSlicer interior_slicer(&po.m_hollowing_data->interior); + + sla::Interior *interior = po.m_hollowing_data ? + po.m_hollowing_data->interior.get() : + nullptr; + + if (interior && ! sla::get_mesh(*interior).empty()) { + TriangleMesh interiormesh = sla::get_mesh(*interior); + interiormesh.repaired = false; + interiormesh.repair(true); + TriangleMeshSlicer interior_slicer(&interiormesh); std::vector interior_slices; interior_slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &interior_slices, thr); @@ -273,17 +500,17 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) diff_ex(po.m_model_slices[i], slice); }); } - + auto mit = slindex_it; for (size_t id = 0; id < po.m_model_slices.size() && mit != po.m_slice_index.end(); id++) { mit->set_model_slice_idx(po, id); ++mit; } - + // We apply the printer correction offset here. apply_printer_corrections(po, soModel); - + if(po.m_config.supports_enable.getBool() || po.m_config.pad_enable.getBool()) { po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh)); @@ -296,22 +523,22 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) { // If supports are disabled, we can skip the model scan. if(!po.m_config.supports_enable.getBool()) return; - - const TriangleMesh &mesh = po.get_mesh_to_print(); - + + const TriangleMesh &mesh = po.get_mesh_to_slice(); + if (!po.m_supportdata) po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh)); - + const ModelObject& mo = *po.m_model_object; - + BOOST_LOG_TRIVIAL(debug) << "Support point count " << mo.sla_support_points.size(); - + // Unless the user modified the points or we already did the calculation, // we will do the autoplacement. Otherwise we will just blindly copy the // frontend data into the backend cache. if (mo.sla_points_status != sla::PointsStatus::UserModified) { - + // calculate heights of slices (slices are calculated already) const std::vector& heights = po.m_model_height_levels; @@ -319,27 +546,27 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) // calculated on slices, the algorithm then raycasts the points // so they actually lie on the mesh. // po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points()); - + throw_if_canceled(); sla::SupportPointGenerator::Config config; const SLAPrintObjectConfig& cfg = po.config(); - + // the density config value is in percents: config.density_relative = float(cfg.support_points_density_relative / 100.f); config.minimal_distance = float(cfg.support_points_minimal_distance); config.head_diameter = float(cfg.support_head_front_diameter); - + // scaling for the sub operations double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportPoints] / 100.0; double init = current_status(); - + auto statuscb = [this, d, init](unsigned st) { double current = init + st * d; if(std::round(current_status()) < std::round(current)) report_status(current, OBJ_STEP_LABELS(slaposSupportPoints)); }; - + // Construction of this object does the calculation. throw_if_canceled(); sla::SupportPointGenerator auto_supports( @@ -350,10 +577,10 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) const std::vector& points = auto_supports.output(); throw_if_canceled(); po.m_supportdata->pts = points; - + BOOST_LOG_TRIVIAL(debug) << "Automatic support points: " << po.m_supportdata->pts.size(); - + // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass // the update status to GLGizmoSlaSupports report_status(-1, L("Generating support points"), @@ -368,9 +595,9 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) void SLAPrint::Steps::support_tree(SLAPrintObject &po) { if(!po.m_supportdata) return; - + sla::PadConfig pcfg = make_pad_cfg(po.m_config); - + if (pcfg.embed_object) po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm); @@ -380,15 +607,15 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po) remove_bottom_points(po.m_supportdata->pts, float(po.m_supportdata->emesh.ground_level() + EPSILON)); } - + po.m_supportdata->cfg = make_support_cfg(po.m_config); // po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points()); - + // scaling for the sub operations double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0; double init = current_status(); sla::JobController ctl; - + ctl.statuscb = [this, d, init](unsigned st, const std::string &logmsg) { double current = init + st * d; if (std::round(current_status()) < std::round(current)) @@ -397,26 +624,26 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po) }; ctl.stopcondition = [this]() { return canceled(); }; ctl.cancelfn = [this]() { throw_if_canceled(); }; - + po.m_supportdata->create_support_tree(ctl); - + if (!po.m_config.supports_enable.getBool()) return; - + throw_if_canceled(); - + // Create the unified mesh auto rc = SlicingStatus::RELOAD_SCENE; - + // This is to prevent "Done." being displayed during merged_mesh() report_status(-1, L("Visualizing supports")); - + BOOST_LOG_TRIVIAL(debug) << "Processed support point count " << po.m_supportdata->pts.size(); - + // Check the mesh for later troubleshooting. if(po.support_mesh().empty()) BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty"; - + report_status(-1, L("Visualizing supports"), rc); } @@ -424,15 +651,15 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) { // this step can only go after the support tree has been created // and before the supports had been sliced. (or the slicing has to be // repeated) - + if(po.m_config.pad_enable.getBool()) { // Get the distilled pad configuration from the config sla::PadConfig pcfg = make_pad_cfg(po.m_config); - + ExPolygons bp; // This will store the base plate of the pad. double pad_h = pcfg.full_height(); const TriangleMesh &trmesh = po.transformed_mesh(); - + 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 @@ -442,19 +669,19 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) { float(po.m_config.layer_height.getFloat()), [this](){ throw_if_canceled(); }); } - + po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg); auto &pad_mesh = po.m_supportdata->support_tree_ptr->retrieve_mesh(sla::MeshType::Pad); - + if (!validate_pad(pad_mesh, pcfg)) throw Slic3r::SlicingError( L("No pad can be generated for this model with the " "current configuration")); - + } else if(po.m_supportdata && po.m_supportdata->support_tree_ptr) { po.m_supportdata->support_tree_ptr->remove_pad(); } - + throw_if_canceled(); report_status(-1, L("Visualizing supports"), SlicingStatus::RELOAD_SCENE); } @@ -464,25 +691,25 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) { // be part of the slices) void SLAPrint::Steps::slice_supports(SLAPrintObject &po) { auto& sd = po.m_supportdata; - + 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) { auto heights = reserve_vector(po.m_slice_index.size()); - + for(auto& rec : po.m_slice_index) heights.emplace_back(rec.slice_level()); sd->support_slices = sd->support_tree_ptr->slice( heights, float(po.config().slice_closing_radius.value)); } - - for (size_t i = 0; i < sd->support_slices.size() && i < po.m_slice_index.size(); ++i) + + for (size_t i = 0; i < sd->support_slices.size() && i < po.m_slice_index.size(); ++i) po.m_slice_index[i].set_support_slice_idx(po, i); - + apply_printer_corrections(po, soSupport); // Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update @@ -497,37 +724,37 @@ using ClipperPolygons = std::vector; static ClipperPolygons polyunion(const ClipperPolygons &subjects) { ClipperLib::Clipper clipper; - + bool closed = true; - + for(auto& path : subjects) { clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed); } - + auto mode = ClipperLib::pftPositive; - + return libnest2d::clipper_execute(clipper, ClipperLib::ctUnion, mode, mode); } static ClipperPolygons polydiff(const ClipperPolygons &subjects, const ClipperPolygons& clips) { ClipperLib::Clipper clipper; - + bool closed = true; - + for(auto& path : subjects) { clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed); } - + for(auto& path : clips) { clipper.AddPath(path.Contour, ClipperLib::ptClip, closed); clipper.AddPaths(path.Holes, ClipperLib::ptClip, closed); } - + auto mode = ClipperLib::pftPositive; - + return libnest2d::clipper_execute(clipper, ClipperLib::ctDifference, mode, mode); } @@ -535,28 +762,28 @@ static ClipperPolygons polydiff(const ClipperPolygons &subjects, const ClipperPo static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o) { namespace sl = libnest2d::sl; - + if (!record.print_obj()) return {}; - + ClipperPolygons polygons; auto &input_polygons = record.get_slice(o); auto &instances = record.print_obj()->instances(); bool is_lefthanded = record.print_obj()->is_left_handed(); polygons.reserve(input_polygons.size() * instances.size()); - + for (const ExPolygon& polygon : input_polygons) { if(polygon.contour.empty()) continue; - + for (size_t i = 0; i < instances.size(); ++i) { ClipperPolygon poly; - + // We need to reverse if is_lefthanded is true but bool needreverse = is_lefthanded; - + // should be a move poly.Contour.reserve(polygon.contour.size() + 1); - + auto& cntr = polygon.contour.points; if(needreverse) for(auto it = cntr.rbegin(); it != cntr.rend(); ++it) @@ -564,12 +791,12 @@ static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o else for(auto& p : cntr) poly.Contour.emplace_back(p.x(), p.y()); - + for(auto& h : polygon.holes) { poly.Holes.emplace_back(); auto& hole = poly.Holes.back(); hole.reserve(h.points.size() + 1); - + if(needreverse) for(auto it = h.points.rbegin(); it != h.points.rend(); ++it) hole.emplace_back(it->x(), it->y()); @@ -577,42 +804,42 @@ static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o for(auto& p : h.points) hole.emplace_back(p.x(), p.y()); } - + if(is_lefthanded) { for(auto& p : poly.Contour) p.X = -p.X; for(auto& h : poly.Holes) for(auto& p : h) p.X = -p.X; } - + sl::rotate(poly, double(instances[i].rotation)); sl::translate(poly, ClipperPoint{instances[i].shift.x(), instances[i].shift.y()}); - + polygons.emplace_back(std::move(poly)); } } - + return polygons; } void SLAPrint::Steps::initialize_printer_input() { auto &printer_input = m_print->m_printer_input; - + // clear the rasterizer input printer_input.clear(); - + size_t mx = 0; for(SLAPrintObject * o : m_print->m_objects) { if(auto m = o->get_slice_index().size() > mx) mx = m; } - + printer_input.reserve(mx); - + auto eps = coord_t(SCALED_EPSILON); - + for(SLAPrintObject * o : m_print->m_objects) { coord_t gndlvl = o->get_slice_index().front().print_level() - ilhs; - + for(const SliceRecord& slicerecord : o->get_slice_index()) { if (!slicerecord.is_valid()) throw Slic3r::SlicingError( @@ -621,7 +848,7 @@ void SLAPrint::Steps::initialize_printer_input() "objects printable.")); coord_t lvlid = slicerecord.print_level() - gndlvl; - + // Neat trick to round the layer levels to the grid. lvlid = eps * (lvlid / eps); @@ -631,8 +858,8 @@ void SLAPrint::Steps::initialize_printer_input() if(it == printer_input.end() || it->level() != lvlid) it = printer_input.insert(it, PrintLayer(lvlid)); - - + + it->add(slicerecord); } } @@ -641,53 +868,53 @@ void SLAPrint::Steps::initialize_printer_input() // Merging the slices from all the print objects into one slice grid and // calculating print statistics from the merge result. void SLAPrint::Steps::merge_slices_and_eval_stats() { - + initialize_printer_input(); - + auto &print_statistics = m_print->m_print_statistics; auto &printer_config = m_print->m_printer_config; auto &material_config = m_print->m_material_config; auto &printer_input = m_print->m_printer_input; - + print_statistics.clear(); - + // libnest calculates positive area for clockwise polygons, Slic3r is in counter-clockwise auto areafn = [](const ClipperPolygon& poly) { return - libnest2d::sl::area(poly); }; - + const double area_fill = printer_config.area_fill.getFloat()*0.01;// 0.5 (50%); const double fast_tilt = printer_config.fast_tilt_time.getFloat();// 5.0; const double slow_tilt = printer_config.slow_tilt_time.getFloat();// 8.0; - + const double init_exp_time = material_config.initial_exposure_time.getFloat(); const double exp_time = material_config.exposure_time.getFloat(); - + const int fade_layers_cnt = m_print->m_default_object_config.faded_layers.getInt();// 10 // [3;20] - + const auto width = scaled(printer_config.display_width.getFloat()); const auto height = scaled(printer_config.display_height.getFloat()); const double display_area = width*height; - + double supports_volume(0.0); double models_volume(0.0); - + double estim_time(0.0); std::vector layers_times; layers_times.reserve(printer_input.size()); - + size_t slow_layers = 0; size_t fast_layers = 0; - + const double delta_fade_time = (init_exp_time - exp_time) / (fade_layers_cnt + 1); double fade_layer_time = init_exp_time; - + sla::ccr::SpinningMutex mutex; using Lock = std::lock_guard; - + // Going to parallel: auto printlayerfn = [this, // functions and read only vars areafn, area_fill, display_area, exp_time, init_exp_time, fast_tilt, slow_tilt, delta_fade_time, - + // write vars &mutex, &models_volume, &supports_volume, &estim_time, &slow_layers, &fast_layers, &fade_layer_time, &layers_times](size_t sliced_layer_cnt) @@ -696,87 +923,87 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { // vector of slice record references auto& slicerecord_references = layer.slices(); - + if(slicerecord_references.empty()) return; - + // Layer height should match for all object slices for a given level. const auto l_height = double(slicerecord_references.front().get().layer_height()); - + // Calculation of the consumed material - + ClipperPolygons model_polygons; ClipperPolygons supports_polygons; - + 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(), size_t(0), [](size_t a, const SliceRecord &sr) { return a + sr.get_slice(soModel).size(); }); - + supports_polygons.reserve(c); - + for(const SliceRecord& record : layer.slices()) { - + ClipperPolygons modelslices = get_all_polygons(record, soModel); for(ClipperPolygon& p_tmp : modelslices) model_polygons.emplace_back(std::move(p_tmp)); - + ClipperPolygons supportslices = get_all_polygons(record, soSupport); for(ClipperPolygon& p_tmp : supportslices) supports_polygons.emplace_back(std::move(p_tmp)); - + } - + model_polygons = polyunion(model_polygons); double layer_model_area = 0; for (const ClipperPolygon& polygon : model_polygons) layer_model_area += areafn(polygon); - + if (layer_model_area < 0 || layer_model_area > 0) { Lock lck(mutex); models_volume += layer_model_area * l_height; } - + if(!supports_polygons.empty()) { if(model_polygons.empty()) supports_polygons = polyunion(supports_polygons); else supports_polygons = polydiff(supports_polygons, model_polygons); // allegedly, union of subject is done withing the diff according to the pftPositive polyFillType } - + double layer_support_area = 0; for (const ClipperPolygon& polygon : supports_polygons) layer_support_area += areafn(polygon); - + if (layer_support_area < 0 || layer_support_area > 0) { Lock lck(mutex); supports_volume += layer_support_area * l_height; } - + // Here we can save the expensively calculated polygons for printing ClipperPolygons trslices; trslices.reserve(model_polygons.size() + supports_polygons.size()); for(ClipperPolygon& poly : model_polygons) trslices.emplace_back(std::move(poly)); for(ClipperPolygon& poly : supports_polygons) trslices.emplace_back(std::move(poly)); - + layer.transformed_slices(polyunion(trslices)); - + // Calculation of the slow and fast layers to the future controlling those values on FW - + const bool is_fast_layer = (layer_model_area + layer_support_area) <= display_area*area_fill; const double tilt_time = is_fast_layer ? fast_tilt : slow_tilt; - + { Lock lck(mutex); if (is_fast_layer) fast_layers++; else slow_layers++; - + // Calculation of the printing time double layer_times = 0.0; @@ -794,15 +1021,15 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { estim_time += layer_times; } }; - + // sequential version for debugging: // for(size_t i = 0; i < m_printer_input.size(); ++i) printlayerfn(i); sla::ccr::for_each(size_t(0), printer_input.size(), printlayerfn); - + auto SCALING2 = SCALING_FACTOR * SCALING_FACTOR; print_statistics.support_used_material = supports_volume * SCALING2; print_statistics.objects_used_material = models_volume * SCALING2; - + // Estimated printing time // A layers count o the highest object if (printer_input.size() == 0) @@ -811,10 +1038,10 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { print_statistics.estimated_print_time = estim_time; print_statistics.layers_times = layers_times; } - + print_statistics.fast_layers_count = fast_layers; print_statistics.slow_layers_count = slow_layers; - + report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW); } @@ -822,23 +1049,23 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { void SLAPrint::Steps::rasterize() { if(canceled() || !m_print->m_printer) return; - + // coefficient to map the rasterization state (0-99) to the allocated // portion (slot) of the process state double sd = (100 - max_objstatus) / 100.0; - + // slot is the portion of 100% that is realted to rasterization unsigned slot = PRINT_STEP_LEVELS[slapsRasterize]; - + // pst: previous state double pst = current_status(); - + double increment = (slot * sd) / m_print->m_printer_input.size(); double dstatus = current_status(); - + sla::ccr::SpinningMutex slck; using Lock = std::lock_guard; - + // procedure to process one height level. This will run in parallel auto lvlfn = [this, &slck, increment, &dstatus, &pst] @@ -846,10 +1073,10 @@ void SLAPrint::Steps::rasterize() { PrintLayer& printlayer = m_print->m_printer_input[idx]; if(canceled()) return; - + for (const ClipperLib::Polygon& poly : printlayer.transformed_slices()) raster.draw(poly); - + // Status indication guarded with the spinlock { Lock lck(slck); @@ -861,12 +1088,13 @@ void SLAPrint::Steps::rasterize() } } }; - + // last minute escape if(canceled()) return; - + // Print all the layers in parallel - m_print->m_printer->draw_layers(m_print->m_printer_input.size(), lvlfn); + m_print->m_printer->draw_layers(m_print->m_printer_input.size(), lvlfn, + [this]() { return canceled(); }, ex_tbb); } std::string SLAPrint::Steps::label(SLAPrintObjectStep step) diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index 083b41202d..d0b1e9ce26 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -112,8 +112,10 @@ SlicingParameters SlicingParameters::create_from_config( if (! soluble_interface) { params.gap_raft_object = object_config.raft_contact_distance.value; - params.gap_object_support = object_config.support_material_contact_distance.value; + params.gap_object_support = object_config.support_material_bottom_contact_distance.value; params.gap_support_object = object_config.support_material_contact_distance.value; + if (params.gap_object_support <= 0) + params.gap_object_support = params.gap_support_object; } if (params.base_raft_layers > 0) { diff --git a/src/libslic3r/Slicing.hpp b/src/libslic3r/Slicing.hpp index 8b3d5c917e..489b2768fb 100644 --- a/src/libslic3r/Slicing.hpp +++ b/src/libslic3r/Slicing.hpp @@ -26,7 +26,7 @@ class DynamicPrintConfig; // (using a normal flow over a soluble support, using a bridging flow over a non-soluble support). struct SlicingParameters { - SlicingParameters() { memset(this, 0, sizeof(SlicingParameters)); } + SlicingParameters() = default; static SlicingParameters create_from_config( const PrintConfig &print_config, @@ -44,58 +44,58 @@ struct SlicingParameters // Height of the object to be printed. This value does not contain the raft height. coordf_t object_print_z_height() const { return object_print_z_max - object_print_z_min; } - bool valid; + bool valid { false }; // Number of raft layers. - size_t base_raft_layers; + size_t base_raft_layers { 0 }; // Number of interface layers including the contact layer. - size_t interface_raft_layers; + size_t interface_raft_layers { 0 }; // Layer heights of the raft (base, interface and a contact layer). - coordf_t base_raft_layer_height; - coordf_t interface_raft_layer_height; - coordf_t contact_raft_layer_height; + coordf_t base_raft_layer_height { 0 }; + coordf_t interface_raft_layer_height { 0 }; + coordf_t contact_raft_layer_height { 0 }; // The regular layer height, applied for all but the first layer, if not overridden by layer ranges // or by the variable layer thickness table. - coordf_t layer_height; + coordf_t layer_height { 0 }; // Minimum / maximum layer height, to be used for the automatic adaptive layer height algorithm, // or by an interactive layer height editor. - coordf_t min_layer_height; - coordf_t max_layer_height; - coordf_t max_suport_layer_height; + coordf_t min_layer_height { 0 }; + coordf_t max_layer_height { 0 }; + coordf_t max_suport_layer_height { 0 }; // First layer height of the print, this may be used for the first layer of the raft // or for the first layer of the print. - coordf_t first_print_layer_height; + coordf_t first_print_layer_height { 0 }; // Thickness of the first layer. This is either the first print layer thickness if printed without a raft, // or a bridging flow thickness if printed over a non-soluble raft, // or a normal layer height if printed over a soluble raft. - coordf_t first_object_layer_height; + coordf_t first_object_layer_height { 0 }; // If the object is printed over a non-soluble raft, the first layer may be printed with a briding flow. - bool first_object_layer_bridging; + bool first_object_layer_bridging { false }; // Soluble interface? (PLA soluble in water, HIPS soluble in lemonen) // otherwise the interface must be broken off. - bool soluble_interface; + bool soluble_interface { false }; // Gap when placing object over raft. - coordf_t gap_raft_object; + coordf_t gap_raft_object { 0 }; // Gap when placing support over object. - coordf_t gap_object_support; + coordf_t gap_object_support { 0 }; // Gap when placing object over support. - coordf_t gap_support_object; + coordf_t gap_support_object { 0 }; // Bottom and top of the printed object. // If printed without a raft, object_print_z_min = 0 and object_print_z_max = object height. // Otherwise object_print_z_min is equal to the raft height. - coordf_t raft_base_top_z; - coordf_t raft_interface_top_z; - coordf_t raft_contact_top_z; + coordf_t raft_base_top_z { 0 }; + coordf_t raft_interface_top_z { 0 }; + coordf_t raft_contact_top_z { 0 }; // In case of a soluble interface, object_print_z_min == raft_contact_top_z, otherwise there is a gap between the raft and the 1st object layer. - coordf_t object_print_z_min; - coordf_t object_print_z_max; + coordf_t object_print_z_min { 0 }; + coordf_t object_print_z_max { 0 }; }; static_assert(IsTriviallyCopyable::value, "SlicingParameters class is not POD (and it should be - see constructor)."); diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 459a156034..08cd04b909 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -6,10 +6,12 @@ #include "Fill/FillBase.hpp" #include "Geometry.hpp" #include "Point.hpp" +#include "MutablePolygon.hpp" #include #include #include +#include #include #include @@ -323,39 +325,50 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object m_object (object), m_print_config (&object->print()->config()), m_object_config (&object->config()), - m_slicing_params (slicing_params), - m_first_layer_flow (support_material_1st_layer_flow(object, float(slicing_params.first_print_layer_height))), - m_support_material_flow (support_material_flow(object, float(slicing_params.layer_height))), - m_support_material_interface_flow(support_material_interface_flow(object, float(slicing_params.layer_height))), - m_support_layer_height_min(0.01) + m_slicing_params (slicing_params) { + m_support_params.first_layer_flow = support_material_1st_layer_flow(object, float(slicing_params.first_print_layer_height)); + m_support_params.support_material_flow = support_material_flow(object, float(slicing_params.layer_height)); + m_support_params.support_material_interface_flow = support_material_interface_flow(object, float(slicing_params.layer_height)); + m_support_params.support_layer_height_min = 0.01; + // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um. - m_support_layer_height_min = 1000000.; + m_support_params.support_layer_height_min = 1000000.; for (auto lh : m_print_config->min_layer_height.values) - m_support_layer_height_min = std::min(m_support_layer_height_min, std::max(0.01, lh)); + m_support_params.support_layer_height_min = std::min(m_support_params.support_layer_height_min, std::max(0.01, lh)); if (m_object_config->support_material_interface_layers.value == 0) { // No interface layers allowed, print everything with the base support pattern. - m_support_material_interface_flow = m_support_material_flow; + m_support_params.support_material_interface_flow = m_support_params.support_material_flow; } // Evaluate the XY gap between the object outer perimeters and the support structures. // Evaluate the XY gap between the object outer perimeters and the support structures. coordf_t external_perimeter_width = 0.; + size_t num_nonempty_regions = 0; + coordf_t bridge_flow_ratio = 0; for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) - if (! object->region_volumes[region_id].empty()) - external_perimeter_width = std::max(external_perimeter_width, - (coordf_t)object->print()->get_region(region_id)->flow(frExternalPerimeter, slicing_params.layer_height, false, false, -1, *object).width); - m_gap_xy = m_object_config->support_material_xy_spacing.get_abs_value(external_perimeter_width); + if (! object->region_volumes[region_id].empty()) { + ++ num_nonempty_regions; + const PrintRegion ®ion = *object->print()->get_region(region_id); + external_perimeter_width = std::max(external_perimeter_width, coordf_t(region.flow(*object, frExternalPerimeter, slicing_params.layer_height).width())); + bridge_flow_ratio += region.config().bridge_flow_ratio; + } + m_support_params.gap_xy = m_object_config->support_material_xy_spacing.get_abs_value(external_perimeter_width); + bridge_flow_ratio /= num_nonempty_regions; - m_can_merge_support_regions = m_object_config->support_material_extruder.value == m_object_config->support_material_interface_extruder.value; - if (! m_can_merge_support_regions && (m_object_config->support_material_extruder.value == 0 || m_object_config->support_material_interface_extruder.value == 0)) { + m_support_params.support_material_bottom_interface_flow = m_slicing_params.soluble_interface || ! m_object_config->thick_bridges ? + m_support_params.support_material_interface_flow.with_flow_ratio(bridge_flow_ratio) : + Flow::bridging_flow(bridge_flow_ratio * m_support_params.support_material_interface_flow.nozzle_diameter(), m_support_params.support_material_interface_flow.nozzle_diameter()); + + m_support_params.can_merge_support_regions = m_object_config->support_material_extruder.value == m_object_config->support_material_interface_extruder.value; + if (!m_support_params.can_merge_support_regions && (m_object_config->support_material_extruder.value == 0 || m_object_config->support_material_interface_extruder.value == 0)) { // One of the support extruders is of "don't care" type. auto object_extruders = m_object->print()->object_extruders(); if (object_extruders.size() == 1 && *object_extruders.begin() == std::max(m_object_config->support_material_extruder.value, m_object_config->support_material_interface_extruder.value)) // Object is printed with the same extruder as the support. - m_can_merge_support_regions = true; + m_support_params.can_merge_support_regions = true; } } @@ -387,14 +400,6 @@ inline void layers_append(PrintObjectSupportMaterial::MyLayersPtr &dst, const Pr dst.insert(dst.end(), src.begin(), src.end()); } -// Compare layers lexicographically. -struct MyLayersPtrCompare -{ - bool operator()(const PrintObjectSupportMaterial::MyLayer* layer1, const PrintObjectSupportMaterial::MyLayer* layer2) const { - return *layer1 < *layer2; - } -}; - void PrintObjectSupportMaterial::generate(PrintObject &object) { BOOST_LOG_TRIVIAL(info) << "Support generator - Start"; @@ -409,13 +414,15 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) BOOST_LOG_TRIVIAL(info) << "Support generator - Creating top contacts"; + std::vector buildplate_covered = this->buildplate_covered(object); + // Determine the top contact surfaces of the support, defined as: // contact = overhangs - clearance + margin // This method is responsible for identifying what contact surfaces // should the support material expose to the object in order to guarantee // that it will be effective, regardless of how it's built below. // If raft is to be generated, the 1st top_contact layer will contain the 1st object layer silhouette without holes. - MyLayersPtr top_contacts = this->top_contact_layers(object, layer_storage); + MyLayersPtr top_contacts = this->top_contact_layers(object, buildplate_covered, layer_storage); if (top_contacts.empty()) // Nothing is supported, no supports are generated. return; @@ -437,8 +444,8 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // may get merged and trimmed by this->generate_base_layers() if the support layers are not synchronized with object layers. std::vector layer_support_areas; MyLayersPtr bottom_contacts = this->bottom_contact_layers_and_layer_support_areas( - object, top_contacts, layer_storage, - layer_support_areas); + object, top_contacts, buildplate_covered, + layer_storage, layer_support_areas); #ifdef SLIC3R_DEBUG for (size_t layer_id = 0; layer_id < object.layers().size(); ++ layer_id) @@ -457,10 +464,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) MyLayersPtr intermediate_layers = this->raft_and_intermediate_support_layers( object, bottom_contacts, top_contacts, layer_storage); -// this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, 0., m_gap_xy); - this->trim_support_layers_by_object(object, top_contacts, - m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, - m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy); + this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_support_params.gap_xy); #ifdef SLIC3R_DEBUG for (const MyLayer *layer : top_contacts) @@ -542,7 +546,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) layers_append(layers_sorted, interface_layers); layers_append(layers_sorted, base_interface_layers); // Sort the layers lexicographically by a raising print_z and a decreasing height. - std::sort(layers_sorted.begin(), layers_sorted.end(), MyLayersPtrCompare()); + std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); int layer_id = 0; assert(object.support_layers().empty()); for (size_t i = 0; i < layers_sorted.size();) { @@ -662,181 +666,229 @@ Polygons collect_slices_outer(const Layer &layer) return out; } +struct SupportGridParams { + SupportGridParams(const PrintObjectConfig &object_config, const Flow &support_material_flow) : + style(object_config.support_material_style.value), + grid_resolution(object_config.support_material_spacing.value + support_material_flow.spacing()), + support_angle(Geometry::deg2rad(object_config.support_material_angle.value)), + extrusion_width(support_material_flow.spacing()), + support_material_closing_radius(object_config.support_material_closing_radius.value), + expansion_to_slice(coord_t(support_material_flow.scaled_spacing() / 2 + 5)), + expansion_to_propagate(-3) {} + + SupportMaterialStyle style; + double grid_resolution; + double support_angle; + double extrusion_width; + double support_material_closing_radius; + coord_t expansion_to_slice; + coord_t expansion_to_propagate; +}; + class SupportGridPattern { public: - // Achtung! The support_polygons need to be trimmed by trimming_polygons, otherwise - // the selection by island_samples (see the island_samples() method) will not work! SupportGridPattern( // Support islands, to be stretched into a grid. Already trimmed with min(lower_layer_offset, m_gap_xy) - const Polygons &support_polygons, + const Polygons *support_polygons, // Trimming polygons, to trim the stretched support islands. support_polygons were already trimmed with trimming_polygons. - const Polygons &trimming_polygons, - // Grid spacing, given by "support_material_spacing" + m_support_material_flow.spacing() - coordf_t support_spacing, - coordf_t support_angle, - coordf_t line_spacing) : - m_support_polygons(&support_polygons), m_trimming_polygons(&trimming_polygons), - m_support_spacing(support_spacing), m_support_angle(support_angle) + const Polygons *trimming_polygons, + const SupportGridParams ¶ms) : + m_style(params.style), + m_support_polygons(support_polygons), m_trimming_polygons(trimming_polygons), + m_support_spacing(params.grid_resolution), m_support_angle(params.support_angle), + m_extrusion_width(params.extrusion_width), + m_support_material_closing_radius(params.support_material_closing_radius) { - if (m_support_angle != 0.) { - // Create a copy of the rotated contours. - m_support_polygons_rotated = support_polygons; - m_trimming_polygons_rotated = trimming_polygons; - m_support_polygons = &m_support_polygons_rotated; - m_trimming_polygons = &m_trimming_polygons_rotated; - polygons_rotate(m_support_polygons_rotated, - support_angle); - polygons_rotate(m_trimming_polygons_rotated, - support_angle); - } - - // Resolution of the sparse support grid. - coord_t grid_resolution = coord_t(scale_(m_support_spacing)); - BoundingBox bbox = get_extents(*m_support_polygons); - bbox.offset(20); - // Align the bounding box with the sparse support grid. - bbox.align_to_grid(grid_resolution); - - // Sample a single point per input support polygon, keep it as a reference to maintain corresponding - // polygons if ever these polygons get split into parts by the trimming polygons. - m_island_samples = island_samples(*m_support_polygons); - -#ifdef SUPPORT_USE_AGG_RASTERIZER - m_bbox = bbox; - // Oversample the grid to avoid leaking of supports through or around the object walls. - int oversampling = std::min(8, int(scale_(m_support_spacing) / (scale_(line_spacing) + 100))); - m_pixel_size = scale_(m_support_spacing / oversampling); - assert(scale_(line_spacing) + 20 < m_pixel_size); - // Add one empty column / row boundaries. - m_bbox.offset(m_pixel_size); - // Grid size fitting the support polygons plus one pixel boundary around the polygons. - Vec2i grid_size_raw(int(ceil((m_bbox.max.x() - m_bbox.min.x()) / m_pixel_size)), - int(ceil((m_bbox.max.y() - m_bbox.min.y()) / m_pixel_size))); - // Overlay macro blocks of (oversampling x oversampling) over the grid. - Vec2i grid_blocks((grid_size_raw.x() + oversampling - 1 - 2) / oversampling, - (grid_size_raw.y() + oversampling - 1 - 2) / oversampling); - // and resize the grid to fit the macro blocks + one pixel boundary. - m_grid_size = grid_blocks * oversampling + Vec2i(2, 2); - assert(m_grid_size.x() >= grid_size_raw.x()); - assert(m_grid_size.y() >= grid_size_raw.y()); - m_grid2 = rasterize_polygons(m_grid_size, m_pixel_size, m_bbox.min, *m_support_polygons); - - seed_fill_block(m_grid2, m_grid_size, - dilate_trimming_region(rasterize_polygons(m_grid_size, m_pixel_size, m_bbox.min, *m_trimming_polygons), m_grid_size), - grid_blocks, oversampling); - -#ifdef SLIC3R_DEBUG + switch (m_style) { + case smsGrid: { - static int irun; - Slic3r::png::write_gray_to_file_scaled(debug_out_path("support-rasterizer-%d.png", irun++), m_grid_size.x(), m_grid_size.y(), m_grid2.data(), 4); - } -#endif // SLIC3R_DEBUG + // Prepare the grid data, it will be reused when extracting support structures. + if (m_support_angle != 0.) { + // Create a copy of the rotated contours. + m_support_polygons_rotated = *support_polygons; + m_trimming_polygons_rotated = *trimming_polygons; + m_support_polygons = &m_support_polygons_rotated; + m_trimming_polygons = &m_trimming_polygons_rotated; + polygons_rotate(m_support_polygons_rotated, - params.support_angle); + polygons_rotate(m_trimming_polygons_rotated, - params.support_angle); + } -#else // SUPPORT_USE_AGG_RASTERIZER - // Create an EdgeGrid, initialize it with projection, initialize signed distance field. - m_grid.set_bbox(bbox); - m_grid.create(*m_support_polygons, grid_resolution); -#if 0 - if (m_grid.has_intersecting_edges()) { - // EdgeGrid fails to produce valid signed distance function for self-intersecting polygons. - m_support_polygons_rotated = simplify_polygons(*m_support_polygons); - m_support_polygons = &m_support_polygons_rotated; + // Resolution of the sparse support grid. + coord_t grid_resolution = coord_t(scale_(m_support_spacing)); + BoundingBox bbox = get_extents(*m_support_polygons); + bbox.offset(20); + // Align the bounding box with the sparse support grid. + bbox.align_to_grid(grid_resolution); + + #ifdef SUPPORT_USE_AGG_RASTERIZER + m_bbox = bbox; + // Oversample the grid to avoid leaking of supports through or around the object walls. + int oversampling = std::min(8, int(scale_(m_support_spacing) / (scale_(params.extrusion_width) + 100))); + m_pixel_size = scale_(m_support_spacing / oversampling); + assert(scale_(params.extrusion_width) + 20 < m_pixel_size); + // Add one empty column / row boundaries. + m_bbox.offset(m_pixel_size); + // Grid size fitting the support polygons plus one pixel boundary around the polygons. + Vec2i grid_size_raw(int(ceil((m_bbox.max.x() - m_bbox.min.x()) / m_pixel_size)), + int(ceil((m_bbox.max.y() - m_bbox.min.y()) / m_pixel_size))); + // Overlay macro blocks of (oversampling x oversampling) over the grid. + Vec2i grid_blocks((grid_size_raw.x() + oversampling - 1 - 2) / oversampling, + (grid_size_raw.y() + oversampling - 1 - 2) / oversampling); + // and resize the grid to fit the macro blocks + one pixel boundary. + m_grid_size = grid_blocks * oversampling + Vec2i(2, 2); + assert(m_grid_size.x() >= grid_size_raw.x()); + assert(m_grid_size.y() >= grid_size_raw.y()); + m_grid2 = rasterize_polygons(m_grid_size, m_pixel_size, m_bbox.min, *m_support_polygons); + + seed_fill_block(m_grid2, m_grid_size, + dilate_trimming_region(rasterize_polygons(m_grid_size, m_pixel_size, m_bbox.min, *m_trimming_polygons), m_grid_size), + grid_blocks, oversampling); + + #ifdef SLIC3R_DEBUG + { + static int irun; + Slic3r::png::write_gray_to_file_scaled(debug_out_path("support-rasterizer-%d.png", irun++), m_grid_size.x(), m_grid_size.y(), m_grid2.data(), 4); + } + #endif // SLIC3R_DEBUG + + #else // SUPPORT_USE_AGG_RASTERIZER + // Create an EdgeGrid, initialize it with projection, initialize signed distance field. m_grid.set_bbox(bbox); m_grid.create(*m_support_polygons, grid_resolution); -// assert(! m_grid.has_intersecting_edges()); - printf("SupportGridPattern: fixing polygons with intersection %s\n", - m_grid.has_intersecting_edges() ? "FAILED" : "SUCCEEDED"); + #if 0 + if (m_grid.has_intersecting_edges()) { + // EdgeGrid fails to produce valid signed distance function for self-intersecting polygons. + m_support_polygons_rotated = simplify_polygons(*m_support_polygons); + m_support_polygons = &m_support_polygons_rotated; + m_grid.set_bbox(bbox); + m_grid.create(*m_support_polygons, grid_resolution); + // assert(! m_grid.has_intersecting_edges()); + printf("SupportGridPattern: fixing polygons with intersection %s\n", + m_grid.has_intersecting_edges() ? "FAILED" : "SUCCEEDED"); + } + #endif + m_grid.calculate_sdf(); + #endif // SUPPORT_USE_AGG_RASTERIZER + break; + } + + case smsSnug: + default: + // nothing to prepare + break; } -#endif - m_grid.calculate_sdf(); -#endif // SUPPORT_USE_AGG_RASTERIZER } // Extract polygons from the grid, offsetted by offset_in_grid, // and trim the extracted polygons by trimming_polygons. // Trimming by the trimming_polygons may split the extracted polygons into pieces. // Remove all the pieces, which do not contain any of the island_samples. - Polygons extract_support(const coord_t offset_in_grid, bool fill_holes) + Polygons extract_support(const coord_t offset_in_grid, bool fill_holes +#ifdef SLIC3R_DEBUG + , const char *step_name, int iRun, size_t layer_id, double print_z +#endif + ) { -#ifdef SUPPORT_USE_AGG_RASTERIZER - Polygons support_polygons_simplified = contours_simplified(m_grid_size, m_pixel_size, m_bbox.min, m_grid2, offset_in_grid, fill_holes); -#else // SUPPORT_USE_AGG_RASTERIZER - // Generate islands, so each island may be tested for overlap with m_island_samples. - assert(std::abs(2 * offset_in_grid) < m_grid.resolution()); - Polygons support_polygons_simplified = m_grid.contours_simplified(offset_in_grid, fill_holes); -#endif // SUPPORT_USE_AGG_RASTERIZER + switch (m_style) { + case smsGrid: + { + #ifdef SUPPORT_USE_AGG_RASTERIZER + Polygons support_polygons_simplified = contours_simplified(m_grid_size, m_pixel_size, m_bbox.min, m_grid2, offset_in_grid, fill_holes); + #else // SUPPORT_USE_AGG_RASTERIZER + // Generate islands, so each island may be tested for overlap with island_samples. + assert(std::abs(2 * offset_in_grid) < m_grid.resolution()); + Polygons support_polygons_simplified = m_grid.contours_simplified(offset_in_grid, fill_holes); + #endif // SUPPORT_USE_AGG_RASTERIZER - ExPolygons islands = diff_ex(support_polygons_simplified, *m_trimming_polygons, false); + ExPolygons islands = diff_ex(support_polygons_simplified, *m_trimming_polygons, false); + + // Extract polygons, which contain some of the island_samples. + Polygons out; + + // Sample a single point per input support polygon, keep it as a reference to maintain corresponding + // polygons if ever these polygons get split into parts by the trimming polygons. + // As offset_in_grid may be negative, m_support_polygons may stick slightly outside of islands. + // Trim ti with islands. + Points samples = island_samples( + offset_in_grid > 0 ? + // Expanding, thus m_support_polygons are all inside islands. + union_ex(*m_support_polygons) : + // Shrinking, thus m_support_polygons may be trimmed a tiny bit by islands. + intersection_ex(*m_support_polygons, to_polygons(islands))); - // Extract polygons, which contain some of the m_island_samples. - Polygons out; -#if 0 - out = to_polygons(std::move(islands)); -#else - for (ExPolygon &island : islands) { - BoundingBox bbox = get_extents(island.contour); - // Samples are sorted lexicographically. - auto it_lower = std::lower_bound(m_island_samples.begin(), m_island_samples.end(), Point(bbox.min - Point(1, 1))); - auto it_upper = std::upper_bound(m_island_samples.begin(), m_island_samples.end(), Point(bbox.max + Point(1, 1))); std::vector> samples_inside; - for (auto it = it_lower; it != it_upper; ++ it) - if (bbox.contains(*it)) - samples_inside.push_back(std::make_pair(*it, false)); - if (! samples_inside.empty()) { - // For all samples_inside count the boundary crossing. - for (size_t i_contour = 0; i_contour <= island.holes.size(); ++ i_contour) { - Polygon &contour = (i_contour == 0) ? island.contour : island.holes[i_contour - 1]; - Points::const_iterator i = contour.points.begin(); - Points::const_iterator j = contour.points.end() - 1; - for (; i != contour.points.end(); j = i ++) { - //FIXME this test is not numerically robust. Particularly, it does not handle horizontal segments at y == point(1) well. - // Does the ray with y == point(1) intersect this line segment? - for (auto &sample_inside : samples_inside) { - if (((*i)(1) > sample_inside.first(1)) != ((*j)(1) > sample_inside.first(1))) { - double x1 = (double)sample_inside.first(0); - double x2 = (double)(*i)(0) + (double)((*j)(0) - (*i)(0)) * (double)(sample_inside.first(1) - (*i)(1)) / (double)((*j)(1) - (*i)(1)); - if (x1 < x2) - sample_inside.second = !sample_inside.second; + for (ExPolygon &island : islands) { + BoundingBox bbox = get_extents(island.contour); + // Samples are sorted lexicographically. + auto it_lower = std::lower_bound(samples.begin(), samples.end(), Point(bbox.min - Point(1, 1))); + auto it_upper = std::upper_bound(samples.begin(), samples.end(), Point(bbox.max + Point(1, 1))); + samples_inside.clear(); + for (auto it = it_lower; it != it_upper; ++ it) + if (bbox.contains(*it)) + samples_inside.push_back(std::make_pair(*it, false)); + if (! samples_inside.empty()) { + // For all samples_inside count the boundary crossing. + for (size_t i_contour = 0; i_contour <= island.holes.size(); ++ i_contour) { + Polygon &contour = (i_contour == 0) ? island.contour : island.holes[i_contour - 1]; + Points::const_iterator i = contour.points.begin(); + Points::const_iterator j = contour.points.end() - 1; + for (; i != contour.points.end(); j = i ++) { + //FIXME this test is not numerically robust. Particularly, it does not handle horizontal segments at y == point(1) well. + // Does the ray with y == point(1) intersect this line segment? + for (auto &sample_inside : samples_inside) { + if (((*i)(1) > sample_inside.first(1)) != ((*j)(1) > sample_inside.first(1))) { + double x1 = (double)sample_inside.first(0); + double x2 = (double)(*i)(0) + (double)((*j)(0) - (*i)(0)) * (double)(sample_inside.first(1) - (*i)(1)) / (double)((*j)(1) - (*i)(1)); + if (x1 < x2) + sample_inside.second = !sample_inside.second; + } } } } + // If any of the sample is inside this island, add this island to the output. + for (auto &sample_inside : samples_inside) + if (sample_inside.second) { + polygons_append(out, std::move(island)); + island.clear(); + break; + } } - // If any of the sample is inside this island, add this island to the output. - for (auto &sample_inside : samples_inside) - if (sample_inside.second) { - polygons_append(out, std::move(island)); - island.clear(); - break; - } } - } -#endif #ifdef SLIC3R_DEBUG - static int iRun = 0; - ++iRun; - BoundingBox bbox = get_extents(*m_trimming_polygons); - if (! islands.empty()) - bbox.merge(get_extents(islands)); - if (!out.empty()) - bbox.merge(get_extents(out)); - if (!support_polygons_simplified.empty()) - bbox.merge(get_extents(support_polygons_simplified)); - SVG svg(debug_out_path("extract_support_from_grid_trimmed-%d.svg", iRun).c_str(), bbox); - svg.draw(union_ex(support_polygons_simplified), "gray", 0.25f); - svg.draw(islands, "red", 0.5f); - svg.draw(union_ex(out), "green", 0.5f); - svg.draw(union_ex(*m_support_polygons), "blue", 0.5f); - svg.draw_outline(islands, "red", "red", scale_(0.05)); - svg.draw_outline(union_ex(out), "green", "green", scale_(0.05)); - svg.draw_outline(union_ex(*m_support_polygons), "blue", "blue", scale_(0.05)); - for (const Point &pt : m_island_samples) - svg.draw(pt, "black", coord_t(scale_(0.15))); - svg.Close(); + BoundingBox bbox = get_extents(*m_trimming_polygons); + if (! islands.empty()) + bbox.merge(get_extents(islands)); + if (!out.empty()) + bbox.merge(get_extents(out)); + if (!support_polygons_simplified.empty()) + bbox.merge(get_extents(support_polygons_simplified)); + SVG svg(debug_out_path("extract_support_from_grid_trimmed-%s-%d-%d-%lf.svg", step_name, iRun, layer_id, print_z).c_str(), bbox); + svg.draw(union_ex(support_polygons_simplified), "gray", 0.25f); + svg.draw(islands, "red", 0.5f); + svg.draw(union_ex(out), "green", 0.5f); + svg.draw(union_ex(*m_support_polygons), "blue", 0.5f); + svg.draw_outline(islands, "red", "red", scale_(0.05)); + svg.draw_outline(union_ex(out), "green", "green", scale_(0.05)); + svg.draw_outline(union_ex(*m_support_polygons), "blue", "blue", scale_(0.05)); + for (const Point &pt : samples) + svg.draw(pt, "black", coord_t(scale_(0.15))); + svg.Close(); #endif /* SLIC3R_DEBUG */ - if (m_support_angle != 0.) - polygons_rotate(out, m_support_angle); - return out; + if (m_support_angle != 0.) + polygons_rotate(out, m_support_angle); + return out; + } + case smsSnug: + // Merge the support polygons by applying morphological closing and inwards smoothing. + auto closing_distance = scaled(m_support_material_closing_radius); + auto smoothing_distance = scaled(m_extrusion_width); + return smooth_outward(offset(offset_ex(*m_support_polygons, closing_distance), - closing_distance), smoothing_distance); + } + assert(false); + return Polygons(); } #if defined(SLIC3R_DEBUG) && ! defined(SUPPORT_USE_AGG_RASTERIZER) @@ -940,9 +992,6 @@ public: m_grid.set_bbox(bbox); m_grid.create(*m_support_polygons, grid_resolution); m_grid.calculate_sdf(); - // Sample a single point per input support polygon, keep it as a reference to maintain corresponding - // polygons if ever these polygons get split into parts by the trimming polygons. - m_island_samples = island_samples(*m_support_polygons); return true; } @@ -1075,11 +1124,7 @@ private: return pts; } - static Points island_samples(const Polygons &polygons) - { - return island_samples(union_ex(polygons)); - } - + SupportMaterialStyle m_style; const Polygons *m_support_polygons; const Polygons *m_trimming_polygons; Polygons m_support_polygons_rotated; @@ -1088,6 +1133,9 @@ private: coordf_t m_support_angle; // X spacing of the support lines parallel with the Y axis. coordf_t m_support_spacing; + coordf_t m_extrusion_width; + // For snug supports: Morphological closing of support areas. + coordf_t m_support_material_closing_radius; #ifdef SUPPORT_USE_AGG_RASTERIZER Vec2i m_grid_size; @@ -1098,10 +1146,6 @@ private: Slic3r::EdgeGrid::Grid m_grid; #endif // SUPPORT_USE_AGG_RASTERIZER - // Internal sample points of supporting expolygons. These internal points are used to pick regions corresponding - // to the initial supporting regions, after these regions werre grown and possibly split to many by the trimming polygons. - Points m_island_samples; - #ifdef SLIC3R_DEBUG // support for deserialization of m_support_polygons, m_trimming_polygons Polygons m_support_polygons_deserialized; @@ -1231,8 +1275,8 @@ namespace SupportMaterialInternal { // since we're dealing with bridges, we can't assume width is larger than spacing, // so we take the largest value and also apply safety offset to be ensure no gaps // are left in between - Flow bridge_flow = layerm->flow(frPerimeter, true); - float w = float(std::max(bridge_flow.scaled_width(), bridge_flow.scaled_spacing())); + Flow perimeter_bridge_flow = layerm->bridging_flow(frPerimeter); + float w = float(std::max(perimeter_bridge_flow.scaled_width(), perimeter_bridge_flow.scaled_spacing())); for (Polyline &polyline : overhang_perimeters) if (polyline.is_straight()) { // This is a bridge @@ -1275,65 +1319,14 @@ namespace SupportMaterialInternal { } } -#if 0 -static int Test() +std::vector PrintObjectSupportMaterial::buildplate_covered(const PrintObject &object) const { -// for (int i = 0; i < 30; ++ i) - { - int i = -1; -// SupportGridPattern grid("d:\\temp\\support-top-contacts-final-run1-layer460-z70.300000-prev.bin", i); -// SupportGridPattern grid("d:\\temp\\support-top-contacts-final-run1-layer460-z70.300000.bin", i); - auto grid = SupportGridPattern::deserialize("d:\\temp\\support-top-contacts-final-run1-layer27-z5.650000.bin", i); - std::vector> intersections = grid.grid().intersecting_edges(); - if (! intersections.empty()) - printf("Intersections between contours!\n"); - Slic3r::export_intersections_to_svg("d:\\temp\\support_polygon_intersections.svg", grid.support_polygons()); - Slic3r::SVG::export_expolygons("d:\\temp\\support_polygons.svg", union_ex(grid.support_polygons(), false)); - Slic3r::SVG::export_expolygons("d:\\temp\\trimming_polygons.svg", union_ex(grid.trimming_polygons(), false)); - Polygons extracted = grid.extract_support(scale_(0.21 / 2), true); - Slic3r::SVG::export_expolygons("d:\\temp\\extracted.svg", union_ex(extracted, false)); - printf("hu!"); - } - return 0; -} -static int run_support_test = Test(); -#endif /* SLIC3R_DEBUG */ - -// Generate top contact layers supporting overhangs. -// For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined. -// If supports over bed surface only are requested, don't generate contact layers over an object. -PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_layers( - const PrintObject &object, MyLayerStorage &layer_storage) const -{ -#ifdef SLIC3R_DEBUG - static int iRun = 0; - ++ iRun; -#endif /* SLIC3R_DEBUG */ - - // Slice support enforcers / support blockers. - std::vector enforcers = object.slice_support_enforcers(); - std::vector blockers = object.slice_support_blockers(); - - // Append custom supports. - object.project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers); - object.project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers); - - // Output layers, sorted by top Z. - MyLayersPtr contact_out; - - const bool support_auto = m_object_config->support_material.value && m_object_config->support_material_auto.value; - // If user specified a custom angle threshold, convert it to radians. - // Zero means automatic overhang detection. - const double threshold_rad = (m_object_config->support_material_threshold.value > 0) ? - M_PI * double(m_object_config->support_material_threshold.value + 1) / 180. : // +1 makes the threshold inclusive - 0.; - // Build support on a build plate only? If so, then collect and union all the surfaces below the current layer. // Unfortunately this is an inherently serial process. const bool buildplate_only = this->build_plate_only(); std::vector buildplate_covered; if (buildplate_only) { - BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() - collecting regions covering the print bed."; + BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::buildplate_covered() - start"; buildplate_covered.assign(object.layers().size(), Polygons()); for (size_t layer_id = 1; layer_id < object.layers().size(); ++ layer_id) { const Layer &lower_layer = *object.layers()[layer_id-1]; @@ -1347,7 +1340,546 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ polygons_append(covered, offset(lower_layer.lslices, scale_(0.01))); covered = union_(covered, false); // don't apply the safety offset. } + BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::buildplate_covered() - end"; } + return buildplate_covered; +} + +struct SupportAnnotations +{ + SupportAnnotations(const PrintObject &object, const std::vector &buildplate_covered) : + enforcers_layers(object.slice_support_enforcers()), + blockers_layers(object.slice_support_blockers()), + buildplate_covered(buildplate_covered) + { + // Append custom supports. + object.project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers_layers); + object.project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers_layers); + } + + std::vector enforcers_layers; + std::vector blockers_layers; + const std::vector& buildplate_covered; +}; + +struct SlicesMarginCache +{ + float offset { -1 }; + // Trimming polygons, including possibly the "build plate only" mask. + Polygons polygons; + // Trimming polygons, without the "build plate only" mask. If empty, use polygons. + Polygons all_polygons; +}; + +static inline std::tuple detect_overhangs( + const Layer &layer, + const size_t layer_id, + const Polygons &lower_layer_polygons, + const PrintConfig &print_config, + const PrintObjectConfig &object_config, + SupportAnnotations &annotations, + SlicesMarginCache &slices_margin, + const double gap_xy +#ifdef SLIC3R_DEBUG + , size_t iRun +#endif // SLIC3R_DEBUG + ) +{ + // Snug overhang polygons. + Polygons overhang_polygons; + // Expanded for stability, trimmed by gap_xy. + Polygons contact_polygons; + // Enforcers projected to overhangs, trimmed + Polygons enforcer_polygons; + + const bool support_auto = object_config.support_material.value && object_config.support_material_auto.value; + const bool buildplate_only = ! annotations.buildplate_covered.empty(); + // If user specified a custom angle threshold, convert it to radians. + // Zero means automatic overhang detection. + const double threshold_rad = (object_config.support_material_threshold.value > 0) ? + M_PI * double(object_config.support_material_threshold.value + 1) / 180. : // +1 makes the threshold inclusive + 0.; + float no_interface_offset = 0.f; + + if (layer_id == 0) + { + // This is the first object layer, so the object is being printed on a raft and + // we're here just to get the object footprint for the raft. +#if 0 + // The following line was filling excessive holes in the raft, see GH #430 + overhang_polygons = collect_slices_outer(layer); +#else + // Don't fill in the holes. The user may apply a higher raft_expansion if one wants a better 1st layer adhesion. + overhang_polygons = to_polygons(layer.lslices); +#endif + // Expand for better stability. + contact_polygons = offset(overhang_polygons, scaled(object_config.raft_expansion.value)); + } + else + { + // Generate overhang / contact_polygons for non-raft layers. + const Layer &lower_layer = *layer.lower_layer; + const bool has_enforcer = ! annotations.enforcers_layers.empty() && ! annotations.enforcers_layers[layer_id].empty(); + float fw = 0; + for (LayerRegion *layerm : layer.regions()) { + // Extrusion width accounts for the roundings of the extrudates. + // It is the maximum widh of the extrudate. + 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 < (size_t)object_config.support_material_enforce_layers.value) ? + // Enforce a full possible support, ignore the overhang angle. + 0.f : + (threshold_rad > 0. ? + // Overhang defined by an angle. + float(scale_(lower_layer.height / tan(threshold_rad))) : + // Overhang defined by half the extrusion width. + 0.5f * fw); + // Overhang polygons for this layer and region. + Polygons diff_polygons; + Polygons layerm_polygons = to_polygons(layerm->slices); + if (lower_layer_offset == 0.f) { + // Support everything. + diff_polygons = diff(layerm_polygons, lower_layer_polygons); + if (buildplate_only) { + // Don't support overhangs above the top surfaces. + // This step is done before the contact surface is calculated by growing the overhang region. + diff_polygons = diff(diff_polygons, annotations.buildplate_covered[layer_id]); + } + } else { + if (support_auto) { + // Get the regions needing a suport, collapse very tiny spots. + //FIXME cache the lower layer offset if this layer has multiple regions. +#if 1 + //FIXME this solution will trigger stupid supports for sharp corners, see GH #4874 + diff_polygons = offset2( + diff(layerm_polygons, + offset2(lower_layer_polygons, - 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)), + //FIXME This offset2 is targeted to reduce very thin regions to support, but it may lead to + // no support at all for not so steep overhangs. + - 0.1f * fw, 0.1f * fw); +#else + diff_polygons = + diff(layerm_polygons, + offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); +#endif + if (buildplate_only && ! annotations.buildplate_covered[layer_id].empty()) { + // Don't support overhangs above the top surfaces. + // This step is done before the contact surface is calculated by growing the overhang region. + diff_polygons = diff(diff_polygons, annotations.buildplate_covered[layer_id]); + } + if (! diff_polygons.empty()) { + // Offset the support regions back to a full overhang, restrict them to the full overhang. + // This is done to increase size of the supporting columns below, as they are calculated by + // propagating these contact surfaces downwards. + diff_polygons = diff( + intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), + lower_layer_polygons); + } + } + } + + if (diff_polygons.empty()) + continue; + + // Apply the "support blockers". + if (! annotations.blockers_layers.empty() && ! annotations.blockers_layers[layer_id].empty()) { + // Expand the blocker a bit. Custom blockers produce strips + // spanning just the projection between the two slices. + // Subtracting them as they are may leave unwanted narrow + // residues of diff_polygons that would then be supported. + diff_polygons = diff(diff_polygons, + offset(union_(to_polygons(std::move(annotations.blockers_layers[layer_id]))), float(1000.*SCALED_EPSILON))); + } + + #ifdef SLIC3R_DEBUG + { + ::Slic3r::SVG svg(debug_out_path("support-top-contacts-raw-run%d-layer%d-region%d.svg", + iRun, layer_id, + std::find_if(layer.regions().begin(), layer.regions().end(), [layerm](const LayerRegion* other){return other == layerm;}) - layer.regions().begin()), + get_extents(diff_polygons)); + Slic3r::ExPolygons expolys = union_ex(diff_polygons, false); + svg.draw(expolys); + } + #endif /* SLIC3R_DEBUG */ + + if (object_config.dont_support_bridges) + SupportMaterialInternal::remove_bridges_from_contacts( + print_config, lower_layer, lower_layer_polygons, layerm, fw, diff_polygons); + + if (diff_polygons.empty()) + continue; + + #ifdef SLIC3R_DEBUG + Slic3r::SVG::export_expolygons( + debug_out_path("support-top-contacts-filtered-run%d-layer%d-region%d-z%f.svg", + iRun, layer_id, + std::find_if(layer.regions().begin(), layer.regions().end(), [layerm](const LayerRegion* other){return other == layerm;}) - layer.regions().begin(), + layer.print_z), + union_ex(diff_polygons, false)); + #endif /* SLIC3R_DEBUG */ + + //FIXME the overhang_polygons are used to construct the support towers as well. + //if (this->has_contact_loops()) + // Store the exact contour of the overhang for the contact loops. + polygons_append(overhang_polygons, diff_polygons); + + // Let's define the required contact area by using a max gap of half the upper + // extrusion width and extending the area according to the configured margin. + // We increment the area in steps because we don't want our support to overflow + // on the other side of the object (if it's very thin). + { + //FIMXE 1) Make the offset configurable, 2) Make the Z span configurable. + //FIXME one should trim with the layer span colliding with the support layer, this layer + // may be lower than lower_layer, so the support area needed may need to be actually bigger! + // For the same reason, the non-bridging support area may be smaller than the bridging support area! + float slices_margin_offset = std::min(lower_layer_offset, float(scale_(gap_xy))); + if (slices_margin.offset != slices_margin_offset) { + slices_margin.offset = slices_margin_offset; + slices_margin.polygons = (slices_margin_offset == 0.f) ? + lower_layer_polygons : + offset2(to_polygons(lower_layer.lslices), - no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS); + if (buildplate_only && ! annotations.buildplate_covered[layer_id].empty()) { + if (has_enforcer) + // Make a backup of trimming polygons before enforcing "on build plate only". + slices_margin.all_polygons = slices_margin.polygons; + // Trim the inflated contact surfaces by the top surfaces as well. + slices_margin.polygons = union_(slices_margin.polygons, annotations.buildplate_covered[layer_id]); + } + } + // Offset the contact polygons outside. +#if 0 + for (size_t i = 0; i < NUM_MARGIN_STEPS; ++ i) { + diff_polygons = diff( + offset( + diff_polygons, + scaled(SUPPORT_MATERIAL_MARGIN / NUM_MARGIN_STEPS), + ClipperLib::jtRound, + // round mitter limit + scale_(0.05)), + slices_margin.polygons); + } +#else + diff_polygons = diff(diff_polygons, slices_margin.polygons); +#endif + } + polygons_append(contact_polygons, diff_polygons); + } // for each layer.region + + if (has_enforcer) { + // Enforce supports (as if with 90 degrees of slope) for the regions covered by the enforcer meshes. +#ifdef SLIC3R_DEBUG + ExPolygons enforcers_united = union_ex(to_polygons(annotations.enforcers_layers[layer_id]), false); +#endif // SLIC3R_DEBUG + enforcer_polygons = diff(intersection(to_polygons(layer.lslices), to_polygons(std::move(annotations.enforcers_layers[layer_id]))), + // Inflate just a tiny bit to avoid intersection of the overhang areas with the object. + offset(lower_layer_polygons, 0.05f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)); +#ifdef SLIC3R_DEBUG + SVG::export_expolygons(debug_out_path("support-top-contacts-enforcers-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), + { { layer.lslices, { "layer.lslices", "gray", 0.2f } }, + { { union_ex(lower_layer_polygons, false) }, { "lower_layer_polygons", "green", 0.5f } }, + { enforcers_united, { "enforcers", "blue", 0.5f } }, + { { union_ex(enforcer_polygons, true) }, { "new_contacts", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif /* SLIC3R_DEBUG */ + polygons_append(overhang_polygons, enforcer_polygons); + polygons_append(contact_polygons, diff(enforcer_polygons, slices_margin.all_polygons.empty() ? slices_margin.polygons : slices_margin.all_polygons)); + } + } + + return std::make_tuple(std::move(overhang_polygons), std::move(contact_polygons), std::move(enforcer_polygons), no_interface_offset); +} + +static inline std::pair new_contact_layer( + const PrintConfig &print_config, + const PrintObjectConfig &object_config, + const SlicingParameters &slicing_params, + const Layer &layer, + std::deque &layer_storage, + tbb::spin_mutex &layer_storage_mutex) +{ + double print_z, bottom_z, height; + PrintObjectSupportMaterial::MyLayer* bridging_layer = nullptr; + assert(layer.id() >= slicing_params.raft_layers()); + size_t layer_id = layer.id() - slicing_params.raft_layers(); + + if (layer_id == 0) { + // This is a raft contact layer sitting directly on the print bed. + assert(slicing_params.has_raft()); + print_z = slicing_params.raft_contact_top_z; + bottom_z = slicing_params.raft_interface_top_z; + height = slicing_params.contact_raft_layer_height; + } else if (slicing_params.soluble_interface) { + // Align the contact surface height with a layer immediately below the supported layer. + // Interface layer will be synchronized with the object. + print_z = layer.bottom_z(); + height = layer.lower_layer->height; + bottom_z = (layer_id == 1) ? slicing_params.object_print_z_min : layer.lower_layer->lower_layer->print_z; + } else { + print_z = layer.bottom_z() - slicing_params.gap_support_object; + bottom_z = print_z; + height = 0.; + // Ignore this contact area if it's too low. + // Don't want to print a layer below the first layer height as it may not stick well. + //FIXME there may be a need for a single layer support, then one may decide to print it either as a bottom contact or a top contact + // and it may actually make sense to do it with a thinner layer than the first layer height. + if (print_z < slicing_params.first_print_layer_height - EPSILON) { + // This contact layer is below the first layer height, therefore not printable. Don't support this surface. + return std::pair(nullptr, nullptr); + } else if (print_z < slicing_params.first_print_layer_height + EPSILON) { + // Align the layer with the 1st layer height. + print_z = slicing_params.first_print_layer_height; + bottom_z = 0; + height = slicing_params.first_print_layer_height; + } else { + // Don't know the height of the top contact layer yet. The top contact layer is printed with a normal flow and + // its height will be set adaptively later on. + } + + // Contact layer will be printed with a normal flow, but + // it will support layers printed with a bridging flow. + if (object_config.thick_bridges && SupportMaterialInternal::has_bridging_extrusions(layer)) { + coordf_t bridging_height = 0.; + for (const LayerRegion* region : layer.regions()) + bridging_height += region->region()->bridging_height_avg(print_config); + bridging_height /= coordf_t(layer.regions().size()); + coordf_t bridging_print_z = layer.print_z - bridging_height - slicing_params.gap_support_object; + if (bridging_print_z >= slicing_params.first_print_layer_height - EPSILON) { + // Not below the first layer height means this layer is printable. + if (print_z < slicing_params.first_print_layer_height + EPSILON) { + // Align the layer with the 1st layer height. + bridging_print_z = slicing_params.first_print_layer_height; + } + if (bridging_print_z < print_z - EPSILON) { + // Allocate the new layer. + bridging_layer = &layer_allocate(layer_storage, layer_storage_mutex, PrintObjectSupportMaterial::sltTopContact); + bridging_layer->idx_object_layer_above = layer_id; + bridging_layer->print_z = bridging_print_z; + if (bridging_print_z == slicing_params.first_print_layer_height) { + bridging_layer->bottom_z = 0; + bridging_layer->height = slicing_params.first_print_layer_height; + } + else { + // Don't know the height yet. + bridging_layer->bottom_z = bridging_print_z; + bridging_layer->height = 0; + } + } + } + } + } + + PrintObjectSupportMaterial::MyLayer &new_layer = layer_allocate(layer_storage, layer_storage_mutex, PrintObjectSupportMaterial::sltTopContact); + new_layer.idx_object_layer_above = layer_id; + new_layer.print_z = print_z; + new_layer.bottom_z = bottom_z; + new_layer.height = height; + return std::make_pair(&new_layer, bridging_layer); +} + +static inline void fill_contact_layer( + PrintObjectSupportMaterial::MyLayer &new_layer, + size_t layer_id, + const SlicingParameters &slicing_params, + const PrintObjectConfig &object_config, + const SlicesMarginCache &slices_margin, + const Polygons &overhang_polygons, + const Polygons &contact_polygons, + const Polygons &enforcer_polygons, + const Polygons &lower_layer_polygons, + const Flow &support_material_flow, + float no_interface_offset +#ifdef SLIC3R_DEBUG + , size_t iRun, + const Layer &layer +#endif // SLIC3R_DEBUG + ) +{ + const SupportGridParams grid_params(object_config, support_material_flow); + + Polygons lower_layer_polygons_for_dense_interface_cache; + auto lower_layer_polygons_for_dense_interface = [&lower_layer_polygons_for_dense_interface_cache, &lower_layer_polygons, no_interface_offset]() -> const Polygons& { + if (lower_layer_polygons_for_dense_interface_cache.empty()) + lower_layer_polygons_for_dense_interface_cache = + offset2(lower_layer_polygons, - no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS); + return lower_layer_polygons_for_dense_interface_cache; + }; + + // Stretch support islands into a grid, trim them. + SupportGridPattern support_grid_pattern(&contact_polygons, &slices_margin.polygons, grid_params); + // 1) Contact polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells. + new_layer.contact_polygons = std::make_unique(support_grid_pattern.extract_support(grid_params.expansion_to_propagate, true +#ifdef SLIC3R_DEBUG + , "top_contact_polygons", iRun, layer_id, layer.print_z +#endif // SLIC3R_DEBUG + )); + // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. + if (layer_id == 0 || slicing_params.soluble_interface) { + // if (no_interface_offset == 0.f) { + new_layer.polygons = support_grid_pattern.extract_support(grid_params.expansion_to_slice, true +#ifdef SLIC3R_DEBUG + , "top_contact_polygons2", iRun, layer_id, layer.print_z +#endif // SLIC3R_DEBUG + ); + } else { + // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. + Polygons dense_interface_polygons = diff(overhang_polygons, lower_layer_polygons_for_dense_interface()); + if (! dense_interface_polygons.empty()) { + dense_interface_polygons = + diff( + // Regularize the contour. + offset(dense_interface_polygons, no_interface_offset * 0.1f), + slices_margin.polygons); + // Support islands, to be stretched into a grid. + //FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons, + // thus some dense interface areas may not get supported. Trim the excess with contact_polygons at the following line. + // See for example GH #4874. + Polygons dense_interface_polygons_trimmed = intersection(dense_interface_polygons, *new_layer.contact_polygons); + // Stretch support islands into a grid, trim them. + SupportGridPattern support_grid_pattern(&dense_interface_polygons_trimmed, &slices_margin.polygons, grid_params); + new_layer.polygons = support_grid_pattern.extract_support(grid_params.expansion_to_slice, false +#ifdef SLIC3R_DEBUG + , "top_contact_polygons3", iRun, layer_id, layer.print_z +#endif // SLIC3R_DEBUG + ); + #ifdef SLIC3R_DEBUG + SVG::export_expolygons(debug_out_path("support-top-contacts-final0-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), + { { { union_ex(lower_layer_polygons, false) }, { "lower_layer_polygons", "gray", 0.2f } }, + { { union_ex(*new_layer.contact_polygons, false) }, { "new_layer.contact_polygons", "yellow", 0.5f } }, + { { union_ex(slices_margin.polygons, false) }, { "slices_margin_cached", "blue", 0.5f } }, + { { union_ex(dense_interface_polygons, false) }, { "dense_interface_polygons", "green", 0.5f } }, + { { union_ex(new_layer.polygons, true) }, { "new_layer.polygons", "red", "black", "", scaled(0.1f), 0.5f } } }); + //support_grid_pattern.serialize(debug_out_path("support-top-contacts-final-run%d-layer%d-z%f.bin", iRun, layer_id, layer.print_z)); + SVG::export_expolygons(debug_out_path("support-top-contacts-final0-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), + { { { union_ex(lower_layer_polygons, false) }, { "lower_layer_polygons", "gray", 0.2f } }, + { { union_ex(*new_layer.contact_polygons, false) }, { "new_layer.contact_polygons", "yellow", 0.5f } }, + { { union_ex(contact_polygons, false) }, { "contact_polygons", "blue", 0.5f } }, + { { union_ex(dense_interface_polygons, false) }, { "dense_interface_polygons", "green", 0.5f } }, + { { union_ex(new_layer.polygons, true) }, { "new_layer.polygons", "red", "black", "", scaled(0.1f), 0.5f } } }); + #endif /* SLIC3R_DEBUG */ + } + } + + if (! enforcer_polygons.empty() && ! slices_margin.all_polygons.empty() && layer_id > 0) { + // Support enforcers used together with support enforcers. The support enforcers need to be handled separately from the rest of the support. + + { + SupportGridPattern support_grid_pattern(&enforcer_polygons, &slices_margin.all_polygons, grid_params); + // 1) Contact polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells. + new_layer.enforcer_polygons = std::make_unique(support_grid_pattern.extract_support(grid_params.expansion_to_propagate, true + #ifdef SLIC3R_DEBUG + , "top_contact_polygons4", iRun, layer_id, layer.print_z + #endif // SLIC3R_DEBUG + )); + } + // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. + // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. + Polygons dense_interface_polygons = diff(enforcer_polygons, lower_layer_polygons_for_dense_interface()); + if (! dense_interface_polygons.empty()) { + dense_interface_polygons = + diff( + // Regularize the contour. + offset(dense_interface_polygons, no_interface_offset * 0.1f), + slices_margin.all_polygons); + // Support islands, to be stretched into a grid. + //FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons, + // thus some dense interface areas may not get supported. Trim the excess with contact_polygons at the following line. + // See for example GH #4874. + Polygons dense_interface_polygons_trimmed = intersection(dense_interface_polygons, *new_layer.enforcer_polygons); + SupportGridPattern support_grid_pattern(&dense_interface_polygons_trimmed, &slices_margin.all_polygons, grid_params); + // Extend the polygons to extrude with the contact polygons of support enforcers. + bool needs_union = ! new_layer.polygons.empty(); + append(new_layer.polygons, support_grid_pattern.extract_support(grid_params.expansion_to_slice, false +#ifdef SLIC3R_DEBUG + , "top_contact_polygons5", iRun, layer_id, layer.print_z +#endif // SLIC3R_DEBUG + )); + if (needs_union) + new_layer.polygons = union_(new_layer.polygons); + } + } + +#ifdef SLIC3R_DEBUG + SVG::export_expolygons(debug_out_path("support-top-contacts-final0-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), + { { { union_ex(lower_layer_polygons, false) }, { "lower_layer_polygons", "gray", 0.2f } }, + { { union_ex(*new_layer.contact_polygons, false) }, { "new_layer.contact_polygons", "yellow", 0.5f } }, + { { union_ex(contact_polygons, false) }, { "contact_polygons", "blue", 0.5f } }, + { { union_ex(overhang_polygons, false) }, { "overhang_polygons", "green", 0.5f } }, + { { union_ex(new_layer.polygons, true) }, { "new_layer.polygons", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif /* SLIC3R_DEBUG */ + + // Even after the contact layer was expanded into a grid, some of the contact islands may be too tiny to be extruded. + // Remove those tiny islands from new_layer.polygons and new_layer.contact_polygons. + + // Store the overhang polygons. + // The overhang polygons are used in the path generator for planning of the contact loops. + // if (this->has_contact_loops()). Compared to "polygons", "overhang_polygons" are snug. + new_layer.overhang_polygons = std::make_unique(std::move(overhang_polygons)); + if (! enforcer_polygons.empty()) + new_layer.enforcer_polygons = std::make_unique(std::move(enforcer_polygons)); +} + +// Merge close contact layers conservatively: If two layers are closer than the minimum allowed print layer height (the min_layer_height parameter), +// the top contact layer is merged into the bottom contact layer. +static void merge_contact_layers(const SlicingParameters &slicing_params, double support_layer_height_min, PrintObjectSupportMaterial::MyLayersPtr &layers) +{ + // Sort the layers, as one layer may produce bridging and non-bridging contact layers with different print_z. + std::sort(layers.begin(), layers.end(), [](const PrintObjectSupportMaterial::MyLayer *l1, const PrintObjectSupportMaterial::MyLayer *l2) { return l1->print_z < l2->print_z; }); + + int i = 0; + int k = 0; + { + // Find the span of layers, which are to be printed at the first layer height. + int j = 0; + for (; j < (int)layers.size() && layers[j]->print_z < slicing_params.first_print_layer_height + support_layer_height_min - EPSILON; ++ j); + if (j > 0) { + // Merge the layers layers (0) to (j - 1) into the layers[0]. + PrintObjectSupportMaterial::MyLayer &dst = *layers.front(); + for (int u = 1; u < j; ++ u) + dst.merge(std::move(*layers[u])); + // Snap the first layer to the 1st layer height. + dst.print_z = slicing_params.first_print_layer_height; + dst.height = slicing_params.first_print_layer_height; + dst.bottom_z = 0; + ++ k; + } + i = j; + } + for (; i < int(layers.size()); ++ k) { + // Find the span of layers closer than m_support_layer_height_min. + int j = i + 1; + coordf_t zmax = layers[i]->print_z + support_layer_height_min + EPSILON; + for (; j < (int)layers.size() && layers[j]->print_z < zmax; ++ j) ; + if (i + 1 < j) { + // Merge the layers layers (i + 1) to (j - 1) into the layers[i]. + PrintObjectSupportMaterial::MyLayer &dst = *layers[i]; + for (int u = i + 1; u < j; ++ u) + dst.merge(std::move(*layers[u])); + } + if (k < i) + layers[k] = layers[i]; + i = j; + } + if (k < (int)layers.size()) + layers.erase(layers.begin() + k, layers.end()); +} + +// Generate top contact layers supporting overhangs. +// For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined. +// If supports over bed surface only are requested, don't generate contact layers over an object. +PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_layers( + const PrintObject &object, const std::vector &buildplate_covered, MyLayerStorage &layer_storage) const +{ +#ifdef SLIC3R_DEBUG + static int iRun = 0; + ++ iRun; + #define SLIC3R_IRUN , iRun +#endif /* SLIC3R_DEBUG */ + + // Slice support enforcers / support blockers. + SupportAnnotations annotations(object, buildplate_covered); + + // Output layers, sorted by top Z. + MyLayersPtr contact_out; BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() in parallel - start"; // Determine top contact areas. @@ -1361,435 +1893,282 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ contact_out.assign(num_layers * 2, nullptr); tbb::spin_mutex layer_storage_mutex; tbb::parallel_for(tbb::blocked_range(this->has_raft() ? 0 : 1, num_layers), - [this, &object, &buildplate_covered, &enforcers, &blockers, support_auto, threshold_rad, &layer_storage, &layer_storage_mutex, &contact_out] + [this, &object, &annotations, &layer_storage, &layer_storage_mutex, &contact_out] (const tbb::blocked_range& range) { for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { - const Layer &layer = *object.layers()[layer_id]; + const Layer &layer = *object.layers()[layer_id]; + Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers()[layer_id - 1]->lslices); + SlicesMarginCache slices_margin; - // Detect overhangs and contact areas needed to support them. - // Collect overhangs and contacts of all regions of this layer supported by the layer immediately below. - Polygons overhang_polygons; - Polygons contact_polygons; - Polygons slices_margin_cached; - float slices_margin_cached_offset = -1.; - Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers()[layer_id-1]->lslices); - // Offset of the lower layer, to trim the support polygons with to calculate dense supports. - float no_interface_offset = 0.f; - if (layer_id == 0) { - // This is the first object layer, so the object is being printed on a raft and - // we're here just to get the object footprint for the raft. -#if 0 - // The following line was filling excessive holes in the raft, see GH #430 - overhang_polygons = collect_slices_outer(layer); -#else - // Don't fill in the holes. The user may apply a higher raft_expansion if one wants a better 1st layer adhesion. - overhang_polygons = to_polygons(layer.lslices); -#endif - // Expand for better stability. - contact_polygons = offset(overhang_polygons, scaled(m_object_config->raft_expansion.value)); - } else { - // Generate overhang / contact_polygons for non-raft layers. - const Layer &lower_layer = *object.layers()[layer_id-1]; - for (LayerRegion *layerm : layer.regions()) { - // Extrusion width accounts for the roundings of the extrudates. - // It is the maximum widh of the extrudate. - 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 < (size_t)m_object_config->support_material_enforce_layers.value) ? - // Enforce a full possible support, ignore the overhang angle. - 0.f : - (threshold_rad > 0. ? - // Overhang defined by an angle. - float(scale_(lower_layer.height / tan(threshold_rad))) : - // Overhang defined by half the extrusion width. - 0.5f * fw); - // Overhang polygons for this layer and region. - Polygons diff_polygons; - Polygons layerm_polygons = to_polygons(layerm->slices); - if (lower_layer_offset == 0.f) { - // Support everything. - diff_polygons = diff(layerm_polygons, lower_layer_polygons); - if (! buildplate_covered.empty()) { - // Don't support overhangs above the top surfaces. - // This step is done before the contact surface is calculated by growing the overhang region. - diff_polygons = diff(diff_polygons, buildplate_covered[layer_id]); - } - } else { - if (support_auto) { - // Get the regions needing a suport, collapse very tiny spots. - //FIXME cache the lower layer offset if this layer has multiple regions. - #if 1 - //FIXME this solution will trigger stupid supports for sharp corners, see GH #4874 - diff_polygons = offset2( - diff(layerm_polygons, - offset2(lower_layer_polygons, - 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)), - //FIXME This offset2 is targeted to reduce very thin regions to support, but it may lead to - // no support at all for not so steep overhangs. - - 0.1f * fw, 0.1f * fw); - #else - diff_polygons = - diff(layerm_polygons, - offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - #endif - if (! buildplate_covered.empty()) { - // Don't support overhangs above the top surfaces. - // This step is done before the contact surface is calculated by growing the overhang region. - diff_polygons = diff(diff_polygons, buildplate_covered[layer_id]); - } - if (! diff_polygons.empty()) { - // Offset the support regions back to a full overhang, restrict them to the full overhang. - // This is done to increase size of the supporting columns below, as they are calculated by - // propagating these contact surfaces downwards. - diff_polygons = diff( - intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), - lower_layer_polygons); - } - } - if (! enforcers.empty()) { - // Apply the "support enforcers". - //FIXME add the "enforcers" to the sparse support regions only. - const ExPolygons &enforcer = enforcers[layer_id]; - if (! enforcer.empty()) { - // Enforce supports (as if with 90 degrees of slope) for the regions covered by the enforcer meshes. -#ifdef SLIC3R_DEBUG - ExPolygons enforcers_united = union_ex(to_polygons(enforcer), false); -#endif // SLIC3R_DEBUG - Polygons new_contacts = diff(intersection(layerm_polygons, to_polygons(std::move(enforcer))), - offset(lower_layer_polygons, 0.05f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)); -#ifdef SLIC3R_DEBUG - SVG::export_expolygons(debug_out_path("support-top-contacts-enforcers-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), - { { { union_ex(layerm_polygons, false) }, { "layerm_polygons", "gray", 0.2f } }, - { { union_ex(lower_layer_polygons, false) }, { "lower_layer_polygons", "green", 0.5f } }, - { enforcers_united, { "enforcers", "blue", 0.5f } }, - { { union_ex(new_contacts, true) }, { "new_contacts", "red", "black", "", scaled(0.1f), 0.5f } } }); -#endif /* SLIC3R_DEBUG */ - if (! new_contacts.empty()) { - if (diff_polygons.empty()) - diff_polygons = std::move(new_contacts); - else - diff_polygons = union_(diff_polygons, new_contacts); - } - } - } - } + auto [overhang_polygons, contact_polygons, enforcer_polygons, no_interface_offset] = + detect_overhangs(layer, layer_id, lower_layer_polygons, *m_print_config, *m_object_config, annotations, slices_margin, m_support_params.gap_xy + #ifdef SLIC3R_DEBUG + , iRun + #endif // SLIC3R_DEBUG + ); - if (diff_polygons.empty()) - continue; - - // Apply the "support blockers". - if (! blockers.empty() && ! blockers[layer_id].empty()) { - // Expand the blocker a bit. Custom blockers produce strips - // spanning just the projection between the two slices. - // Subtracting them as they are may leave unwanted narrow - // residues of diff_polygons that would then be supported. - diff_polygons = diff(diff_polygons, - offset(union_(to_polygons(std::move(blockers[layer_id]))), float(1000.*SCALED_EPSILON))); - } - - #ifdef SLIC3R_DEBUG - { - ::Slic3r::SVG svg(debug_out_path("support-top-contacts-raw-run%d-layer%d-region%d.svg", - iRun, layer_id, - std::find_if(layer.regions().begin(), layer.regions().end(), [layerm](const LayerRegion* other){return other == layerm;}) - layer.regions().begin()), - get_extents(diff_polygons)); - Slic3r::ExPolygons expolys = union_ex(diff_polygons, false); - svg.draw(expolys); - } - #endif /* SLIC3R_DEBUG */ - - if (this->m_object_config->dont_support_bridges) - SupportMaterialInternal::remove_bridges_from_contacts( - *m_print_config, lower_layer, lower_layer_polygons, layerm, fw, diff_polygons); - - if (diff_polygons.empty()) - continue; - - #ifdef SLIC3R_DEBUG - Slic3r::SVG::export_expolygons( - debug_out_path("support-top-contacts-filtered-run%d-layer%d-region%d-z%f.svg", - iRun, layer_id, - std::find_if(layer.regions().begin(), layer.regions().end(), [layerm](const LayerRegion* other){return other == layerm;}) - layer.regions().begin(), - layer.print_z), - union_ex(diff_polygons, false)); - #endif /* SLIC3R_DEBUG */ - - //FIXME the overhang_polygons are used to construct the support towers as well. - //if (this->has_contact_loops()) - // Store the exact contour of the overhang for the contact loops. - polygons_append(overhang_polygons, diff_polygons); - - // Let's define the required contact area by using a max gap of half the upper - // extrusion width and extending the area according to the configured margin. - // We increment the area in steps because we don't want our support to overflow - // on the other side of the object (if it's very thin). - { - //FIMXE 1) Make the offset configurable, 2) Make the Z span configurable. - //FIXME one should trim with the layer span colliding with the support layer, this layer - // may be lower than lower_layer, so the support area needed may need to be actually bigger! - // For the same reason, the non-bridging support area may be smaller than the bridging support area! - float slices_margin_offset = std::min(lower_layer_offset, float(scale_(m_gap_xy))); - if (slices_margin_cached_offset != slices_margin_offset) { - slices_margin_cached_offset = slices_margin_offset; - slices_margin_cached = (slices_margin_offset == 0.f) ? - lower_layer_polygons : - offset2(to_polygons(lower_layer.lslices), - no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS); - if (! buildplate_covered.empty()) { - // Trim the inflated contact surfaces by the top surfaces as well. - polygons_append(slices_margin_cached, buildplate_covered[layer_id]); - slices_margin_cached = union_(slices_margin_cached); - } - } - // Offset the contact polygons outside. - for (size_t i = 0; i < NUM_MARGIN_STEPS; ++ i) { - diff_polygons = diff( - offset( - diff_polygons, - SUPPORT_MATERIAL_MARGIN / NUM_MARGIN_STEPS, - ClipperLib::jtRound, - // round mitter limit - scale_(0.05)), - slices_margin_cached); - } - } - polygons_append(contact_polygons, diff_polygons); - } // for each layer.region - } // end of Generate overhang/contact_polygons for non-raft layers. - // Now apply the contact areas to the layer where they need to be made. if (! contact_polygons.empty()) { - MyLayer &new_layer = layer_allocate(layer_storage, layer_storage_mutex, sltTopContact); - new_layer.idx_object_layer_above = layer_id; - MyLayer *bridging_layer = nullptr; - if (layer_id == 0) { - // This is a raft contact layer sitting directly on the print bed. - assert(this->has_raft()); - new_layer.print_z = m_slicing_params.raft_contact_top_z; - new_layer.bottom_z = m_slicing_params.raft_interface_top_z; - new_layer.height = m_slicing_params.contact_raft_layer_height; - } else if (m_slicing_params.soluble_interface) { - // Align the contact surface height with a layer immediately below the supported layer. - // Interface layer will be synchronized with the object. - new_layer.print_z = layer.print_z - layer.height; - new_layer.height = object.layers()[layer_id - 1]->height; - new_layer.bottom_z = (layer_id == 1) ? m_slicing_params.object_print_z_min : object.layers()[layer_id - 2]->print_z; - } else { - new_layer.print_z = layer.print_z - layer.height - m_object_config->support_material_contact_distance; - new_layer.bottom_z = new_layer.print_z; - new_layer.height = 0.; - // Ignore this contact area if it's too low. - // Don't want to print a layer below the first layer height as it may not stick well. - //FIXME there may be a need for a single layer support, then one may decide to print it either as a bottom contact or a top contact - // and it may actually make sense to do it with a thinner layer than the first layer height. - if (new_layer.print_z < m_slicing_params.first_print_layer_height - EPSILON) { - // This contact layer is below the first layer height, therefore not printable. Don't support this surface. - continue; - } else if (new_layer.print_z < m_slicing_params.first_print_layer_height + EPSILON) { - // Align the layer with the 1st layer height. - new_layer.print_z = m_slicing_params.first_print_layer_height; - new_layer.bottom_z = 0; - new_layer.height = m_slicing_params.first_print_layer_height; - } else { - // Don't know the height of the top contact layer yet. The top contact layer is printed with a normal flow and - // its height will be set adaptively later on. - } - - // Contact layer will be printed with a normal flow, but - // it will support layers printed with a bridging flow. - if (SupportMaterialInternal::has_bridging_extrusions(layer)) { - coordf_t bridging_height = 0.; - for (const LayerRegion *region : layer.regions()) - bridging_height += region->region()->bridging_height_avg(*m_print_config); - bridging_height /= coordf_t(layer.regions().size()); - coordf_t bridging_print_z = layer.print_z - bridging_height - m_object_config->support_material_contact_distance; - if (bridging_print_z >= m_slicing_params.first_print_layer_height - EPSILON) { - // Not below the first layer height means this layer is printable. - if (new_layer.print_z < m_slicing_params.first_print_layer_height + EPSILON) { - // Align the layer with the 1st layer height. - bridging_print_z = m_slicing_params.first_print_layer_height; - } - if (bridging_print_z < new_layer.print_z - EPSILON) { - // Allocate the new layer. - bridging_layer = &layer_allocate(layer_storage, layer_storage_mutex, sltTopContact); - bridging_layer->idx_object_layer_above = layer_id; - bridging_layer->print_z = bridging_print_z; - if (bridging_print_z == m_slicing_params.first_print_layer_height) { - bridging_layer->bottom_z = 0; - bridging_layer->height = m_slicing_params.first_print_layer_height; - } else { - // Don't know the height yet. - bridging_layer->bottom_z = bridging_print_z; - bridging_layer->height = 0; - } - } - } - } - } - - // Achtung! The contact_polygons need to be trimmed by slices_margin_cached, otherwise - // the selection by island_samples (see the SupportGridPattern::island_samples() method) will not work! - SupportGridPattern support_grid_pattern( - // Support islands, to be stretched into a grid. - contact_polygons, - // Trimming polygons, to trim the stretched support islands. - slices_margin_cached, - // Grid resolution. - m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), - Geometry::deg2rad(m_object_config->support_material_angle.value), - m_support_material_flow.spacing()); - // 1) Contact polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells. - new_layer.contact_polygons = new Polygons(support_grid_pattern.extract_support(-3, true)); - // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. - if (layer_id == 0 || m_slicing_params.soluble_interface) { - // if (no_interface_offset == 0.f) { - new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5, true); - } else { - // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. - Polygons dense_interface_polygons = diff(overhang_polygons, - offset2(lower_layer_polygons, - no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS)); - if (! dense_interface_polygons.empty()) { - dense_interface_polygons = - // Achtung! The dense_interface_polygons need to be trimmed by slices_margin_cached, otherwise - // the selection by island_samples (see the SupportGridPattern::island_samples() method) will not work! - diff( - // Regularize the contour. - offset(dense_interface_polygons, no_interface_offset * 0.1f), - slices_margin_cached); - SupportGridPattern support_grid_pattern( - // Support islands, to be stretched into a grid. - //FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons, - // thus some dense interface areas may not get supported. Trim the excess with contact_polygons at the following line. - // See for example GH #4874. - intersection(dense_interface_polygons, *new_layer.contact_polygons), - // Trimming polygons, to trim the stretched support islands. - slices_margin_cached, - // Grid resolution. - m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), - Geometry::deg2rad(m_object_config->support_material_angle.value), - m_support_material_flow.spacing()); - new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5, false); + auto [new_layer, bridging_layer] = new_contact_layer(*m_print_config, *m_object_config, m_slicing_params, layer, layer_storage, layer_storage_mutex); + if (new_layer) { + fill_contact_layer(*new_layer, layer_id, m_slicing_params, + *m_object_config, slices_margin, overhang_polygons, contact_polygons, enforcer_polygons, lower_layer_polygons, + m_support_params.support_material_flow, no_interface_offset #ifdef SLIC3R_DEBUG - SVG::export_expolygons(debug_out_path("support-top-contacts-final0-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), - { { { union_ex(lower_layer_polygons, false) }, { "lower_layer_polygons", "gray", 0.2f } }, - { { union_ex(*new_layer.contact_polygons, false) }, { "new_layer.contact_polygons", "yellow", 0.5f } }, - { { union_ex(slices_margin_cached, false) }, { "slices_margin_cached", "blue", 0.5f } }, - { { union_ex(dense_interface_polygons, false) }, { "dense_interface_polygons", "green", 0.5f } }, - { { union_ex(new_layer.polygons, true) }, { "new_layer.polygons", "red", "black", "", scaled(0.1f), 0.5f } } }); - //support_grid_pattern.serialize(debug_out_path("support-top-contacts-final-run%d-layer%d-z%f.bin", iRun, layer_id, layer.print_z)); - SVG::export_expolygons(debug_out_path("support-top-contacts-final0-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), - { { { union_ex(lower_layer_polygons, false) }, { "lower_layer_polygons", "gray", 0.2f } }, - { { union_ex(*new_layer.contact_polygons, false) }, { "new_layer.contact_polygons", "yellow", 0.5f } }, - { { union_ex(contact_polygons, false) }, { "contact_polygons", "blue", 0.5f } }, - { { union_ex(dense_interface_polygons, false) }, { "dense_interface_polygons", "green", 0.5f } }, - { { union_ex(new_layer.polygons, true) }, { "new_layer.polygons", "red", "black", "", scaled(0.1f), 0.5f } } }); - #endif /* SLIC3R_DEBUG */ + , iRun, layer + #endif // SLIC3R_DEBUG + ); + contact_out[layer_id * 2] = new_layer; + if (bridging_layer != nullptr) { + bridging_layer->polygons = new_layer->polygons; + bridging_layer->contact_polygons = std::make_unique(*new_layer->contact_polygons); + bridging_layer->overhang_polygons = std::make_unique(*new_layer->overhang_polygons); + if (new_layer->enforcer_polygons) + bridging_layer->enforcer_polygons = std::make_unique(*new_layer->enforcer_polygons); + contact_out[layer_id * 2 + 1] = bridging_layer; } } - #ifdef SLIC3R_DEBUG - SVG::export_expolygons(debug_out_path("support-top-contacts-final0-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), - { { { union_ex(lower_layer_polygons, false) }, { "lower_layer_polygons", "gray", 0.2f } }, - { { union_ex(*new_layer.contact_polygons, false) }, { "new_layer.contact_polygons", "yellow", 0.5f } }, - { { union_ex(contact_polygons, false) }, { "contact_polygons", "blue", 0.5f } }, - { { union_ex(overhang_polygons, false) }, { "overhang_polygons", "green", 0.5f } }, - { { union_ex(new_layer.polygons, true) }, { "new_layer.polygons", "red", "black", "", scaled(0.1f), 0.5f } } }); - #endif /* SLIC3R_DEBUG */ - - // Even after the contact layer was expanded into a grid, some of the contact islands may be too tiny to be extruded. - // Remove those tiny islands from new_layer.polygons and new_layer.contact_polygons. - - // Store the overhang polygons. - // The overhang polygons are used in the path generator for planning of the contact loops. - // if (this->has_contact_loops()). Compared to "polygons", "overhang_polygons" are snug. - new_layer.overhang_polygons = new Polygons(std::move(overhang_polygons)); - contact_out[layer_id * 2] = &new_layer; - if (bridging_layer != nullptr) { - bridging_layer->polygons = new_layer.polygons; - bridging_layer->contact_polygons = new Polygons(*new_layer.contact_polygons); - bridging_layer->overhang_polygons = new Polygons(*new_layer.overhang_polygons); - contact_out[layer_id * 2 + 1] = bridging_layer; - } } } }); // Compress contact_out, remove the nullptr items. remove_nulls(contact_out); - // Sort the layers, as one layer may produce bridging and non-bridging contact layers with different print_z. - std::sort(contact_out.begin(), contact_out.end(), [](const MyLayer *l1, const MyLayer *l2) { return l1->print_z < l2->print_z; }); - // Merge close contact layers conservatively: If two layers are closer than the minimum allowed print layer height (the min_layer_height parameter), - // the top contact layer is merged into the bottom contact layer. - { - int i = 0; - int k = 0; - { - // Find the span of layers, which are to be printed at the first layer height. - int j = 0; - 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(); - for (int u = 1; u < j; ++ u) { - MyLayer &src = *contact_out[u]; - // The union_() does not support move semantic yet, but maybe one day it will. - dst.polygons = union_(dst.polygons, std::move(src.polygons)); - *dst.contact_polygons = union_(*dst.contact_polygons, std::move(*src.contact_polygons)); - *dst.overhang_polygons = union_(*dst.overhang_polygons, std::move(*src.overhang_polygons)); - // Source polygon is no more needed, it will not be refrenced. Release its data. - src.reset(); - } - // Snap the first layer to the 1st layer height. - dst.print_z = m_slicing_params.first_print_layer_height; - dst.height = m_slicing_params.first_print_layer_height; - dst.bottom_z = 0; - ++ k; - } - i = j; - } - for (; i < int(contact_out.size()); ++ k) { - // 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 < (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]; - for (int u = i + 1; u < j; ++ u) { - MyLayer &src = *contact_out[u]; - // The union_() does not support move semantic yet, but maybe one day it will. - dst.polygons = union_(dst.polygons, std::move(src.polygons)); - *dst.contact_polygons = union_(*dst.contact_polygons, std::move(*src.contact_polygons)); - *dst.overhang_polygons = union_(*dst.overhang_polygons, std::move(*src.overhang_polygons)); - // Source polygon is no more needed, it will not be refrenced. Release its data. - src.reset(); - } - } - if (k < i) - contact_out[k] = contact_out[i]; - i = j; - } - if (k < (int)contact_out.size()) - contact_out.erase(contact_out.begin() + k, contact_out.end()); - } + merge_contact_layers(m_slicing_params, m_support_params.support_layer_height_min, contact_out); BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() in parallel - end"; return contact_out; } +// Find the bottom contact layers above the top surfaces of this layer. +static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts( + const SlicingParameters &slicing_params, + const PrintObjectSupportMaterial::SupportParams &support_params, + const PrintObject &object, + const Layer &layer, + // Existing top contact layers, to which this newly created bottom contact layer will be snapped to guarantee a minimum layer height. + const PrintObjectSupportMaterial::MyLayersPtr &top_contacts, + // First top contact layer index overlapping with this new bottom interface layer. + size_t contact_idx, + // To allocate a new layer from. + std::deque &layer_storage, + // To trim the support areas above this bottom interface layer with this newly created bottom interface layer. + std::vector &layer_support_areas, + // Support areas projected from top to bottom, starting with top support interfaces. + const Polygons &supports_projected +#ifdef SLIC3R_DEBUG + , size_t iRun + , const Polygons &polygons_new +#endif // SLIC3R_DEBUG + ) +{ + Polygons top = collect_region_slices_by_type(layer, stTop); +#ifdef SLIC3R_DEBUG + SVG::export_expolygons(debug_out_path("support-bottom-layers-raw-%d-%lf.svg", iRun, layer.print_z), + { { { union_ex(top, false) }, { "top", "blue", 0.5f } }, + { { union_ex(supports_projected, true) }, { "overhangs", "magenta", 0.5f } }, + { layer.lslices, { "layer.lslices", "green", 0.5f } }, + { { union_ex(polygons_new, true) }, { "polygons_new", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif /* SLIC3R_DEBUG */ + + // Now find whether any projection of the contact surfaces above layer.print_z not yet supported by any + // top surfaces above layer.print_z falls onto this top surface. + // Touching are the contact surfaces supported exclusively by this top surfaces. + // Don't use a safety offset as it has been applied during insertion of polygons. + if (top.empty()) + return nullptr; + + Polygons touching = intersection(top, supports_projected, false); + if (touching.empty()) + return nullptr; + + assert(layer.id() >= slicing_params.raft_layers()); + size_t layer_id = layer.id() - slicing_params.raft_layers(); + + // Allocate a new bottom contact layer. + PrintObjectSupportMaterial::MyLayer &layer_new = layer_allocate(layer_storage, PrintObjectSupportMaterial::sltBottomContact); + // Grow top surfaces so that interface and support generation are generated + // with some spacing from object - it looks we don't need the actual + // top shapes so this can be done here + //FIXME calculate layer height based on the actual thickness of the layer: + // If the layer is extruded with no bridging flow, support just the normal extrusions. + layer_new.height = slicing_params.soluble_interface ? + // Align the interface layer with the object's layer height. + layer.upper_layer->height : + // Place a bridge flow interface layer or the normal flow interface layer over the top surface. + support_params.support_material_bottom_interface_flow.height(); + layer_new.print_z = slicing_params.soluble_interface ? layer.upper_layer->print_z : + layer.print_z + layer_new.height + slicing_params.gap_object_support; + layer_new.bottom_z = layer.print_z; + layer_new.idx_object_layer_below = layer_id; + layer_new.bridging = !slicing_params.soluble_interface && object.config().thick_bridges; + //FIXME how much to inflate the bottom surface, as it is being extruded with a bridging flow? The following line uses a normal flow. + //FIXME why is the offset positive? It will be trimmed by the object later on anyway, but then it just wastes CPU clocks. + layer_new.polygons = offset(touching, float(support_params.support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS); + + if (! slicing_params.soluble_interface) { + // Walk the top surfaces, snap the top of the new bottom surface to the closest top of the top surface, + // so there will be no support surfaces generated with thickness lower than m_support_layer_height_min. + for (size_t top_idx = size_t(std::max(0, contact_idx)); + top_idx < top_contacts.size() && top_contacts[top_idx]->print_z < layer_new.print_z + support_params.support_layer_height_min + EPSILON; + ++ top_idx) { + if (top_contacts[top_idx]->print_z > layer_new.print_z - support_params.support_layer_height_min - EPSILON) { + // A top layer has been found, which is close to the new bottom layer. + coordf_t diff = layer_new.print_z - top_contacts[top_idx]->print_z; + assert(std::abs(diff) <= support_params.support_layer_height_min + EPSILON); + if (diff > 0.) { + // The top contact layer is below this layer. Make the bridging layer thinner to align with the existing top layer. + assert(diff < layer_new.height + EPSILON); + assert(layer_new.height - diff >= support_params.support_layer_height_min - EPSILON); + layer_new.print_z = top_contacts[top_idx]->print_z; + layer_new.height -= diff; + } + else { + // The top contact layer is above this layer. One may either make this layer thicker or thinner. + // By making the layer thicker, one will decrease the number of discrete layers with the price of extruding a bit too thick bridges. + // By making the layer thinner, one adds one more discrete layer. + layer_new.print_z = top_contacts[top_idx]->print_z; + layer_new.height -= diff; + } + break; + } + } + } + +#ifdef SLIC3R_DEBUG + Slic3r::SVG::export_expolygons( + debug_out_path("support-bottom-contacts-%d-%lf.svg", iRun, layer_new.print_z), + union_ex(layer_new.polygons, false)); +#endif /* SLIC3R_DEBUG */ + + // Trim the already created base layers above the current layer intersecting with the new bottom contacts layer. + //FIXME Maybe this is no more needed, as the overlapping base layers are trimmed by the bottom layers at the final stage? + touching = offset(touching, float(SCALED_EPSILON)); + for (int layer_id_above = layer_id + 1; layer_id_above < int(object.total_layer_count()); ++layer_id_above) { + const Layer &layer_above = *object.layers()[layer_id_above]; + if (layer_above.print_z > layer_new.print_z - EPSILON) + break; + if (! layer_support_areas[layer_id_above].empty()) { +#ifdef SLIC3R_DEBUG + SVG::export_expolygons(debug_out_path("support-support-areas-raw-before-trimming-%d-with-%f-%lf.svg", iRun, layer.print_z, layer_above.print_z), + { { { union_ex(touching, false) }, { "touching", "blue", 0.5f } }, + { { union_ex(layer_support_areas[layer_id_above], true) }, { "above", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif /* SLIC3R_DEBUG */ + layer_support_areas[layer_id_above] = diff(layer_support_areas[layer_id_above], touching); +#ifdef SLIC3R_DEBUG + Slic3r::SVG::export_expolygons( + debug_out_path("support-support-areas-raw-after-trimming-%d-with-%f-%lf.svg", iRun, layer.print_z, layer_above.print_z), + union_ex(layer_support_areas[layer_id_above], false)); +#endif /* SLIC3R_DEBUG */ + } + } + + return &layer_new; +} + +// Returns polygons to print + polygons to propagate downwards. +// Called twice: First for normal supports, possibly trimmed by "on build plate only", second for support enforcers not trimmed by "on build plate only". +static inline std::pair project_support_to_grid(const Layer &layer, const SupportGridParams &grid_params, const Polygons &overhangs, Polygons *layer_buildplate_covered +#ifdef SLIC3R_DEBUG + , size_t iRun, size_t layer_id, const char *debug_name +#endif /* SLIC3R_DEBUG */ +) +{ + // Remove the areas that touched from the projection that will continue on next, lower, top surfaces. +// Polygons trimming = union_(to_polygons(layer.slices), touching, true); + Polygons trimming = layer_buildplate_covered ? std::move(*layer_buildplate_covered) : offset(layer.lslices, float(SCALED_EPSILON)); + Polygons overhangs_projection = diff(overhangs, trimming, false); + +#ifdef SLIC3R_DEBUG + SVG::export_expolygons(debug_out_path("support-support-areas-%s-raw-%d-%lf.svg", debug_name, iRun, layer.print_z), + { { { union_ex(trimming, false) }, { "trimming", "blue", 0.5f } }, + { { union_ex(overhangs_projection, true) }, { "overhangs_projection", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif /* SLIC3R_DEBUG */ + + remove_sticks(overhangs_projection); + remove_degenerate(overhangs_projection); + +#ifdef SLIC3R_DEBUG + SVG::export_expolygons(debug_out_path("support-support-areas-%s-raw-cleaned-%d-%lf.svg", debug_name, iRun, layer.print_z), + { { { union_ex(trimming, false) }, { "trimming", "blue", 0.5f } }, + { { union_ex(overhangs_projection, false) }, { "overhangs_projection", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif /* SLIC3R_DEBUG */ + + SupportGridPattern support_grid_pattern(&overhangs_projection, &trimming, grid_params); + tbb::task_group task_group_inner; + + std::pair out; + + // 1) Cache the slice of a support volume. The support volume is expanded by 1/2 of support material flow spacing + // to allow a placement of suppot zig-zag snake along the grid lines. + task_group_inner.run([&grid_params, &support_grid_pattern, &out +#ifdef SLIC3R_DEBUG + , &layer, layer_id, iRun, debug_name +#endif /* SLIC3R_DEBUG */ + ] { + out.first = support_grid_pattern.extract_support(grid_params.expansion_to_slice, true +#ifdef SLIC3R_DEBUG + , (std::string(debug_name) + "_support_area").c_str(), iRun, layer_id, layer.print_z +#endif // SLIC3R_DEBUG + ); +#ifdef SLIC3R_DEBUG + Slic3r::SVG::export_expolygons( + debug_out_path("support-layer_support_area-gridded-%s-%d-%lf.svg", debug_name, iRun, layer.print_z), + union_ex(out.first, false)); +#endif /* SLIC3R_DEBUG */ + }); + + // 2) Support polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells. + task_group_inner.run([&grid_params, &support_grid_pattern, &out +#ifdef SLIC3R_DEBUG + , &layer, layer_id, &overhangs_projection, &trimming, iRun, debug_name +#endif /* SLIC3R_DEBUG */ + ] { + out.second = support_grid_pattern.extract_support(grid_params.expansion_to_propagate, true +#ifdef SLIC3R_DEBUG + , "support_projection", iRun, layer_id, layer.print_z +#endif // SLIC3R_DEBUG + ); +#ifdef SLIC3R_DEBUG + Slic3r::SVG::export_expolygons( + debug_out_path("support-projection_new-gridded-%d-%lf.svg", iRun, layer.print_z), + union_ex(out.second, false)); +#endif /* SLIC3R_DEBUG */ +#ifdef SLIC3R_DEBUG + SVG::export_expolygons(debug_out_path("support-projection_new-gridded-%d-%lf.svg", iRun, layer.print_z), + { { { union_ex(trimming, false) }, { "trimming", "gray", 0.5f } }, + { { union_ex(overhangs_projection, true) }, { "overhangs_projection", "blue", 0.5f } }, + { { union_ex(out.second, true) }, { "projection_new", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif /* SLIC3R_DEBUG */ + }); + + task_group_inner.wait(); + return out; +} + // Generate bottom contact layers supporting the top contact layers. // For a soluble interface material synchronize the layer heights with the object, // otherwise set the layer height to a bridging flow of a support interface nozzle. PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_layer_support_areas( - const PrintObject &object, const MyLayersPtr &top_contacts, MyLayerStorage &layer_storage, - std::vector &layer_support_areas) const + const PrintObject &object, const MyLayersPtr &top_contacts, std::vector &buildplate_covered, + MyLayerStorage &layer_storage, std::vector &layer_support_areas) const { + if (top_contacts.empty()) + return MyLayersPtr(); + #ifdef SLIC3R_DEBUG - static int iRun = 0; - ++ iRun; + static size_t s_iRun = 0; + size_t iRun = s_iRun ++; #endif /* SLIC3R_DEBUG */ + //FIXME higher expansion_to_slice here? why? + //const auto expansion_to_slice = m_support_material_flow.scaled_spacing() / 2 + 25; + const SupportGridParams grid_params(*m_object_config, m_support_params.support_material_flow); + const bool buildplate_only = ! buildplate_covered.empty(); + // Allocate empty surface areas, one per object layer. layer_support_areas.assign(object.total_layer_count(), Polygons()); @@ -1797,244 +2176,117 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta // we'll use them to clip our support and detect where does it stick MyLayersPtr bottom_contacts; - if (! top_contacts.empty()) - { - // There is some support to be built, if there are non-empty top surfaces detected. - // Sum of unsupported contact areas above the current layer.print_z. - Polygons projection; - // Last top contact layer visited when collecting the projection of contact areas. - int contact_idx = int(top_contacts.size()) - 1; - for (int layer_id = int(object.total_layer_count()) - 2; layer_id >= 0; -- layer_id) { - BOOST_LOG_TRIVIAL(trace) << "Support generator - bottom_contact_layers - layer " << layer_id; - const Layer &layer = *object.get_layer(layer_id); - // Collect projections of all contact areas above or at the same level as this top surface. + // There is some support to be built, if there are non-empty top surfaces detected. + // Sum of unsupported contact areas above the current layer.print_z. + Polygons overhangs_projection; + // Sum of unsupported enforcer contact areas above the current layer.print_z. + // Only used if "supports on build plate only" is enabled and both automatic and support enforcers are enabled. + Polygons enforcers_projection; + // Last top contact layer visited when collecting the projection of contact areas. + int contact_idx = int(top_contacts.size()) - 1; + for (int layer_id = int(object.total_layer_count()) - 2; layer_id >= 0; -- layer_id) { + BOOST_LOG_TRIVIAL(trace) << "Support generator - bottom_contact_layers - layer " << layer_id; + const Layer &layer = *object.get_layer(layer_id); + // Collect projections of all contact areas above or at the same level as this top surface. #ifdef SLIC3R_DEBUG - Polygons polygons_new; + Polygons polygons_new; + Polygons enforcers_new; #endif // SLIC3R_DEBUG - for (; contact_idx >= 0 && top_contacts[contact_idx]->print_z > layer.print_z - EPSILON; -- contact_idx) { + for (; contact_idx >= 0 && top_contacts[contact_idx]->print_z > layer.print_z - EPSILON; -- contact_idx) { + MyLayer &top_contact = *top_contacts[contact_idx]; #ifndef SLIC3R_DEBUG - Polygons polygons_new; + Polygons polygons_new; + Polygons enforcers_new; #endif // SLIC3R_DEBUG - // Contact surfaces are expanded away from the object, trimmed by the object. - // Use a slight positive offset to overlap the touching regions. + // Contact surfaces are expanded away from the object, trimmed by the object. + // Use a slight positive offset to overlap the touching regions. #if 0 - // Merge and collect the contact polygons. The contact polygons are inflated, but not extended into a grid form. - polygons_append(polygons_new, offset(*top_contacts[contact_idx]->contact_polygons, SCALED_EPSILON)); + // Merge and collect the contact polygons. The contact polygons are inflated, but not extended into a grid form. + polygons_append(polygons_new, offset(*top_contact.contact_polygons, SCALED_EPSILON)); + if (top_contact.enforcer_polygons) + polygons_append(enforcers_new, offset(*top_contact.enforcer_polygons, SCALED_EPSILON)); #else - // Consume the contact_polygons. The contact polygons are already expanded into a grid form, and they are a tiny bit smaller - // than the grid cells. - polygons_append(polygons_new, std::move(*top_contacts[contact_idx]->contact_polygons)); + // Consume the contact_polygons. The contact polygons are already expanded into a grid form, and they are a tiny bit smaller + // than the grid cells. + polygons_append(polygons_new, std::move(*top_contact.contact_polygons)); + if (top_contact.enforcer_polygons) + polygons_append(enforcers_new, std::move(*top_contact.enforcer_polygons)); #endif - // These are the overhang surfaces. They are touching the object and they are not expanded away from the object. - // Use a slight positive offset to overlap the touching regions. - polygons_append(polygons_new, offset(*top_contacts[contact_idx]->overhang_polygons, float(SCALED_EPSILON))); - polygons_append(projection, union_(polygons_new)); - } - if (projection.empty()) - continue; - Polygons projection_raw = union_(projection); - - tbb::task_group task_group; - if (! m_object_config->support_material_buildplate_only) - // Find the bottom contact layers above the top surfaces of this layer. - task_group.run([this, &object, &top_contacts, contact_idx, &layer, layer_id, &layer_storage, &layer_support_areas, &bottom_contacts, &projection_raw - #ifdef SLIC3R_DEBUG - , &polygons_new - #endif // SLIC3R_DEBUG - ] { - Polygons top = collect_region_slices_by_type(layer, stTop); - #ifdef SLIC3R_DEBUG - { - BoundingBox bbox = get_extents(projection_raw); - bbox.merge(get_extents(top)); - ::Slic3r::SVG svg(debug_out_path("support-bottom-layers-raw-%d-%lf.svg", iRun, layer.print_z), bbox); - svg.draw(union_ex(top, false), "blue", 0.5f); - svg.draw(union_ex(projection_raw, true), "red", 0.5f); - svg.draw_outline(union_ex(projection_raw, true), "red", "blue", scale_(0.1f)); - svg.draw(layer.lslices, "green", 0.5f); - svg.draw(union_ex(polygons_new, true), "magenta", 0.5f); - svg.draw_outline(union_ex(polygons_new, true), "magenta", "magenta", scale_(0.1f)); - } - #endif /* SLIC3R_DEBUG */ - - // Now find whether any projection of the contact surfaces above layer.print_z not yet supported by any - // top surfaces above layer.print_z falls onto this top surface. - // Touching are the contact surfaces supported exclusively by this top surfaces. - // Don't use a safety offset as it has been applied during insertion of polygons. - if (! top.empty()) { - Polygons touching = intersection(top, projection_raw, false); - if (! touching.empty()) { - // Allocate a new bottom contact layer. - MyLayer &layer_new = layer_allocate(layer_storage, sltBottomContact); - bottom_contacts.push_back(&layer_new); - // Grow top surfaces so that interface and support generation are generated - // with some spacing from object - it looks we don't need the actual - // top shapes so this can be done here - //FIXME calculate layer height based on the actual thickness of the layer: - // If the layer is extruded with no bridging flow, support just the normal extrusions. - layer_new.height = m_slicing_params.soluble_interface ? - // Align the interface layer with the object's layer height. - object.layers()[layer_id + 1]->height : - // Place a bridge flow interface layer over the top surface. - //FIXME Check whether the bottom bridging surfaces are extruded correctly (no bridging flow correction applied?) - // According to Jindrich the bottom surfaces work well. - //FIXME test the bridging flow instead? - m_support_material_interface_flow.nozzle_diameter; - layer_new.print_z = m_slicing_params.soluble_interface ? object.layers()[layer_id + 1]->print_z : - layer.print_z + layer_new.height + m_object_config->support_material_contact_distance.value; - layer_new.bottom_z = layer.print_z; - layer_new.idx_object_layer_below = layer_id; - layer_new.bridging = ! m_slicing_params.soluble_interface; - //FIXME how much to inflate the bottom surface, as it is being extruded with a bridging flow? The following line uses a normal flow. - //FIXME why is the offset positive? It will be trimmed by the object later on anyway, but then it just wastes CPU clocks. - layer_new.polygons = offset(touching, float(m_support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS); - if (! m_slicing_params.soluble_interface) { - // Walk the top surfaces, snap the top of the new bottom surface to the closest top of the top surface, - // so there will be no support surfaces generated with thickness lower than m_support_layer_height_min. - for (size_t top_idx = size_t(std::max(0, contact_idx)); - top_idx < top_contacts.size() && top_contacts[top_idx]->print_z < layer_new.print_z + this->m_support_layer_height_min + EPSILON; - ++ top_idx) { - if (top_contacts[top_idx]->print_z > layer_new.print_z - this->m_support_layer_height_min - EPSILON) { - // A top layer has been found, which is close to the new bottom layer. - coordf_t diff = layer_new.print_z - top_contacts[top_idx]->print_z; - assert(std::abs(diff) <= this->m_support_layer_height_min + EPSILON); - if (diff > 0.) { - // The top contact layer is below this layer. Make the bridging layer thinner to align with the existing top layer. - assert(diff < layer_new.height + EPSILON); - assert(layer_new.height - diff >= m_support_layer_height_min - EPSILON); - layer_new.print_z = top_contacts[top_idx]->print_z; - layer_new.height -= diff; - } else { - // The top contact layer is above this layer. One may either make this layer thicker or thinner. - // By making the layer thicker, one will decrease the number of discrete layers with the price of extruding a bit too thick bridges. - // By making the layer thinner, one adds one more discrete layer. - layer_new.print_z = top_contacts[top_idx]->print_z; - layer_new.height -= diff; - } - break; - } - } - } - #ifdef SLIC3R_DEBUG - Slic3r::SVG::export_expolygons( - debug_out_path("support-bottom-contacts-%d-%lf.svg", iRun, layer_new.print_z), - union_ex(layer_new.polygons, false)); - #endif /* SLIC3R_DEBUG */ - // Trim the already created base layers above the current layer intersecting with the new bottom contacts layer. - //FIXME Maybe this is no more needed, as the overlapping base layers are trimmed by the bottom layers at the final stage? - touching = offset(touching, float(SCALED_EPSILON)); - for (int layer_id_above = layer_id + 1; layer_id_above < int(object.total_layer_count()); ++ layer_id_above) { - const Layer &layer_above = *object.layers()[layer_id_above]; - if (layer_above.print_z > layer_new.print_z - EPSILON) - break; - if (! layer_support_areas[layer_id_above].empty()) { -#ifdef SLIC3R_DEBUG - { - BoundingBox bbox = get_extents(touching); - bbox.merge(get_extents(layer_support_areas[layer_id_above])); - ::Slic3r::SVG svg(debug_out_path("support-support-areas-raw-before-trimming-%d-with-%f-%lf.svg", iRun, layer.print_z, layer_above.print_z), bbox); - svg.draw(union_ex(touching, false), "blue", 0.5f); - svg.draw(union_ex(layer_support_areas[layer_id_above], true), "red", 0.5f); - svg.draw_outline(union_ex(layer_support_areas[layer_id_above], true), "red", "blue", scale_(0.1f)); - } -#endif /* SLIC3R_DEBUG */ - layer_support_areas[layer_id_above] = diff(layer_support_areas[layer_id_above], touching); -#ifdef SLIC3R_DEBUG - Slic3r::SVG::export_expolygons( - debug_out_path("support-support-areas-raw-after-trimming-%d-with-%f-%lf.svg", iRun, layer.print_z, layer_above.print_z), - union_ex(layer_support_areas[layer_id_above], false)); -#endif /* SLIC3R_DEBUG */ - } - } - } - } // ! top.empty() - }); - - Polygons &layer_support_area = layer_support_areas[layer_id]; - task_group.run([this, &projection, &projection_raw, &layer, &layer_support_area] { - // Remove the areas that touched from the projection that will continue on next, lower, top surfaces. - // Polygons trimming = union_(to_polygons(layer.slices), touching, true); - Polygons trimming = offset(layer.lslices, float(SCALED_EPSILON)); - projection = diff(projection_raw, trimming, false); - #ifdef SLIC3R_DEBUG - { - BoundingBox bbox = get_extents(projection_raw); - bbox.merge(get_extents(trimming)); - ::Slic3r::SVG svg(debug_out_path("support-support-areas-raw-%d-%lf.svg", iRun, layer.print_z), bbox); - svg.draw(union_ex(trimming, false), "blue", 0.5f); - svg.draw(union_ex(projection, true), "red", 0.5f); - svg.draw_outline(union_ex(projection, true), "red", "blue", scale_(0.1f)); - } - #endif /* SLIC3R_DEBUG */ - remove_sticks(projection); - remove_degenerate(projection); - #ifdef SLIC3R_DEBUG - Slic3r::SVG::export_expolygons( - debug_out_path("support-support-areas-raw-cleaned-%d-%lf.svg", iRun, layer.print_z), - union_ex(projection, false)); - #endif /* SLIC3R_DEBUG */ - SupportGridPattern support_grid_pattern( - // Support islands, to be stretched into a grid. - projection, - // Trimming polygons, to trim the stretched support islands. - trimming, - // Grid spacing. - m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), - Geometry::deg2rad(m_object_config->support_material_angle.value), - m_support_material_flow.spacing()); - tbb::task_group task_group_inner; - // 1) Cache the slice of a support volume. The support volume is expanded by 1/2 of support material flow spacing - // to allow a placement of suppot zig-zag snake along the grid lines. - task_group_inner.run([this, &support_grid_pattern, &layer_support_area - #ifdef SLIC3R_DEBUG - , &layer - #endif /* SLIC3R_DEBUG */ - ] { - layer_support_area = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 25, true); - #ifdef SLIC3R_DEBUG - Slic3r::SVG::export_expolygons( - debug_out_path("support-layer_support_area-gridded-%d-%lf.svg", iRun, layer.print_z), - union_ex(layer_support_area, false)); - #endif /* SLIC3R_DEBUG */ - }); - // 2) Support polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells. - Polygons projection_new; - task_group_inner.run([&projection_new, &support_grid_pattern - #ifdef SLIC3R_DEBUG - , &layer, &projection, &trimming - #endif /* SLIC3R_DEBUG */ - ] { - projection_new = support_grid_pattern.extract_support(-5, true); - #ifdef SLIC3R_DEBUG - Slic3r::SVG::export_expolygons( - debug_out_path("support-projection_new-gridded-%d-%lf.svg", iRun, layer.print_z), - union_ex(projection_new, false)); - #endif /* SLIC3R_DEBUG */ -#ifdef SLIC3R_DEBUG - { - BoundingBox bbox = get_extents(projection); - bbox.merge(get_extents(projection_new)); - bbox.merge(get_extents(trimming)); - ::Slic3r::SVG svg(debug_out_path("support-projection_new-gridded-%d-%lf.svg", iRun, layer.print_z), bbox); - svg.draw(union_ex(trimming, false), "gray", 0.5f); - svg.draw(union_ex(projection_new, false), "red", 0.5f); - svg.draw(union_ex(projection, false), "blue", 0.5f); - } -#endif /* SLIC3R_DEBUG */ - }); - task_group_inner.wait(); - projection = std::move(projection_new); - }); - task_group.wait(); + // These are the overhang surfaces. They are touching the object and they are not expanded away from the object. + // Use a slight positive offset to overlap the touching regions. + polygons_append(polygons_new, offset(*top_contact.overhang_polygons, float(SCALED_EPSILON))); + polygons_append(overhangs_projection, union_(polygons_new)); + polygons_append(enforcers_projection, enforcers_new); } - std::reverse(bottom_contacts.begin(), bottom_contacts.end()); -// trim_support_layers_by_object(object, bottom_contacts, 0., 0., m_gap_xy); - trim_support_layers_by_object(object, bottom_contacts, - m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, - m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy); + if (overhangs_projection.empty() && enforcers_projection.empty()) + continue; - } // ! top_contacts.empty() + // Overhangs_projection will be filled in asynchronously, move it away. + Polygons overhangs_projection_raw = union_(std::move(overhangs_projection)); + Polygons enforcers_projection_raw = union_(std::move(enforcers_projection)); + tbb::task_group task_group; + const Polygons &overhangs_for_bottom_contacts = buildplate_only ? enforcers_projection_raw : overhangs_projection_raw; + if (! overhangs_for_bottom_contacts.empty()) + // Find the bottom contact layers above the top surfaces of this layer. + task_group.run([this, &object, &layer, &top_contacts, contact_idx, &layer_storage, &layer_support_areas, &bottom_contacts, &overhangs_for_bottom_contacts + #ifdef SLIC3R_DEBUG + , iRun, &polygons_new + #endif // SLIC3R_DEBUG + ] { + // Find the bottom contact layers above the top surfaces of this layer. + MyLayer *layer_new = detect_bottom_contacts( + m_slicing_params, m_support_params, object, layer, top_contacts, contact_idx, layer_storage, layer_support_areas, overhangs_for_bottom_contacts +#ifdef SLIC3R_DEBUG + , iRun, polygons_new +#endif // SLIC3R_DEBUG + ); + if (layer_new) + bottom_contacts.push_back(layer_new); + }); + + Polygons &layer_support_area = layer_support_areas[layer_id]; + Polygons *layer_buildplate_covered = buildplate_covered.empty() ? nullptr : &buildplate_covered[layer_id]; + task_group.run([&grid_params, &overhangs_projection, &overhangs_projection_raw, &layer, &layer_support_area, layer_buildplate_covered +#ifdef SLIC3R_DEBUG + , iRun, layer_id +#endif /* SLIC3R_DEBUG */ + ] { + // buildplate_covered[layer_id] will be consumed here. + std::tie(layer_support_area, overhangs_projection) = project_support_to_grid(layer, grid_params, overhangs_projection_raw, layer_buildplate_covered +#ifdef SLIC3R_DEBUG + , iRun, layer_id, "general" +#endif /* SLIC3R_DEBUG */ + ); + }); + + Polygons layer_support_area_enforcers; + if (! enforcers_projection.empty()) + // Project the enforcers polygons downwards, don't trim them with the "buildplate only" polygons. + task_group.run([&grid_params, &enforcers_projection, &enforcers_projection_raw, &layer, &layer_support_area_enforcers +#ifdef SLIC3R_DEBUG + , iRun, layer_id +#endif /* SLIC3R_DEBUG */ + ]{ + std::tie(layer_support_area_enforcers, enforcers_projection) = project_support_to_grid(layer, grid_params, enforcers_projection_raw, nullptr +#ifdef SLIC3R_DEBUG + , iRun, layer_id, "enforcers" +#endif /* SLIC3R_DEBUG */ + ); + }); + + task_group.wait(); + + if (! layer_support_area_enforcers.empty()) { + if (layer_support_area.empty()) + layer_support_area = std::move(layer_support_area_enforcers); + else + layer_support_area = union_(layer_support_area, layer_support_area_enforcers); + } + } // over all layers downwards + + std::reverse(bottom_contacts.begin(), bottom_contacts.end()); + trim_support_layers_by_object(object, bottom_contacts, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_support_params.gap_xy); return bottom_contacts; } @@ -2179,7 +2431,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int // Verify that the extremes are separated by m_support_layer_height_min. for (size_t i = 1; i < extremes.size(); ++ i) { assert(extremes[i]->extreme_z() - extremes[i-1]->extreme_z() == 0. || - extremes[i]->extreme_z() - extremes[i-1]->extreme_z() > m_support_layer_height_min - EPSILON); + extremes[i]->extreme_z() - extremes[i-1]->extreme_z() > m_support_params.support_layer_height_min - EPSILON); assert(extremes[i]->extreme_z() - extremes[i-1]->extreme_z() > 0. || extremes[i]->layer_type == extremes[i-1]->layer_type || (extremes[i]->layer_type == sltBottomContact && extremes[i - 1]->layer_type == sltTopContact)); @@ -2203,7 +2455,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int // This is a bottom of a synchronized (or soluble) top contact layer, its height has been decided in this->top_contact_layers(). assert(extr2->layer_type == sltTopContact); assert(extr2->bottom_z == m_slicing_params.first_print_layer_height); - assert(extr2->print_z >= m_slicing_params.first_print_layer_height + m_support_layer_height_min - EPSILON); + assert(extr2->print_z >= m_slicing_params.first_print_layer_height + m_support_params.support_layer_height_min - EPSILON); if (intermediate_layers.empty() || intermediate_layers.back()->print_z < m_slicing_params.first_print_layer_height) { MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); layer_new.bottom_z = 0.; @@ -2239,7 +2491,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int if (dist == 0.) continue; // The new layers shall be at least m_support_layer_height_min thick. - assert(dist >= m_support_layer_height_min - EPSILON); + assert(dist >= m_support_params.support_layer_height_min - EPSILON); if (synchronize) { // Emit support layers synchronized with the object layers. // Find the first object layer, which has its print_z in this support Z range. @@ -2268,10 +2520,10 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int assert(n_layers_extra > 0); coordf_t step = dist / coordf_t(n_layers_extra); if (extr1 != nullptr && extr1->layer_type == sltTopContact && - extr1->print_z + m_support_layer_height_min > extr1->bottom_z + step) { + extr1->print_z + m_support_params.support_layer_height_min > extr1->bottom_z + step) { // The bottom extreme is a bottom of a top surface. Ensure that the gap // between the 1st intermediate layer print_z and extr1->print_z is not too small. - assert(extr1->bottom_z + m_support_layer_height_min < extr1->print_z + EPSILON); + assert(extr1->bottom_z + m_support_params.support_layer_height_min < extr1->print_z + EPSILON); // Generate the first intermediate layer. MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); layer_new.bottom_z = extr1->bottom_z; @@ -2462,10 +2714,7 @@ void PrintObjectSupportMaterial::generate_base_layers( ++ iRun; #endif /* SLIC3R_DEBUG */ -// trim_support_layers_by_object(object, intermediate_layers, 0., 0., m_gap_xy); - this->trim_support_layers_by_object(object, intermediate_layers, - m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, - m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy); + this->trim_support_layers_by_object(object, intermediate_layers, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_support_params.gap_xy); } void PrintObjectSupportMaterial::trim_support_layers_by_object( @@ -2499,7 +2748,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( // BOOST_LOG_TRIVIAL(trace) << "Support generator - trim_support_layers_by_object - trimmming non-empty layer " << idx_layer << " of " << nonempty_layers.size(); assert(! support_layer.polygons.empty() && support_layer.print_z >= m_slicing_params.raft_contact_top_z + EPSILON); // Find the overlapping object layers including the extra above / below gap. - coordf_t z_threshold = support_layer.print_z - support_layer.height - gap_extra_below + EPSILON; + coordf_t z_threshold = support_layer.bottom_print_z() - gap_extra_below + EPSILON; idx_object_layer_overlapping = idx_higher_or_equal( object.layers().begin(), object.layers().end(), idx_object_layer_overlapping, [z_threshold](const Layer *layer){ return layer->print_z >= z_threshold; }); @@ -2508,11 +2757,11 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( size_t i = idx_object_layer_overlapping; for (; i < object.layers().size(); ++ i) { const Layer &object_layer = *object.layers()[i]; - if (object_layer.print_z - object_layer.height > support_layer.print_z + gap_extra_above - EPSILON) + if (object_layer.bottom_z() > support_layer.print_z + gap_extra_above - EPSILON) break; polygons_append(polygons_trimming, offset(object_layer.lslices, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); } - if (! m_slicing_params.soluble_interface) { + if (! m_slicing_params.soluble_interface && m_object_config->thick_bridges) { // Collect all bottom surfaces, which will be extruded with a bridging flow. for (; i < object.layers().size(); ++ i) { const Layer &object_layer = *object.layers()[i]; @@ -2526,6 +2775,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( offset(to_expolygons(region->fill_surfaces.filter_by_type(stBottomBridge)), gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); if (region->region()->config().overhangs.value) + // Add bridging perimeters. SupportMaterialInternal::collect_bridging_perimeter_areas(region->perimeters, gap_xy_scaled, polygons_trimming); } if (! some_region_overlaps) @@ -2654,26 +2904,27 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf new_layer.bottom_z = print_z; new_layer.polygons = interface_polygons; //FIXME misusing contact_polygons for support columns. - new_layer.contact_polygons = new Polygons(columns); + new_layer.contact_polygons = std::make_unique(columns); } - } else if (columns_base != nullptr) { + } else { + if (columns_base != nullptr) { // Expand the bases of the support columns in the 1st layer. - { Polygons &raft = columns_base->polygons; - Polygons trimming = offset(m_object->layers().front()->lslices, (float)scale_(m_gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS); + Polygons trimming = offset(m_object->layers().front()->lslices, (float)scale_(m_support_params.gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS); if (inflate_factor_1st_layer > SCALED_EPSILON) { // Inflate in multiple steps to avoid leaking of the support 1st layer through object walls. - auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / m_first_layer_flow.scaled_width()))); + auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / m_support_params.first_layer_flow.scaled_width()))); float step = inflate_factor_1st_layer / nsteps; for (int i = 0; i < nsteps; ++ i) raft = diff(offset(raft, step), trimming); } else raft = diff(raft, trimming); + if (contacts != nullptr) + columns_base->polygons = diff(columns_base->polygons, interface_polygons); } - if (contacts != nullptr) - columns_base->polygons = diff(columns_base->polygons, interface_polygons); if (! brim.empty()) { - columns_base->polygons = diff(columns_base->polygons, brim); + if (columns_base) + columns_base->polygons = diff(columns_base->polygons, brim); if (contacts) contacts->polygons = diff(contacts->polygons, brim); if (interfaces) @@ -2709,17 +2960,23 @@ std::pairsupport_material_interface_extruder.value > 0 && m_print_config->filament_soluble.get_at(m_object_config->support_material_interface_extruder.value - 1) && // Base extruder: Either "print with active extruder" not soluble. (m_object_config->support_material_extruder.value == 0 || ! m_print_config->filament_soluble.get_at(m_object_config->support_material_extruder.value - 1)); - int num_interface_layers = m_object_config->support_material_interface_layers.value; - int num_base_interface_layers = soluble_interface_non_soluble_base ? std::min(num_interface_layers / 2, 2) : 0; + int num_interface_layers_top = m_object_config->support_material_interface_layers; + int num_interface_layers_bottom = m_object_config->support_material_bottom_interface_layers; + if (num_interface_layers_bottom < 0) + num_interface_layers_bottom = num_interface_layers_top; + int num_base_interface_layers_top = soluble_interface_non_soluble_base ? std::min(num_interface_layers_top / 2, 2) : 0; + int num_base_interface_layers_bottom = soluble_interface_non_soluble_base ? std::min(num_interface_layers_bottom / 2, 2) : 0; - if (! intermediate_layers.empty() && num_interface_layers > 1) { + if (! intermediate_layers.empty() && (num_interface_layers_top > 1 || num_interface_layers_bottom > 1)) { // For all intermediate layers, collect top contact surfaces, which are not further than support_material_interface_layers. BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - start"; // Since the intermediate layer index starts at zero the number of interface layer needs to be reduced by 1. - -- num_interface_layers; - int num_interface_layers_only = num_interface_layers - num_base_interface_layers; + -- num_interface_layers_top; + -- num_interface_layers_bottom; + int num_interface_layers_only_top = num_interface_layers_top - num_base_interface_layers_top; + int num_interface_layers_only_bottom = num_interface_layers_bottom - num_base_interface_layers_bottom; interface_layers.assign(intermediate_layers.size(), nullptr); - if (num_base_interface_layers) + if (num_base_interface_layers_top || num_base_interface_layers_bottom) base_interface_layers.assign(intermediate_layers.size(), nullptr); tbb::spin_mutex layer_storage_mutex; // Insert a new layer into base_interface_layers, if intersection with base exists. @@ -2743,7 +3000,8 @@ std::pair(0, int(intermediate_layers.size())), - [&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer, num_interface_layers, num_base_interface_layers, num_interface_layers_only, + [&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer, + num_interface_layers_top, num_interface_layers_bottom, num_base_interface_layers_top, num_base_interface_layers_bottom, num_interface_layers_only_top, num_interface_layers_only_bottom, &interface_layers, &base_interface_layers](const tbb::blocked_range& range) { // Gather the top / bottom contact layers intersecting with num_interface_layers resp. num_interface_layers_only intermediate layers above / below // this intermediate layer. @@ -2754,45 +3012,51 @@ std::pairprint_z; - coordf_t top_inteface_z = std::numeric_limits::max(); - coordf_t bottom_z = intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers + 1)]->bottom_z; - coordf_t bottom_interface_z = - std::numeric_limits::max(); - if (num_base_interface_layers > 0) { - // Some base interface layers will be generated. - if (num_interface_layers_only == 0) - // Only base interface layers to generate. - std::swap(top_inteface_z, bottom_interface_z); - else { - top_inteface_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_only - 1)]->print_z; - bottom_interface_z = intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_only)]->bottom_z; - } - } - // Move idx_top_contact_first up until above the current print_z. - idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON - // Collect the top contact areas above this intermediate layer, below top_z. Polygons polygons_top_contact_projected_interface; Polygons polygons_top_contact_projected_base; - for (int idx_top_contact = idx_top_contact_first; idx_top_contact < int(top_contacts.size()); ++ idx_top_contact) { - const MyLayer &top_contact_layer = *top_contacts[idx_top_contact]; - //FIXME maybe this adds one interface layer in excess? - if (top_contact_layer.bottom_z - EPSILON > top_z) - break; - polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface, top_contact_layer.polygons); - } - // Move idx_bottom_contact_first up until touching bottom_z. - idx_bottom_contact_first = idx_higher_or_equal(bottom_contacts, idx_bottom_contact_first, [bottom_z](const MyLayer *layer){ return layer->print_z >= bottom_z - EPSILON; }); - // Collect the top contact areas above this intermediate layer, below top_z. Polygons polygons_bottom_contact_projected_interface; Polygons polygons_bottom_contact_projected_base; - for (int idx_bottom_contact = idx_bottom_contact_first; idx_bottom_contact < int(bottom_contacts.size()); ++ idx_bottom_contact) { - const MyLayer &bottom_contact_layer = *bottom_contacts[idx_bottom_contact]; - if (bottom_contact_layer.print_z - EPSILON > intermediate_layer.bottom_z) - break; - polygons_append(bottom_contact_layer.print_z - EPSILON > bottom_interface_z ? polygons_bottom_contact_projected_interface : polygons_bottom_contact_projected_base, bottom_contact_layer.polygons); + if (num_interface_layers_top > 0) { + // Top Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces + coordf_t top_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_top - 1)]->print_z; + coordf_t top_inteface_z = std::numeric_limits::max(); + if (num_base_interface_layers_top > 0) + // Some top base interface layers will be generated. + top_inteface_z = num_interface_layers_only_top == 0 ? + // Only base interface layers to generate. + - std::numeric_limits::max() : + intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_only_top - 1)]->print_z; + // Move idx_top_contact_first up until above the current print_z. + idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON + // Collect the top contact areas above this intermediate layer, below top_z. + for (int idx_top_contact = idx_top_contact_first; idx_top_contact < int(top_contacts.size()); ++ idx_top_contact) { + const MyLayer &top_contact_layer = *top_contacts[idx_top_contact]; + //FIXME maybe this adds one interface layer in excess? + if (top_contact_layer.bottom_z - EPSILON > top_z) + break; + polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface, top_contact_layer.polygons); + } + } + if (num_interface_layers_bottom > 0) { + // Bottom Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces + coordf_t bottom_z = intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_bottom + 1)]->bottom_z; + coordf_t bottom_interface_z = - std::numeric_limits::max(); + if (num_base_interface_layers_bottom > 0) + // Some bottom base interface layers will be generated. + bottom_interface_z = num_interface_layers_only_bottom == 0 ? + // Only base interface layers to generate. + std::numeric_limits::max() : + intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_only_bottom)]->bottom_z; + // Move idx_bottom_contact_first up until touching bottom_z. + idx_bottom_contact_first = idx_higher_or_equal(bottom_contacts, idx_bottom_contact_first, [bottom_z](const MyLayer *layer){ return layer->print_z >= bottom_z - EPSILON; }); + // Collect the top contact areas above this intermediate layer, below top_z. + for (int idx_bottom_contact = idx_bottom_contact_first; idx_bottom_contact < int(bottom_contacts.size()); ++ idx_bottom_contact) { + const MyLayer &bottom_contact_layer = *bottom_contacts[idx_bottom_contact]; + if (bottom_contact_layer.print_z - EPSILON > intermediate_layer.bottom_z) + break; + polygons_append(bottom_contact_layer.print_z - EPSILON > bottom_interface_z ? polygons_bottom_contact_projected_interface : polygons_bottom_contact_projected_base, bottom_contact_layer.polygons); + } } - MyLayer *interface_layer = nullptr; if (! polygons_bottom_contact_projected_interface.empty() || ! polygons_top_contact_projected_interface.empty()) { interface_layer = insert_layer( @@ -2810,7 +3074,7 @@ std::pair(); - eec->no_sort = true; + std::unique_ptr eec; + if (no_sort) { + eec = std::make_unique(); + eec->no_sort = true; + } + ExtrusionEntitiesPtr &out = no_sort ? eec->entities : dst; // Draw the perimeters. Polylines polylines; polylines.reserve(expoly.holes.size() + 1); @@ -2903,18 +3172,24 @@ static inline void fill_expolygons_with_sheath_generate_paths( pl.clip_end(clip_length); polylines.emplace_back(std::move(pl)); } - extrusion_entities_append_paths(eec->entities, polylines, erSupportMaterial, flow.mm3_per_mm(), flow.width, flow.height); + extrusion_entities_append_paths(out, polylines, erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height()); // Fill in the rest. - fill_expolygons_generate_paths(eec->entities, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow); - dst.emplace_back(eec.release()); + fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow); + if (no_sort && ! eec->empty()) + dst.emplace_back(eec.release()); } } // Support layers, partially processed. struct MyLayerExtruded { - MyLayerExtruded() : layer(nullptr), m_polygons_to_extrude(nullptr) {} - ~MyLayerExtruded() { delete m_polygons_to_extrude; m_polygons_to_extrude = nullptr; } + MyLayerExtruded& operator=(MyLayerExtruded &&rhs) { + this->layer = rhs.layer; + this->extrusions = std::move(rhs.extrusions); + this->m_polygons_to_extrude = std::move(rhs.m_polygons_to_extrude); + rhs.layer = nullptr; + return *this; + } bool empty() const { return layer == nullptr || layer->polygons.empty(); @@ -2922,7 +3197,7 @@ struct MyLayerExtruded void set_polygons_to_extrude(Polygons &&polygons) { if (m_polygons_to_extrude == nullptr) - m_polygons_to_extrude = new Polygons(std::move(polygons)); + m_polygons_to_extrude = std::make_unique(std::move(polygons)); else *m_polygons_to_extrude = std::move(polygons); } @@ -2943,12 +3218,11 @@ struct MyLayerExtruded if (m_polygons_to_extrude == nullptr) { // This layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). assert(this->extrusions.empty()); - m_polygons_to_extrude = new Polygons(this->layer->polygons); + m_polygons_to_extrude = std::make_unique(this->layer->polygons); } Slic3r::polygons_append(*m_polygons_to_extrude, std::move(*other.m_polygons_to_extrude)); *m_polygons_to_extrude = union_(*m_polygons_to_extrude, true); - delete other.m_polygons_to_extrude; - other.m_polygons_to_extrude = nullptr; + other.m_polygons_to_extrude.reset(); } else if (m_polygons_to_extrude != nullptr) { assert(other.m_polygons_to_extrude == nullptr); // The other layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). @@ -2971,12 +3245,14 @@ struct MyLayerExtruded } // The source layer. It carries the height and extrusion type (bridging / non bridging, extrusion height). - PrintObjectSupportMaterial::MyLayer *layer; + PrintObjectSupportMaterial::MyLayer *layer { nullptr }; // Collect extrusions. They will be exported sorted by the bottom height. ExtrusionEntitiesPtr extrusions; + +private: // In case the extrusions are non-empty, m_polygons_to_extrude may contain the rest areas yet to be filled by additional support. // This is useful mainly for the loop interfaces, which are generated before the zig-zag infills. - Polygons *m_polygons_to_extrude; + std::unique_ptr m_polygons_to_extrude; }; typedef std::vector MyLayerExtrudedPtrs; @@ -3011,8 +3287,7 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const if (n_contact_loops == 0 || top_contact_layer.empty()) return; - Flow flow = interface_flow_src; - flow.height = float(top_contact_layer.layer->height); + Flow flow = interface_flow_src.with_height(top_contact_layer.layer->height); Polygons overhang_polygons; if (top_contact_layer.layer->overhang_polygons != nullptr) @@ -3209,7 +3484,7 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const extrusion_entities_append_paths( top_contact_layer.extrusions, std::move(loop_lines), - erSupportMaterialInterface, flow.mm3_per_mm(), flow.width, flow.height); + erSupportMaterialInterface, flow.mm3_per_mm(), flow.width(), flow.height()); } #ifdef SLIC3R_DEBUG @@ -3232,6 +3507,9 @@ static std::string dbg_index_to_color(int idx) // Therefore the bottom interface spots are expanded a bit. The expanded regions may overlap with another bottom interface layers, // leading to over extrusion, where they overlap. The over extrusion is better avoided as it often makes the interface layers // to stick too firmly to the object. +// +// Modulate thickness (increase bottom_z) of extrusions_in_out generated for this_layer +// if they overlap with overlapping_layers, whose print_z is above this_layer.bottom_z() and below this_layer.print_z. void modulate_extrusion_by_overlapping_layers( // Extrusions generated for this_layer. ExtrusionEntitiesPtr &extrusions_in_out, @@ -3320,8 +3598,8 @@ void modulate_extrusion_by_overlapping_layers( // Collect the paths of this_layer. { Polylines &polylines = path_fragments.back().polylines; - for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) { - ExtrusionPath *path = dynamic_cast(*it); + for (ExtrusionEntity *ee : extrusions_in_out) { + ExtrusionPath *path = dynamic_cast(ee); assert(path != nullptr); polylines.emplace_back(Polyline(std::move(path->polyline))); path_ends.emplace_back(std::pair(polylines.back().points.front(), polylines.back().points.back())); @@ -3342,7 +3620,7 @@ void modulate_extrusion_by_overlapping_layers( // Adjust the extrusion parameters for a reduced layer height and a non-bridging flow (nozzle_dmr = -1, does not matter). assert(this_layer.print_z > overlapping_layer.print_z); frag.height = float(this_layer.print_z - overlapping_layer.print_z); - frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f, false).mm3_per_mm(); + frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f).mm3_per_mm(); #ifdef SLIC3R_DEBUG svg.draw(frag.polylines, dbg_index_to_color(i_overlapping_layer), scale_(0.1)); #endif /* SLIC3R_DEBUG */ @@ -3481,17 +3759,16 @@ void PrintObjectSupportMaterial::generate_toolpaths( const MyLayersPtr &interface_layers, const MyLayersPtr &base_interface_layers) const { -// Slic3r::debugf "Generating patterns\n"; // loop_interface_processor with a given circle radius. - LoopInterfaceProcessor loop_interface_processor(1.5 * m_support_material_interface_flow.scaled_width()); + LoopInterfaceProcessor loop_interface_processor(1.5 * m_support_params.support_material_interface_flow.scaled_width()); loop_interface_processor.n_contact_loops = this->has_contact_loops() ? 1 : 0; float base_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value)); float interface_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value + 90.)); - coordf_t interface_spacing = m_object_config->support_material_interface_spacing.value + m_support_material_interface_flow.spacing(); - coordf_t interface_density = std::min(1., m_support_material_interface_flow.spacing() / interface_spacing); - coordf_t support_spacing = m_object_config->support_material_spacing.value + m_support_material_flow.spacing(); - coordf_t support_density = std::min(1., m_support_material_flow.spacing() / support_spacing); + coordf_t interface_spacing = m_object_config->support_material_interface_spacing.value + m_support_params.support_material_interface_flow.spacing(); + coordf_t interface_density = std::min(1., m_support_params.support_material_interface_flow.spacing() / interface_spacing); + coordf_t support_spacing = m_object_config->support_material_spacing.value + m_support_params.support_material_flow.spacing(); + coordf_t support_density = std::min(1., m_support_params.support_material_flow.spacing() / support_spacing); if (m_object_config->support_material_interface_layers.value == 0) { // No interface layers allowed, print everything with the base support pattern. interface_spacing = support_spacing; @@ -3501,7 +3778,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( // Prepare fillers. SupportMaterialPattern support_pattern = m_object_config->support_material_pattern; bool with_sheath = m_object_config->support_material_with_sheath; - InfillPattern infill_pattern = (support_pattern == smpHoneycomb ? ipHoneycomb : ipRectilinear); + InfillPattern infill_pattern = (support_pattern == smpHoneycomb ? ipHoneycomb : ipSupportBase); std::vector angles; angles.push_back(base_angle); @@ -3565,10 +3842,11 @@ void PrintObjectSupportMaterial::generate_toolpaths( //FIXME misusing contact_polygons for support columns. ((raft_layer.contact_polygons == nullptr) ? Polygons() : *raft_layer.contact_polygons); if (! to_infill_polygons.empty()) { - Flow flow(float(m_support_material_flow.width), float(raft_layer.height), m_support_material_flow.nozzle_diameter, raft_layer.bridging); + assert(! raft_layer.bridging); + Flow flow(float(m_support_params.support_material_flow.width()), float(raft_layer.height), m_support_params.support_material_flow.nozzle_diameter()); Fill * filler = filler_support.get(); filler->angle = raft_angle_base; - filler->spacing = m_support_material_flow.spacing(); + filler->spacing = m_support_params.support_material_flow.spacing(); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_density)); fill_expolygons_with_sheath_generate_paths( // Destination @@ -3579,24 +3857,25 @@ void PrintObjectSupportMaterial::generate_toolpaths( filler, float(support_density), // Extrusion parameters erSupportMaterial, flow, - with_sheath); + with_sheath, false); } } Fill *filler = filler_interface.get(); - Flow flow = m_first_layer_flow; + Flow flow = m_support_params.first_layer_flow; float density = 0.f; if (support_layer_id == 0) { // Base flange. filler->angle = raft_angle_1st_layer; - filler->spacing = m_first_layer_flow.spacing(); + filler->spacing = m_support_params.first_layer_flow.spacing(); density = float(m_object_config->raft_first_layer_density.value * 0.01); } else if (support_layer_id >= m_slicing_params.base_raft_layers) { filler->angle = raft_angle_interface; // We don't use $base_flow->spacing because we need a constant spacing // value that guarantees that all layers are correctly aligned. - filler->spacing = m_support_material_flow.spacing(); - flow = Flow(float(m_support_material_interface_flow.width), float(raft_layer.height), m_support_material_flow.nozzle_diameter, raft_layer.bridging); + filler->spacing = m_support_params.support_material_flow.spacing(); + assert(! raft_layer.bridging); + flow = Flow(float(m_support_params.support_material_interface_flow.width()), float(raft_layer.height), m_support_params.support_material_flow.nozzle_diameter()); density = float(interface_density); } else continue; @@ -3611,7 +3890,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( // Extrusion parameters (support_layer_id < m_slicing_params.base_raft_layers) ? erSupportMaterial : erSupportMaterialInterface, flow, // sheath at first layer - support_layer_id == 0); + support_layer_id == 0, support_layer_id == 0); } }); @@ -3621,14 +3900,22 @@ void PrintObjectSupportMaterial::generate_toolpaths( std::vector overlapping; }; struct LayerCache { - MyLayerExtruded bottom_contact_layer; - MyLayerExtruded top_contact_layer; - MyLayerExtruded base_layer; - MyLayerExtruded interface_layer; - MyLayerExtruded base_interface_layer; - std::vector overlaps; + MyLayerExtruded bottom_contact_layer; + MyLayerExtruded top_contact_layer; + MyLayerExtruded base_layer; + MyLayerExtruded interface_layer; + MyLayerExtruded base_interface_layer; + boost::container::static_vector nonempty; + + void add_nonempty_and_sort() { + for (MyLayerExtruded *item : { &bottom_contact_layer, &top_contact_layer, &interface_layer, &base_interface_layer, &base_layer }) + if (! item->empty()) + this->nonempty.emplace_back(item); + // Sort the layers with the same print_z coordinate by their heights, thickest first. + std::stable_sort(this->nonempty.begin(), this->nonempty.end(), [](const LayerCacheItem &lc1, const LayerCacheItem &lc2) { return lc1.layer_extruded->layer->height > lc2.layer_extruded->layer->height; }); + } }; - std::vector layer_caches(support_layers.size(), LayerCache()); + std::vector layer_caches(support_layers.size()); const auto fill_type_interface = @@ -3694,25 +3981,27 @@ void PrintObjectSupportMaterial::generate_toolpaths( base_layer.layer = intermediate_layers[idx_layer_intermediate]; if (m_object_config->support_material_interface_layers == 0) { - // If no interface layers were requested, we treat the contact layer exactly as a generic base layer. - if (m_can_merge_support_regions) { + // If no top interface layers were requested, we treat the contact layer exactly as a generic base layer. + if (m_support_params.can_merge_support_regions) { if (base_layer.could_merge(top_contact_layer)) base_layer.merge(std::move(top_contact_layer)); - else if (base_layer.empty() && !top_contact_layer.empty() && !top_contact_layer.layer->bridging) - std::swap(base_layer, top_contact_layer); - if (base_layer.could_merge(bottom_contact_layer)) - base_layer.merge(std::move(bottom_contact_layer)); - else if (base_layer.empty() && !bottom_contact_layer.empty() && !bottom_contact_layer.layer->bridging) - std::swap(base_layer, bottom_contact_layer); + else if (base_layer.empty()) + base_layer = std::move(top_contact_layer); } } else { - loop_interface_processor.generate(top_contact_layer, m_support_material_interface_flow); + loop_interface_processor.generate(top_contact_layer, m_support_params.support_material_interface_flow); // If no loops are allowed, we treat the contact layer exactly as a generic interface layer. // Merge interface_layer into top_contact_layer, as the top_contact_layer is not synchronized and therefore it will be used // to trim other layers. if (top_contact_layer.could_merge(interface_layer)) top_contact_layer.merge(std::move(interface_layer)); } + if ((m_object_config->support_material_interface_layers == 0 || m_object_config->support_material_bottom_interface_layers == 0) && m_support_params.can_merge_support_regions) { + if (base_layer.could_merge(bottom_contact_layer)) + base_layer.merge(std::move(bottom_contact_layer)); + else if (base_layer.empty() && ! bottom_contact_layer.empty() && ! bottom_contact_layer.layer->bridging) + base_layer = std::move(bottom_contact_layer); + } #if 0 if ( ! interface_layer.empty() && ! base_layer.empty()) { @@ -3731,44 +4020,41 @@ void PrintObjectSupportMaterial::generate_toolpaths( MyLayerExtruded &layer_ex = (i == 0) ? top_contact_layer : (i == 1 ? bottom_contact_layer : interface_layer); if (layer_ex.empty() || layer_ex.polygons_to_extrude().empty()) continue; - bool interface_as_base = (&layer_ex == &interface_layer) && m_object_config->support_material_interface_layers.value == 0; + bool interface_as_base = m_object_config->support_material_interface_layers.value == 0 || + (m_object_config->support_material_bottom_interface_layers == 0 && &layer_ex == &bottom_contact_layer); //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) - Flow interface_flow( - float(layer_ex.layer->bridging ? layer_ex.layer->height : (interface_as_base ? m_support_material_flow.width : m_support_material_interface_flow.width)), - float(layer_ex.layer->height), - m_support_material_interface_flow.nozzle_diameter, - layer_ex.layer->bridging); + auto interface_flow = layer_ex.layer->bridging ? + Flow::bridging_flow(layer_ex.layer->height, m_support_params.support_material_bottom_interface_flow.nozzle_diameter()) : + (interface_as_base ? &m_support_params.support_material_flow : &m_support_params.support_material_interface_flow)->with_height(float(layer_ex.layer->height)); filler_interface->angle = interface_as_base ? // If zero interface layers are configured, use the same angle as for the base layers. angles[support_layer_id % angles.size()] : // Use interface angle for the interface layers. interface_angle; - filler_interface->spacing = m_support_material_interface_flow.spacing(); - filler_interface->link_max_length = coord_t(scale_(filler_interface->spacing * link_max_length_factor / interface_density)); + double density = interface_as_base ? support_density : interface_density; + filler_interface->spacing = interface_as_base ? m_support_params.support_material_flow.spacing() : m_support_params.support_material_interface_flow.spacing(); + filler_interface->link_max_length = coord_t(scale_(filler_interface->spacing * link_max_length_factor / density)); fill_expolygons_generate_paths( // Destination layer_ex.extrusions, // Regions to fill union_ex(layer_ex.polygons_to_extrude(), true), // Filler and its parameters - filler_interface.get(), float(interface_density), + filler_interface.get(), float(density), // Extrusion parameters erSupportMaterialInterface, interface_flow); } // Base interface layers under soluble interfaces - if ( ! base_interface_layer.empty() && ! base_interface_layer.polygons_to_extrude().empty()){ + if ( ! base_interface_layer.empty() && ! base_interface_layer.polygons_to_extrude().empty()) { Fill *filler = filler_base_interface.get(); //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) - Flow interface_flow( - float(base_interface_layer.layer->bridging ? base_interface_layer.layer->height : m_support_material_flow.width), // m_support_material_interface_flow.width)), - float(base_interface_layer.layer->height), - m_support_material_flow.nozzle_diameter, - base_interface_layer.layer->bridging); + assert(! base_interface_layer.layer->bridging); + Flow interface_flow = m_support_params.support_material_flow.with_height(float(base_interface_layer.layer->height)); filler->angle = interface_angle; - filler->spacing = m_support_material_interface_flow.spacing(); + filler->spacing = m_support_params.support_material_interface_flow.spacing(); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / interface_density)); fill_expolygons_generate_paths( // Destination @@ -3788,27 +4074,26 @@ void PrintObjectSupportMaterial::generate_toolpaths( filler->angle = angles[support_layer_id % angles.size()]; // We don't use $base_flow->spacing because we need a constant spacing // value that guarantees that all layers are correctly aligned. - Flow flow( - float(base_layer.layer->bridging ? base_layer.layer->height : m_support_material_flow.width), - float(base_layer.layer->height), - m_support_material_flow.nozzle_diameter, - base_layer.layer->bridging); - filler->spacing = m_support_material_flow.spacing(); + assert(! base_layer.layer->bridging); + auto flow = m_support_params.support_material_flow.with_height(float(base_layer.layer->height)); + filler->spacing = m_support_params.support_material_flow.spacing(); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_density)); float density = float(support_density); bool sheath = with_sheath; + bool no_sort = false; if (base_layer.layer->bottom_z < EPSILON) { // Base flange (the 1st layer). filler = filler_first_layer; filler->angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value + 90.)); density = float(m_object_config->raft_first_layer_density.value * 0.01); - flow = m_first_layer_flow; + flow = m_support_params.first_layer_flow; // use the proper spacing for first layer as we don't need to align // its pattern to the other layers //FIXME When paralellizing, each thread shall have its own copy of the fillers. filler->spacing = flow.spacing(); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); - sheath = true; + sheath = true; + no_sort = true; } fill_expolygons_with_sheath_generate_paths( // Destination @@ -3819,7 +4104,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( filler, density, // Extrusion parameters erSupportMaterial, flow, - sheath); + sheath, no_sort); } @@ -3828,24 +4113,13 @@ void PrintObjectSupportMaterial::generate_toolpaths( base_layer.could_merge(base_interface_layer)) base_layer.merge(std::move(base_interface_layer)); - layer_cache.overlaps.reserve(5); - if (! bottom_contact_layer.empty()) - layer_cache.overlaps.push_back(&bottom_contact_layer); - if (! top_contact_layer.empty()) - layer_cache.overlaps.push_back(&top_contact_layer); - if (! interface_layer.empty()) - layer_cache.overlaps.push_back(&interface_layer); - if (! base_interface_layer.empty()) - layer_cache.overlaps.push_back(&base_interface_layer); - if (! base_layer.empty()) - layer_cache.overlaps.push_back(&base_layer); - // Sort the layers with the same print_z coordinate by their heights, thickest first. - std::sort(layer_cache.overlaps.begin(), layer_cache.overlaps.end(), [](const LayerCacheItem &lc1, const LayerCacheItem &lc2) { return lc1.layer_extruded->layer->height > lc2.layer_extruded->layer->height; }); + layer_cache.add_nonempty_and_sort(); + // Collect the support areas with this print_z into islands, as there is no need // for retraction over these islands. Polygons polys; // Collect the extrusions, sorted by the bottom extrusion height. - for (LayerCacheItem &layer_cache_item : layer_cache.overlaps) { + for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) { // Collect islands to polys. layer_cache_item.layer_extruded->polygons_append(polys); // The print_z of the top contact surfaces and bottom_z of the bottom contact surfaces are "free" @@ -3859,32 +4133,22 @@ void PrintObjectSupportMaterial::generate_toolpaths( // Collect overlapping top/bottom surfaces. layer_cache_item.overlapping.reserve(20); coordf_t bottom_z = layer_cache_item.layer_extruded->layer->bottom_print_z() + EPSILON; - for (int i = int(idx_layer_bottom_contact) - 1; i >= 0 && bottom_contacts[i]->print_z > bottom_z; -- i) - layer_cache_item.overlapping.push_back(bottom_contacts[i]); - for (int i = int(idx_layer_top_contact) - 1; i >= 0 && top_contacts[i]->print_z > bottom_z; -- i) - layer_cache_item.overlapping.push_back(top_contacts[i]); + auto add_overlapping = [&layer_cache_item, bottom_z](const MyLayersPtr &layers, size_t idx_top) { + for (int i = int(idx_top) - 1; i >= 0 && layers[i]->print_z > bottom_z; -- i) + layer_cache_item.overlapping.push_back(layers[i]); + }; + add_overlapping(top_contacts, idx_layer_top_contact); if (layer_cache_item.layer_extruded->layer->layer_type == sltBottomContact) { // Bottom contact layer may overlap with a base layer, which may be changed to interface layer. - for (int i = int(idx_layer_intermediate) - 1; i >= 0 && intermediate_layers[i]->print_z > bottom_z; -- i) - layer_cache_item.overlapping.push_back(intermediate_layers[i]); - for (int i = int(idx_layer_interface) - 1; i >= 0 && interface_layers[i]->print_z > bottom_z; -- i) - layer_cache_item.overlapping.push_back(interface_layers[i]); - for (int i = int(idx_layer_base_interface) - 1; i >= 0 && base_interface_layers[i]->print_z > bottom_z; -- i) - layer_cache_item.overlapping.push_back(base_interface_layers[i]); + add_overlapping(intermediate_layers, idx_layer_intermediate); + add_overlapping(interface_layers, idx_layer_interface); + add_overlapping(base_interface_layers, idx_layer_base_interface); } - std::sort(layer_cache_item.overlapping.begin(), layer_cache_item.overlapping.end(), MyLayersPtrCompare()); + // Order the layers by lexicographically by an increasing print_z and a decreasing layer height. + std::stable_sort(layer_cache_item.overlapping.begin(), layer_cache_item.overlapping.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); } if (! polys.empty()) expolygons_append(support_layer.support_islands.expolygons, union_ex(polys)); - /* { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output("islands_" . $z . ".svg", - red_expolygons => union_ex($contact), - green_expolygons => union_ex($interface), - green_polylines => [ map $_->unpack->polyline, @{$layer->support_contact_fills} ], - polylines => [ map $_->unpack->polyline, @{$layer->support_fills} ], - ); - } */ } // for each support_layer_id }); @@ -3895,12 +4159,35 @@ void PrintObjectSupportMaterial::generate_toolpaths( for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) { SupportLayer &support_layer = *support_layers[support_layer_id]; LayerCache &layer_cache = layer_caches[support_layer_id]; - for (LayerCacheItem &layer_cache_item : layer_cache.overlaps) { + // For all extrusion types at this print_z, ordered by decreasing layer height: + for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) { + // Trim the extrusion height from the bottom by the overlapping layers. modulate_extrusion_by_overlapping_layers(layer_cache_item.layer_extruded->extrusions, *layer_cache_item.layer_extruded->layer, layer_cache_item.overlapping); support_layer.support_fills.append(std::move(layer_cache_item.layer_extruded->extrusions)); } } }); + +#ifndef NDEBUG + struct Test { + static bool verify_nonempty(const ExtrusionEntityCollection *collection) { + for (const ExtrusionEntity *ee : collection->entities) { + if (const ExtrusionPath *path = dynamic_cast(ee)) + assert(! path->empty()); + else if (const ExtrusionMultiPath *multipath = dynamic_cast(ee)) + assert(! multipath->empty()); + else if (const ExtrusionEntityCollection *eecol = dynamic_cast(ee)) { + assert(! eecol->empty()); + return verify_nonempty(eecol); + } else + assert(false); + } + return true; + } + }; + for (const SupportLayer *support_layer : support_layers) + assert(Test::verify_nonempty(&support_layer->support_fills)); +#endif // NDEBUG } /* diff --git a/src/libslic3r/SupportMaterial.hpp b/src/libslic3r/SupportMaterial.hpp index 7123290a62..a1bd81297e 100644 --- a/src/libslic3r/SupportMaterial.hpp +++ b/src/libslic3r/SupportMaterial.hpp @@ -48,39 +48,8 @@ public: class MyLayer { public: - MyLayer() : - layer_type(sltUnknown), - print_z(0.), - bottom_z(0.), - height(0.), - idx_object_layer_above(size_t(-1)), - idx_object_layer_below(size_t(-1)), - bridging(false), - contact_polygons(nullptr), - overhang_polygons(nullptr) - {} - - ~MyLayer() - { - delete contact_polygons; - contact_polygons = nullptr; - delete overhang_polygons; - overhang_polygons = nullptr; - } - void reset() { - layer_type = sltUnknown; - print_z = 0.; - bottom_z = 0.; - height = 0.; - idx_object_layer_above = size_t(-1); - idx_object_layer_below = size_t(-1); - bridging = false; - polygons.clear(); - delete contact_polygons; - contact_polygons = nullptr; - delete overhang_polygons; - overhang_polygons = nullptr; + *this = MyLayer(); } bool operator==(const MyLayer &layer2) const { @@ -103,6 +72,21 @@ public: return false; } + void merge(MyLayer &&rhs) { + // The union_() does not support move semantic yet, but maybe one day it will. + this->polygons = union_(this->polygons, std::move(rhs.polygons)); + auto merge = [](std::unique_ptr &dst, std::unique_ptr &src) { + if (! dst || dst->empty()) + dst = std::move(src); + else if (src && ! src->empty()) + *dst = union_(*dst, std::move(*src)); + }; + merge(this->contact_polygons, rhs.contact_polygons); + merge(this->overhang_polygons, rhs.overhang_polygons); + merge(this->enforcer_polygons, rhs.enforcer_polygons); + rhs.reset(); + } + // For the bridging flow, bottom_print_z will be above bottom_z to account for the vertical separation. // For the non-bridging flow, bottom_print_z will be equal to bottom_z. coordf_t bottom_print_z() const { return print_z - height; } @@ -110,29 +94,44 @@ public: // To sort the extremes of top / bottom interface layers. coordf_t extreme_z() const { return (this->layer_type == sltTopContact) ? this->bottom_z : this->print_z; } - SupporLayerType layer_type; + SupporLayerType layer_type { sltUnknown }; // Z used for printing, in unscaled coordinates. - coordf_t print_z; + coordf_t print_z { 0 }; // Bottom Z of this layer. For soluble layers, bottom_z + height = print_z, // otherwise bottom_z + gap + height = print_z. - coordf_t bottom_z; + coordf_t bottom_z { 0 }; // Layer height in unscaled coordinates. - coordf_t height; + coordf_t height { 0 }; // Index of a PrintObject layer_id supported by this layer. This will be set for top contact layers. // If this is not a contact layer, it will be set to size_t(-1). - size_t idx_object_layer_above; + size_t idx_object_layer_above { size_t(-1) }; // Index of a PrintObject layer_id, which supports this layer. This will be set for bottom contact layers. // If this is not a contact layer, it will be set to size_t(-1). - size_t idx_object_layer_below; + size_t idx_object_layer_below { size_t(-1) }; // Use a bridging flow when printing this support layer. - bool bridging; + bool bridging { false }; // Polygons to be filled by the support pattern. Polygons polygons; // Currently for the contact layers only. - // MyLayer owns the contact_polygons and overhang_polygons, they are freed by the destructor. - Polygons *contact_polygons; - Polygons *overhang_polygons; + std::unique_ptr contact_polygons; + std::unique_ptr overhang_polygons; + // Enforcers need to be propagated independently in case the "support on build plate only" option is enabled. + std::unique_ptr enforcer_polygons; + }; + + struct SupportParams { + Flow first_layer_flow; + Flow support_material_flow; + Flow support_material_interface_flow; + Flow support_material_bottom_interface_flow; + // Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder? + bool can_merge_support_regions; + + coordf_t support_layer_height_min; + // coordf_t support_layer_height_max; + + coordf_t gap_xy; }; // Layers are allocated and owned by a deque. Once a layer is allocated, it is maintained @@ -159,17 +158,19 @@ public: void generate(PrintObject &object); private: + std::vector buildplate_covered(const PrintObject &object) const; + // Generate top contact layers supporting overhangs. // For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined. // If supports over bed surface only are requested, don't generate contact layers over an object. - MyLayersPtr top_contact_layers(const PrintObject &object, MyLayerStorage &layer_storage) const; + MyLayersPtr top_contact_layers(const PrintObject &object, const std::vector &buildplate_covered, MyLayerStorage &layer_storage) const; // Generate bottom contact layers supporting the top contact layers. // For a soluble interface material synchronize the layer heights with the object, // otherwise set the layer height to a bridging flow of a support interface nozzle. MyLayersPtr bottom_contact_layers_and_layer_support_areas( - const PrintObject &object, const MyLayersPtr &top_contacts, MyLayerStorage &layer_storage, - std::vector &layer_support_areas) const; + const PrintObject &object, const MyLayersPtr &top_contacts, std::vector &buildplate_covered, + MyLayerStorage &layer_storage, std::vector &layer_support_areas) const; // Trim the top_contacts layers with the bottom_contacts layers if they overlap, so there would not be enough vertical space for both of them. void trim_top_contacts_by_bottom_contacts(const PrintObject &object, const MyLayersPtr &bottom_contacts, MyLayersPtr &top_contacts) const; @@ -240,17 +241,8 @@ private: // Pre-calculated parameters shared between the object slicer and the support generator, // carrying information on a raft, 1st layer height, 1st object layer height, gap between the raft and object etc. SlicingParameters m_slicing_params; - - Flow m_first_layer_flow; - Flow m_support_material_flow; - Flow m_support_material_interface_flow; - // Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder? - bool m_can_merge_support_regions; - - coordf_t m_support_layer_height_min; -// coordf_t m_support_layer_height_max; - - coordf_t m_gap_xy; + // Various precomputed support parameters to be shared with external functions. + SupportParams m_support_params; }; } // namespace Slic3r diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 3ada1cd359..303ffe9274 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -4,7 +4,6 @@ //============= // debug techs //============= - // Shows camera target in the 3D scene #define ENABLE_SHOW_CAMERA_TARGET 0 // Log debug messages to console when changing selection @@ -27,39 +26,39 @@ #define ENABLE_GCODE_VIEWER_STATISTICS 0 // Enable G-Code viewer comparison between toolpaths height and width detected from gcode and calculated at gcode generation #define ENABLE_GCODE_VIEWER_DATA_CHECKING 0 + + // Enable rendering of objects using environment map #define ENABLE_ENVIRONMENT_MAP 0 // Enable smoothing of objects normals #define ENABLE_SMOOTH_NORMALS 0 +// Enable rendering markers for options in preview as fixed screen size points +#define ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS 1 //==================== -// 2.3.0.alpha4 techs +// 2.4.0.alpha0 techs //==================== -#define ENABLE_2_3_0_ALPHA4 1 +#define ENABLE_2_4_0_ALPHA0 1 -#define ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS (1 && ENABLE_2_3_0_ALPHA4) - - -//=================== -// 2.3.0.beta1 techs -//=================== -#define ENABLE_2_3_0_BETA1 1 - -#define ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN (1 && ENABLE_2_3_0_BETA1) - - -//==================== -// 2.3.1.alpha1 techs -//==================== -#define ENABLE_2_3_1_ALPHA1 1 - -#define ENABLE_SPLITTED_VERTEX_BUFFER (1 && ENABLE_2_3_1_ALPHA1) +// Enable splitting of vertex buffers used to render toolpaths +#define ENABLE_SPLITTED_VERTEX_BUFFER (1 && ENABLE_2_4_0_ALPHA0) +// Enable rendering only starting and final caps for toolpaths #define ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS (1 && ENABLE_SPLITTED_VERTEX_BUFFER) -#define ENABLE_RELOAD_FROM_DISK_FOR_3MF (1 && ENABLE_2_3_1_ALPHA1) -#define ENABLE_WARNING_TEXTURE_REMOVAL (1 && ENABLE_2_3_1_ALPHA1) -#define ENABLE_GCODE_LINES_ID_IN_H_SLIDER (1 && ENABLE_2_3_1_ALPHA1) -#define ENABLE_VALIDATE_CUSTOM_GCODE (1 && ENABLE_2_3_1_ALPHA1) +// Enable reload from disk command for 3mf files +#define ENABLE_RELOAD_FROM_DISK_FOR_3MF (1 && ENABLE_2_4_0_ALPHA0) +// Removes obsolete warning texture code +#define ENABLE_WARNING_TEXTURE_REMOVAL (1 && ENABLE_2_4_0_ALPHA0) +// Enable showing gcode line numbers in previeww horizontal slider +#define ENABLE_GCODE_LINES_ID_IN_H_SLIDER (1 && ENABLE_2_4_0_ALPHA0) +// Enable validation of custom gcode against gcode processor reserved keywords +#define ENABLE_VALIDATE_CUSTOM_GCODE (1 && ENABLE_2_4_0_ALPHA0) +// Enable showing a imgui window containing gcode in preview +#define ENABLE_GCODE_WINDOW (1 && ENABLE_2_4_0_ALPHA0) +// Enable exporting lines M73 for remaining time to next printer stop to gcode +#define ENABLE_EXTENDED_M73_LINES (1 && ENABLE_VALIDATE_CUSTOM_GCODE) +// Enable a modified version of automatic downscale on load of objects too big +#define ENABLE_MODIFIED_DOWNSCALE_ON_LOAD_OBJECTS_TOO_BIG (1 && ENABLE_2_4_0_ALPHA0) #endif // _prusaslicer_technologies_h_ diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index adb9be64d1..f8fa1ca17f 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -511,20 +511,22 @@ void TriangleMesh::merge(const TriangleMesh &mesh) //FIXME This could be extremely slow! Use it for tiny meshes only! ExPolygons TriangleMesh::horizontal_projection() const { - Polygons pp; - pp.reserve(this->stl.stats.number_of_facets); + ClipperLib::Paths paths; + Polygon p; + p.points.assign(3, Point()); + auto delta = scaled(0.01); + std::vector deltas { delta, delta, delta }; + paths.reserve(this->stl.stats.number_of_facets); 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.make_counter_clockwise(); // do this after scaling, as winding order might change while doing that - pp.emplace_back(p); + p.make_counter_clockwise(); + paths.emplace_back(mittered_offset_path_scaled(p.points, deltas, 3.)); } // the offset factor was tuned using groovemount.stl - return union_ex(offset(pp, scale_(0.01)), true); + return ClipperPaths_to_Slic3rExPolygons(paths); } // 2D convex hull of a 3D mesh projected into the Z=0 plane. @@ -1797,7 +1799,7 @@ void TriangleMeshSlicer::make_expolygons(const Polygons &loops, const float clos // append to the supplied collection if (safety_offset > 0) - expolygons_append(*slices, offset2_ex(union_(loops, false), +safety_offset, -safety_offset)); + expolygons_append(*slices, offset2_ex(union_ex(loops, false), +safety_offset, -safety_offset)); else expolygons_append(*slices, union_ex(loops, false)); } @@ -2063,4 +2065,22 @@ TriangleMesh make_sphere(double radius, double fa) return mesh; } +std::vector > create_neighbor_index(const indexed_triangle_set &its) +{ + if (its.vertices.empty()) return {}; + + size_t res = its.indices.size() / its.vertices.size(); + std::vector< std::vector > index(its.vertices.size(), + reserve_vector(res)); + + for (size_t fi = 0; fi < its.indices.size(); ++fi) { + auto &face = its.indices[fi]; + index[face(0)].emplace_back(fi); + index[face(1)].emplace_back(fi); + index[face(2)].emplace_back(fi); + } + + return index; +} + } diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 9625298f4e..e6f6dc84b8 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -89,6 +89,12 @@ private: std::deque find_unvisited_neighbors(std::vector &facet_visited) const; }; +// Create an index of faces belonging to each vertex. The returned vector can +// be indexed with vertex indices and contains a list of face indices for each +// vertex. +std::vector< std::vector > +create_neighbor_index(const indexed_triangle_set &its); + enum FacetEdgeType { // A general case, the cutting plane intersect a face at two different edges. feGeneral, diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 0c26d42c84..1ac45f1b52 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -6,6 +6,7 @@ #include #include +#include "Platform.hpp" #include "Time.hpp" #ifdef WIN32 @@ -19,9 +20,14 @@ #ifdef BSD #include #endif - #ifdef __APPLE__ + #ifdef __APPLE__ #include #endif + #ifdef __linux__ + #include + #include + #include + #endif #endif #include @@ -417,6 +423,140 @@ std::error_code rename_file(const std::string &from, const std::string &to) #endif } +#ifdef __linux__ +// Copied from boost::filesystem, to support copying a file to a weird filesystem, which does not support changing file attributes, +// for example ChromeOS Linux integration or FlashAIR WebDAV. +// Copied and simplified from boost::filesystem::detail::copy_file() with option = overwrite_if_exists and with just the Linux path kept, +// and only features supported by Linux 3.10 (on our build server with CentOS 7) are kept, namely sendfile with ranges and statx() are not supported. +bool copy_file_linux(const boost::filesystem::path &from, const boost::filesystem::path &to, boost::system::error_code &ec) +{ + using namespace boost::filesystem; + + struct fd_wrapper + { + int fd { -1 }; + fd_wrapper() = default; + explicit fd_wrapper(int fd) throw() : fd(fd) {} + ~fd_wrapper() throw() { if (fd >= 0) ::close(fd); } + }; + + ec.clear(); + int err = 0; + + // Note: Declare fd_wrappers here so that errno is not clobbered by close() that may be called in fd_wrapper destructors + fd_wrapper infile, outfile; + + while (true) { + infile.fd = ::open(from.c_str(), O_RDONLY | O_CLOEXEC); + if (infile.fd < 0) { + err = errno; + if (err == EINTR) + continue; + fail: + ec.assign(err, boost::system::system_category()); + return false; + } + break; + } + + struct ::stat from_stat; + if (::fstat(infile.fd, &from_stat) != 0) { + fail_errno: + err = errno; + goto fail; + } + + const mode_t from_mode = from_stat.st_mode; + if (!S_ISREG(from_mode)) { + err = ENOSYS; + goto fail; + } + + // Enable writing for the newly created files. Having write permission set is important e.g. for NFS, + // which checks the file permission on the server, even if the client's file descriptor supports writing. + mode_t to_mode = from_mode | S_IWUSR; + int oflag = O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC; + + while (true) { + outfile.fd = ::open(to.c_str(), oflag, to_mode); + if (outfile.fd < 0) { + err = errno; + if (err == EINTR) + continue; + goto fail; + } + break; + } + + struct ::stat to_stat; + if (::fstat(outfile.fd, &to_stat) != 0) + goto fail_errno; + + to_mode = to_stat.st_mode; + if (!S_ISREG(to_mode)) { + err = ENOSYS; + goto fail; + } + + if (from_stat.st_dev == to_stat.st_dev && from_stat.st_ino == to_stat.st_ino) { + err = EEXIST; + goto fail; + } + + //! copy_file implementation that uses sendfile loop. Requires sendfile to support file descriptors. + //FIXME Vojtech: This is a copy loop valid for Linux 2.6.33 and newer. + // copy_file_data_copy_file_range() supports cross-filesystem copying since 5.3, but Vojtech did not want to polute this + // function with that, we don't think the performance gain is worth it for the types of files we are copying, + // and our build server based on CentOS 7 with Linux 3.10 does not support that anyways. + { + // sendfile will not send more than this amount of data in one call + constexpr std::size_t max_send_size = 0x7ffff000u; + uintmax_t offset = 0u; + while (off_t(offset) < from_stat.st_size) { + uintmax_t size_left = from_stat.st_size - offset; + std::size_t size_to_copy = max_send_size; + if (size_left < static_cast(max_send_size)) + size_to_copy = static_cast(size_left); + ssize_t sz = ::sendfile(outfile.fd, infile.fd, nullptr, size_to_copy); + if (sz < 0) { + err = errno; + if (err == EINTR) + continue; + if (err == 0) + break; + goto fail; // err already contains the error code + } + offset += sz; + } + } + + // If we created a new file with an explicitly added S_IWUSR permission, + // we may need to update its mode bits to match the source file. + if (to_mode != from_mode && ::fchmod(outfile.fd, from_mode) != 0) { + if (platform_flavor() == PlatformFlavor::LinuxOnChromium) { + // Ignore that. 9p filesystem does not allow fmod(). + BOOST_LOG_TRIVIAL(info) << "copy_file_linux() failed to fchmod() the output file \"" << to.string() << "\" to " << from_mode << ": " << ec.message() << + " This may be expected when writing to a 9p filesystem."; + } else { + // Generic linux. Write out an error to console. At least we may get some feedback. + BOOST_LOG_TRIVIAL(error) << "copy_file_linux() failed to fchmod() the output file \"" << to.string() << "\" to " << from_mode << ": " << ec.message(); + } + } + + // Note: Use fsync/fdatasync followed by close to avoid dealing with the possibility of close failing with EINTR. + // Even if close fails, including with EINTR, most operating systems (presumably, except HP-UX) will close the + // file descriptor upon its return. This means that if an error happens later, when the OS flushes data to the + // underlying media, this error will go unnoticed and we have no way to receive it from close. Calling fsync/fdatasync + // ensures that all data have been written, and even if close fails for some unfathomable reason, we don't really + // care at that point. + err = ::fdatasync(outfile.fd); + if (err != 0) + goto fail_errno; + + return true; +} +#endif // __linux__ + CopyFileResult copy_file_inner(const std::string& from, const std::string& to, std::string& error_message) { const boost::filesystem::path source(from); @@ -434,7 +574,13 @@ CopyFileResult copy_file_inner(const std::string& from, const std::string& to, s if (ec) BOOST_LOG_TRIVIAL(debug) << "boost::filesystem::permisions before copy error message (this could be irrelevant message based on file system): " << ec.message(); ec.clear(); +#ifdef __linux__ + // We want to allow copying files on Linux to succeed even if changing the file attributes fails. + // That may happen when copying on some exotic file system, for example Linux on Chrome. + copy_file_linux(source, target, ec); +#else // __linux__ boost::filesystem::copy_file(source, target, boost::filesystem::copy_option::overwrite_if_exists, ec); +#endif // __linux__ if (ec) { error_message = ec.message(); return FAIL_COPY_FILE; diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 505c8f0f21..4b3a1c6ca9 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -93,6 +93,8 @@ set(SLIC3R_GUI_SOURCES GUI/SavePresetDialog.cpp GUI/PhysicalPrinterDialog.hpp GUI/PhysicalPrinterDialog.cpp + GUI/GUI_Factories.cpp + GUI/GUI_Factories.hpp GUI/GUI_ObjectList.cpp GUI/GUI_ObjectList.hpp GUI/GUI_ObjectManipulation.cpp diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 74b58b052a..1c43e7eb04 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -113,7 +113,7 @@ void Bed3D::Axes::render() const glsafe(::glPopMatrix()); }; - m_arrow.init_from(stilized_arrow(16, DefaultTipRadius, DefaultTipLength, DefaultStemRadius, m_stem_length)); + const_cast(&m_arrow)->init_from(stilized_arrow(16, DefaultTipRadius, DefaultTipLength, DefaultStemRadius, m_stem_length)); GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader == nullptr) @@ -143,13 +143,6 @@ void Bed3D::Axes::render() const glsafe(::glDisable(GL_DEPTH_TEST)); } -Bed3D::Bed3D() - : m_type(Custom) - , m_vbo_id(0) - , m_scale_factor(1.0f) -{ -} - bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom) { auto check_texture = [](const std::string& texture) { @@ -193,7 +186,7 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, c ExPolygon poly; for (const Vec2d& p : m_shape) { - poly.contour.append(Point(scale_(p(0)), scale_(p(1)))); + poly.contour.append({ scale_(p(0)), scale_(p(1)) }); } calc_triangles(poly); @@ -228,7 +221,8 @@ Point Bed3D::point_projection(const Point& point) const void Bed3D::render(GLCanvas3D& canvas, bool bottom, float scale_factor, bool show_axes, bool show_texture) const { - m_scale_factor = scale_factor; + float* factor = const_cast(&m_scale_factor); + *factor = scale_factor; if (show_axes) render_axes(); @@ -247,22 +241,24 @@ void Bed3D::render(GLCanvas3D& canvas, bool bottom, float scale_factor, void Bed3D::calc_bounding_boxes() const { - m_bounding_box = BoundingBoxf3(); + BoundingBoxf3* bounding_box = const_cast(&m_bounding_box); + *bounding_box = BoundingBoxf3(); for (const Vec2d& p : m_shape) { - m_bounding_box.merge(Vec3d(p(0), p(1), 0.0)); + bounding_box->merge({ p(0), p(1), 0.0 }); } - m_extended_bounding_box = m_bounding_box; + BoundingBoxf3* extended_bounding_box = const_cast(&m_extended_bounding_box); + *extended_bounding_box = m_bounding_box; // extend to contain axes - m_extended_bounding_box.merge(m_axes.get_origin() + m_axes.get_total_length() * Vec3d::Ones()); - m_extended_bounding_box.merge(m_extended_bounding_box.min + Vec3d(-Axes::DefaultTipRadius, -Axes::DefaultTipRadius, m_extended_bounding_box.max(2))); + extended_bounding_box->merge(m_axes.get_origin() + m_axes.get_total_length() * Vec3d::Ones()); + extended_bounding_box->merge(extended_bounding_box->min + Vec3d(-Axes::DefaultTipRadius, -Axes::DefaultTipRadius, extended_bounding_box->max(2))); // extend to contain model, if any BoundingBoxf3 model_bb = m_model.get_bounding_box(); if (model_bb.defined) { model_bb.translate(m_model_offset); - m_extended_bounding_box.merge(model_bb); + extended_bounding_box->merge(model_bb); } } @@ -339,21 +335,24 @@ void Bed3D::render_system(GLCanvas3D& canvas, bool bottom, bool show_texture) co void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const { + GLTexture* texture = const_cast(&m_texture); + GLTexture* temp_texture = const_cast(&m_temp_texture); + if (m_texture_filename.empty()) { - m_texture.reset(); + texture->reset(); render_default(bottom); return; } - if ((m_texture.get_id() == 0) || (m_texture.get_source() != m_texture_filename)) { - m_texture.reset(); + if (texture->get_id() == 0 || texture->get_source() != m_texture_filename) { + texture->reset(); if (boost::algorithm::iends_with(m_texture_filename, ".svg")) { // use higher resolution images if graphic card and opengl version allow GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size(); - if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != m_texture_filename)) { + if (temp_texture->get_id() == 0 || temp_texture->get_source() != m_texture_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(m_texture_filename, false, false, false, max_tex_size / 8)) { + if (!temp_texture->load_from_svg_file(m_texture_filename, false, false, false, max_tex_size / 8)) { render_default(bottom); return; } @@ -361,15 +360,15 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const } // starts generating the main texture, compression will run asynchronously - if (!m_texture.load_from_svg_file(m_texture_filename, true, true, true, max_tex_size)) { + if (!texture->load_from_svg_file(m_texture_filename, true, true, true, max_tex_size)) { render_default(bottom); return; } } else if (boost::algorithm::iends_with(m_texture_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() != m_texture_filename)) { - if (!m_temp_texture.load_from_file(m_texture_filename, false, GLTexture::None, false)) { + if (temp_texture->get_id() == 0 || temp_texture->get_source() != m_texture_filename) { + if (!temp_texture->load_from_file(m_texture_filename, false, GLTexture::None, false)) { render_default(bottom); return; } @@ -377,7 +376,7 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const } // starts generating the main texture, compression will run asynchronously - if (!m_texture.load_from_file(m_texture_filename, true, GLTexture::MultiThreaded, true)) { + if (!texture->load_from_file(m_texture_filename, true, GLTexture::MultiThreaded, true)) { render_default(bottom); return; } @@ -387,13 +386,13 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const return; } } - else if (m_texture.unsent_compressed_data_available()) { + else if (texture->unsent_compressed_data_available()) { // sends to gpu the already available compressed levels of the main texture - m_texture.send_compressed_data_to_gpu(); + texture->send_compressed_data_to_gpu(); // the temporary texture is not needed anymore, reset it - if (m_temp_texture.get_id() != 0) - m_temp_texture.reset(); + if (temp_texture->get_id() != 0) + temp_texture->reset(); canvas.request_extra_frame(); @@ -406,9 +405,11 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const shader->set_uniform("transparent_background", bottom); shader->set_uniform("svg_source", boost::algorithm::iends_with(m_texture.get_source(), ".svg")); - if (m_vbo_id == 0) { - glsafe(::glGenBuffers(1, &m_vbo_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id)); + unsigned int* vbo_id = const_cast(&m_vbo_id); + + if (*vbo_id == 0) { + glsafe(::glGenBuffers(1, vbo_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, *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)); } @@ -428,12 +429,12 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const GLint tex_coords_id = 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(); + GLuint tex_id = (GLuint)temp_texture->get_id(); if (tex_id == 0) - tex_id = (GLuint)m_texture.get_id(); + tex_id = (GLuint)texture->get_id(); glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, *vbo_id)); if (position_id != -1) { glsafe(::glEnableVertexAttribArray(position_id)); @@ -471,24 +472,26 @@ void Bed3D::render_model() const if (m_model_filename.empty()) return; - if ((m_model.get_filename() != m_model_filename) && m_model.init_from_file(m_model_filename)) { + GLModel* model = const_cast(&m_model); + + if (model->get_filename() != m_model_filename && model->init_from_file(m_model_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_offset = shift; + *const_cast(&m_model_offset) = shift; // update extended bounding box calc_bounding_boxes(); } - if (!m_model.get_filename().empty()) { + if (!model->get_filename().empty()) { GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader != nullptr) { shader->start_using(); shader->set_uniform("uniform_color", m_model_color); ::glPushMatrix(); ::glTranslated(m_model_offset(0), m_model_offset(1), m_model_offset(2)); - m_model.render(); + model->render(); ::glPopMatrix(); shader->stop_using(); } @@ -511,7 +514,7 @@ void Bed3D::render_custom(GLCanvas3D& canvas, bool bottom, bool show_texture) co void Bed3D::render_default(bool bottom) const { - m_texture.reset(); + const_cast(&m_texture)->reset(); unsigned int triangles_vcount = m_triangles.get_vertices_count(); if (triangles_vcount > 0) { diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 6266304bee..c2630b7993 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -48,7 +48,7 @@ class Bed3D private: Vec3d m_origin{ Vec3d::Zero() }; float m_stem_length{ DefaultStemLength }; - mutable GLModel m_arrow; + GLModel m_arrow; public: const Vec3d& get_origin() const { return m_origin; } @@ -67,28 +67,28 @@ public: }; private: - EType m_type; + EType m_type{ Custom }; Pointfs m_shape; std::string m_texture_filename; std::string m_model_filename; - mutable BoundingBoxf3 m_bounding_box; - mutable BoundingBoxf3 m_extended_bounding_box; + BoundingBoxf3 m_bounding_box; + BoundingBoxf3 m_extended_bounding_box; Polygon m_polygon; GeometryBuffer m_triangles; GeometryBuffer m_gridlines; - mutable GLTexture m_texture; - mutable GLModel m_model; - mutable Vec3d m_model_offset{ Vec3d::Zero() }; - std::array m_model_color{ 0.235f, 0.235f, 0.235f, 1.0f }; + GLTexture m_texture; // temporary texture shown until the main texture has still no levels compressed - mutable GLTexture m_temp_texture; - mutable unsigned int m_vbo_id; + GLTexture m_temp_texture; + GLModel m_model; + Vec3d m_model_offset{ Vec3d::Zero() }; + std::array m_model_color{ 0.235f, 0.235f, 0.235f, 1.0f }; + unsigned int m_vbo_id{ 0 }; Axes m_axes; - mutable float m_scale_factor; + float m_scale_factor{ 1.0f }; public: - Bed3D(); + Bed3D() = default; ~Bed3D() { reset(); } EType get_type() const { return m_type; } diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 04301061db..ba62576f23 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -421,20 +421,24 @@ const BoundingBoxf3& GLVolume::transformed_bounding_box() const 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 = box.transformed(world_matrix()); - m_transformed_bounding_box_dirty = false; + BoundingBoxf3* transformed_bounding_box = const_cast(&m_transformed_bounding_box); + bool* transformed_bounding_box_dirty = const_cast(&m_transformed_bounding_box_dirty); + if (*transformed_bounding_box_dirty) { + *transformed_bounding_box = box.transformed(world_matrix()); + *transformed_bounding_box_dirty = false; } - - return m_transformed_bounding_box; + return *transformed_bounding_box; } const BoundingBoxf3& GLVolume::transformed_convex_hull_bounding_box() const { - if (m_transformed_convex_hull_bounding_box_dirty) - m_transformed_convex_hull_bounding_box = this->transformed_convex_hull_bounding_box(world_matrix()); - return m_transformed_convex_hull_bounding_box; + BoundingBoxf3* transformed_convex_hull_bounding_box = const_cast(&m_transformed_convex_hull_bounding_box); + bool* transformed_convex_hull_bounding_box_dirty = const_cast(&m_transformed_convex_hull_bounding_box_dirty); + if (*transformed_convex_hull_bounding_box_dirty) { + *transformed_convex_hull_bounding_box = this->transformed_convex_hull_bounding_box(world_matrix()); + *transformed_convex_hull_bounding_box_dirty = false; + } + return *transformed_convex_hull_bounding_box; } BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box(const Transform3d &trafo) const @@ -610,25 +614,28 @@ void GLVolumeCollection::load_object_auxiliary( } 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 size_unknown, float brim_width, bool opengl_initialized) + 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); if (height == 0.0f) height = 0.1f; - Point origin_of_rotation(0.f, 0.f); + TriangleMesh mesh; float color[4] = { 0.5f, 0.5f, 0.0f, 1.f }; - // In case we don't know precise dimensions of the wipe tower yet, we'll draw the box with different color with one side jagged: + // In case we don't know precise dimensions of the wipe tower yet, we'll draw + // the box with different color with one side jagged: if (size_unknown) { color[0] = 0.9f; color[1] = 0.6f; - depth = std::max(depth, 10.f); // Too narrow tower would interfere with the teeth. The estimate is not precise anyway. + // Too narrow tower would interfere with the teeth. The estimate is not precise anyway. + depth = std::max(depth, 10.f); float min_width = 30.f; - // We'll now create the box with jagged edge. y-coordinates of the pre-generated model are shifted so that the front - // edge has y=0 and centerline of the back edge has y=depth: + // We'll now create the box with jagged edge. y-coordinates of the pre-generated model + // are shifted so that the front edge has y=0 and centerline of the back edge has y=depth: Pointf3s points; std::vector facets; float out_points_idx[][3] = { { 0, -depth, 0 }, { 0, 0, 0 }, { 38.453f, 0, 0 }, { 61.547f, 0, 0 }, { 100.0f, 0, 0 }, { 100.0f, -depth, 0 }, { 55.7735f, -10.0f, 0 }, { 44.2265f, 10.0f, 0 }, @@ -637,13 +644,17 @@ int GLVolumeCollection::load_wipe_tower_preview( {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.emplace_back(out_points_idx[i][0] / (100.f / min_width), out_points_idx[i][1] + depth, out_points_idx[i][2]); + points.emplace_back(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.emplace_back(out_facets_idx[i][0], out_facets_idx[i][1], out_facets_idx[i][2]); + facets.emplace_back(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. + // 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; i < n; ++i) { mesh.merge(tooth_mesh); @@ -788,7 +799,56 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab glsafe(::glDisable(GL_BLEND)); } -bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state) +bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state) const +{ + if (config == nullptr) + return false; + + const ConfigOptionPoints* opt = dynamic_cast(config->option("bed_shape")); + if (opt == nullptr) + return false; + + BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values)); + BoundingBoxf3 print_volume({ unscale(bed_box_2D.min(0)), unscale(bed_box_2D.min(1)), 0.0 }, { unscale(bed_box_2D.max(0)), unscale(bed_box_2D.max(1)), config->opt_float("max_print_height") }); + // Allow the objects to protrude below the print bed + print_volume.min(2) = -1e10; + print_volume.min(0) -= BedEpsilon; + print_volume.min(1) -= BedEpsilon; + print_volume.max(0) += BedEpsilon; + print_volume.max(1) += BedEpsilon; + + ModelInstanceEPrintVolumeState state = ModelInstancePVS_Inside; + + bool contained_min_one = false; + + for (GLVolume* volume : this->volumes) { + if (volume == nullptr || volume->is_modifier || (volume->is_wipe_tower && !volume->shader_outside_printer_detection_enabled) || (volume->composite_id.volume_id < 0 && !volume->shader_outside_printer_detection_enabled)) + continue; + + const BoundingBoxf3& bb = volume->transformed_convex_hull_bounding_box(); + bool contained = print_volume.contains(bb); + + volume->is_outside = !contained; + if (!volume->printable) + continue; + + if (contained) + contained_min_one = true; + + if (state == ModelInstancePVS_Inside && volume->is_outside) + state = ModelInstancePVS_Fully_Outside; + + if (state == ModelInstancePVS_Fully_Outside && volume->is_outside && print_volume.intersects(bb)) + state = ModelInstancePVS_Partly_Outside; + } + + if (out_state != nullptr) + *out_state = state; + + return contained_min_one; +} + +bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, bool& partlyOut, bool& fullyOut) const { if (config == nullptr) return false; @@ -806,13 +866,12 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M print_volume.max(0) += BedEpsilon; print_volume.max(1) += BedEpsilon; - ModelInstanceEPrintVolumeState state = ModelInstancePVS_Inside; - bool contained_min_one = false; - for (GLVolume* volume : this->volumes) - { - if ((volume == nullptr) || volume->is_modifier || (volume->is_wipe_tower && !volume->shader_outside_printer_detection_enabled) || ((volume->composite_id.volume_id < 0) && !volume->shader_outside_printer_detection_enabled)) + partlyOut = false; + fullyOut = false; + for (GLVolume* volume : this->volumes) { + if (volume == nullptr || volume->is_modifier || (volume->is_wipe_tower && !volume->shader_outside_printer_detection_enabled) || (volume->composite_id.volume_id < 0 && !volume->shader_outside_printer_detection_enabled)) continue; const BoundingBoxf3& bb = volume->transformed_convex_hull_bounding_box(); @@ -825,16 +884,17 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M if (contained) contained_min_one = true; - if ((state == ModelInstancePVS_Inside) && volume->is_outside) - state = ModelInstancePVS_Fully_Outside; - - if ((state == ModelInstancePVS_Fully_Outside) && volume->is_outside && print_volume.intersects(bb)) - state = ModelInstancePVS_Partly_Outside; + if (volume->is_outside) { + if (print_volume.intersects(bb)) + partlyOut = true; + else + fullyOut = true; + } } - + /* if (out_state != nullptr) *out_state = state; - + */ return contained_min_one; } diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index e4d9c60672..25c5443cda 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -267,15 +267,15 @@ private: // Shift in z required by sla supports+pad double m_sla_shift_z; // Bounding box of this volume, in unscaled coordinates. - mutable BoundingBoxf3 m_transformed_bounding_box; + BoundingBoxf3 m_transformed_bounding_box; // Whether or not is needed to recalculate the transformed bounding box. - mutable bool m_transformed_bounding_box_dirty; + bool m_transformed_bounding_box_dirty; // 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; + 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; + bool m_transformed_convex_hull_bounding_box_dirty; public: // Color of the triangles / quads held by this volume. @@ -568,7 +568,8 @@ public: // returns true if all the volumes are completely contained in the print volume // returns the containment state in the given out_state, if non-null - bool check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state); + bool check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state) const; + bool check_outside_state(const DynamicPrintConfig* config, bool& partlyOut, bool& fullyOut) const; void reset_outside_state(); void update_colors_by_extruder(const DynamicPrintConfig* config); diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 87a3220232..089fba6566 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -430,10 +430,10 @@ bool BackgroundSlicingProcess::empty() const return m_print->empty(); } -std::string BackgroundSlicingProcess::validate() +std::string BackgroundSlicingProcess::validate(std::string* warning) { assert(m_print != nullptr); - return m_print->validate(); + return m_print->validate(warning); } // Apply config over the print. Returns false, if the new config values caused any of the already diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index b3f8a0a6bb..d3819f15c6 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -131,7 +131,7 @@ public: bool empty() const; // Validate the print. Returns an empty string if valid, returns an error message if invalid. // Call validate before calling start(). - std::string validate(); + std::string validate(std::string* warning = nullptr); // Set the export path of the G-code. // Once the path is set, the G-code diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index ade1d40437..d8b80f1ce8 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -21,12 +21,6 @@ double Camera::FrustrumMinNearZ = 100.0; double Camera::FrustrumZMargin = 10.0; double Camera::MaxFovDeg = 60.0; -Camera::Camera() - : requires_zoom_to_bed(false) -{ - set_default_orientation(); -} - std::string Camera::get_type_as_string() const { switch (m_type) @@ -49,11 +43,6 @@ void Camera::set_type(EType type) } } -void Camera::set_type(const std::string& type) -{ - set_type((type == "1") ? Perspective : Ortho); -} - void Camera::select_next_type() { unsigned char next = (unsigned char)m_type + 1; @@ -65,24 +54,18 @@ void Camera::select_next_type() void Camera::set_target(const Vec3d& target) { - Vec3d new_target = validate_target(target); - Vec3d new_displacement = new_target - m_target; - if (!new_displacement.isApprox(Vec3d::Zero())) - { + const Vec3d new_target = validate_target(target); + const Vec3d new_displacement = new_target - m_target; + if (!new_displacement.isApprox(Vec3d::Zero())) { m_target = new_target; m_view_matrix.translate(-new_displacement); } } -void Camera::update_zoom(double delta_zoom) -{ - set_zoom(m_zoom / (1.0 - std::max(std::min(delta_zoom, 4.0), -4.0) * 0.1)); -} - void Camera::set_zoom(double zoom) { // Don't allow to zoom too far outside the scene. - double zoom_min = min_zoom(); + const double zoom_min = min_zoom(); if (zoom_min > 0.0) zoom = std::max(zoom, zoom_min); @@ -123,7 +106,7 @@ double Camera::get_fov() const void Camera::apply_viewport(int x, int y, unsigned int w, unsigned int h) const { glsafe(::glViewport(0, 0, w, h)); - glsafe(::glGetIntegerv(GL_VIEWPORT, m_viewport.data())); + glsafe(::glGetIntegerv(GL_VIEWPORT, const_cast*>(&m_viewport)->data())); } void Camera::apply_view_matrix() const @@ -138,22 +121,23 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa double w = 0.0; double h = 0.0; - double old_distance = m_distance; - m_frustrum_zs = calc_tight_frustrum_zs_around(box); + const double old_distance = m_distance; + std::pair* frustrum_zs = const_cast*>(&m_frustrum_zs); + *frustrum_zs = calc_tight_frustrum_zs_around(box); if (m_distance != old_distance) // the camera has been moved re-apply view matrix apply_view_matrix(); if (near_z > 0.0) - m_frustrum_zs.first = std::max(std::min(m_frustrum_zs.first, near_z), FrustrumMinNearZ); + frustrum_zs->first = std::max(std::min(frustrum_zs->first, near_z), FrustrumMinNearZ); if (far_z > 0.0) - m_frustrum_zs.second = std::max(m_frustrum_zs.second, far_z); + frustrum_zs->second = std::max(frustrum_zs->second, far_z); w = 0.5 * (double)m_viewport[2]; h = 0.5 * (double)m_viewport[3]; - double inv_zoom = get_inv_zoom(); + const double inv_zoom = get_inv_zoom(); w *= inv_zoom; h *= inv_zoom; @@ -162,16 +146,16 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa default: case Ortho: { - m_gui_scale = 1.0; + *const_cast(&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; + const double scale = frustrum_zs->first / m_distance; w *= scale; h *= scale; - m_gui_scale = scale; + *const_cast(&m_gui_scale) = scale; break; } } @@ -184,26 +168,25 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa default: case Ortho: { - glsafe(::glOrtho(-w, w, -h, h, m_frustrum_zs.first, m_frustrum_zs.second)); + glsafe(::glOrtho(-w, w, -h, h, frustrum_zs->first, frustrum_zs->second)); break; } case Perspective: { - glsafe(::glFrustum(-w, w, -h, h, m_frustrum_zs.first, m_frustrum_zs.second)); + glsafe(::glFrustum(-w, w, -h, h, frustrum_zs->first, frustrum_zs->second)); break; } } - glsafe(::glGetDoublev(GL_PROJECTION_MATRIX, m_projection_matrix.data())); + glsafe(::glGetDoublev(GL_PROJECTION_MATRIX, const_cast(&m_projection_matrix)->data())); glsafe(::glMatrixMode(GL_MODELVIEW)); } void Camera::zoom_to_box(const BoundingBoxf3& box, double margin_factor) { // Calculate the zoom factor needed to adjust the view around the given box. - double zoom = calc_zoom_to_bounding_box_factor(box, margin_factor); - if (zoom > 0.0) - { + const double zoom = calc_zoom_to_bounding_box_factor(box, margin_factor); + if (zoom > 0.0) { m_zoom = zoom; // center view around box center set_target(box.center()); @@ -213,9 +196,8 @@ void Camera::zoom_to_box(const BoundingBoxf3& box, double margin_factor) void Camera::zoom_to_volumes(const GLVolumePtrs& volumes, double margin_factor) { Vec3d center; - double zoom = calc_zoom_to_volumes_factor(volumes, center, margin_factor); - if (zoom > 0.0) - { + const double zoom = calc_zoom_to_volumes_factor(volumes, center, margin_factor); + if (zoom > 0.0) { m_zoom = zoom; // center view around the calculated center set_target(center); @@ -289,8 +271,8 @@ void Camera::rotate_on_sphere(double delta_azimut_rad, double delta_zenit_rad, b } } - Vec3d translation = m_view_matrix.translation() + m_view_rotation * m_target; - auto rot_z = Eigen::AngleAxisd(delta_azimut_rad, Vec3d::UnitZ()); + const Vec3d translation = m_view_matrix.translation() + m_view_rotation * m_target; + const auto rot_z = Eigen::AngleAxisd(delta_azimut_rad, Vec3d::UnitZ()); m_view_rotation *= rot_z * Eigen::AngleAxisd(delta_zenit_rad, rot_z.inverse() * get_dir_right()); m_view_rotation.normalize(); m_view_matrix.fromPositionOrientationScale(m_view_rotation * (- m_target) + translation, m_view_rotation, Vec3d(1., 1., 1.)); @@ -299,10 +281,10 @@ void Camera::rotate_on_sphere(double delta_azimut_rad, double delta_zenit_rad, b // Virtual trackball, rotate around an axis, where the eucledian norm of the axis gives the rotation angle in radians. void Camera::rotate_local_around_target(const Vec3d& rotation_rad) { - double angle = rotation_rad.norm(); + const double angle = rotation_rad.norm(); if (std::abs(angle) > EPSILON) { - Vec3d translation = m_view_matrix.translation() + m_view_rotation * m_target; - Vec3d axis = m_view_rotation.conjugate() * rotation_rad.normalized(); + const Vec3d translation = m_view_matrix.translation() + m_view_rotation * m_target; + const Vec3d axis = m_view_rotation.conjugate() * rotation_rad.normalized(); m_view_rotation *= Eigen::Quaterniond(Eigen::AngleAxisd(angle, axis)); m_view_rotation.normalize(); m_view_matrix.fromPositionOrientationScale(m_view_rotation * (-m_target) + translation, m_view_rotation, Vec3d(1., 1., 1.)); @@ -310,18 +292,13 @@ void Camera::rotate_local_around_target(const Vec3d& rotation_rad) } } -double Camera::min_zoom() const -{ - return 0.7 * calc_zoom_to_bounding_box_factor(m_scene_box); -} - std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const { std::pair ret; auto& [near_z, far_z] = ret; // box in eye space - BoundingBoxf3 eye_box = box.transformed(m_view_matrix); + const BoundingBoxf3 eye_box = box.transformed(m_view_matrix); near_z = -eye_box.max(2); far_z = -eye_box.min(2); @@ -330,73 +307,71 @@ std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBo far_z += FrustrumZMargin; // ensure min size - if (far_z - near_z < FrustrumMinZRange) - { - double mid_z = 0.5 * (near_z + far_z); - double half_size = 0.5 * FrustrumMinZRange; + if (far_z - near_z < FrustrumMinZRange) { + const double mid_z = 0.5 * (near_z + far_z); + const double half_size = 0.5 * FrustrumMinZRange; near_z = mid_z - half_size; far_z = mid_z + half_size; } - if (near_z < FrustrumMinNearZ) - { - float delta = FrustrumMinNearZ - near_z; + if (near_z < FrustrumMinNearZ) { + const double delta = FrustrumMinNearZ - near_z; set_distance(m_distance + delta); near_z += delta; far_z += delta; } - else if ((near_z > 2.0 * FrustrumMinNearZ) && (m_distance > DefaultDistance)) - { - float delta = m_distance - DefaultDistance; - set_distance(DefaultDistance); - near_z -= delta; - far_z -= delta; - } +// The following is commented out because it causes flickering of the 3D scene GUI +// when the bounding box of the scene gets large enough +// We need to introduce some smarter code to move the camera back and forth in such case +// else if (near_z > 2.0 * FrustrumMinNearZ && m_distance > DefaultDistance) { +// float delta = m_distance - DefaultDistance; +// set_distance(DefaultDistance); +// near_z -= delta; +// far_z -= delta; +// } return ret; } double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, double margin_factor) const { - double max_bb_size = box.max_size(); + 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 - Vec3d right = get_dir_right(); - Vec3d up = get_dir_up(); - Vec3d forward = get_dir_forward(); - - Vec3d bb_center = box.center(); + const Vec3d right = get_dir_right(); + const Vec3d up = get_dir_up(); + const Vec3d forward = get_dir_forward(); + const 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)); + const std::vector vertices = { + box.min, + { box.max(0), box.min(1), box.min(2) }, + { box.max(0), box.max(1), box.min(2) }, + { box.min(0), box.max(1), box.min(2) }, + { box.min(0), box.min(1), box.max(2) }, + { box.max(0), box.min(1), box.max(2) }, + box.max, + { box.min(0), box.max(1), box.max(2) } + }; double min_x = DBL_MAX; double min_y = DBL_MAX; double max_x = -DBL_MAX; double max_y = -DBL_MAX; - for (const Vec3d& v : vertices) - { + for (const Vec3d& v : vertices) { // project vertex on the plane perpendicular to camera forward axis - Vec3d pos = v - bb_center; - Vec3d proj_on_plane = pos - pos.dot(forward) * forward; + const Vec3d pos = v - bb_center; + const 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); + const double x_on_plane = proj_on_plane.dot(right); + const double y_on_plane = proj_on_plane.dot(up); min_x = std::min(min_x, x_on_plane); min_y = std::min(min_y, y_on_plane); @@ -406,7 +381,7 @@ double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, double double dx = max_x - min_x; double dy = max_y - min_y; - if ((dx <= 0.0) || (dy <= 0.0)) + if (dx <= 0.0 || dy <= 0.0) return -1.0f; dx *= margin_factor; @@ -423,13 +398,12 @@ double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, Vec3d& c // project the volumes vertices on a plane perpendicular to the camera forward axis // then calculates the vertices coordinate on this plane along the camera xy axes - Vec3d right = get_dir_right(); - Vec3d up = get_dir_up(); - Vec3d forward = get_dir_forward(); + const Vec3d right = get_dir_right(); + const Vec3d up = get_dir_up(); + const Vec3d forward = get_dir_forward(); BoundingBoxf3 box; - for (const GLVolume* volume : volumes) - { + for (const GLVolume* volume : volumes) { box.merge(volume->transformed_bounding_box()); } center = box.center(); @@ -439,24 +413,22 @@ double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, Vec3d& c double max_x = -DBL_MAX; double max_y = -DBL_MAX; - for (const GLVolume* volume : volumes) - { + for (const GLVolume* volume : volumes) { const Transform3d& transform = volume->world_matrix(); const TriangleMesh* hull = volume->convex_hull(); if (hull == nullptr) continue; - for (const Vec3f& vertex : hull->its.vertices) - { - Vec3d v = transform * vertex.cast(); + for (const Vec3f& vertex : hull->its.vertices) { + const Vec3d v = transform * vertex.cast(); // project vertex on the plane perpendicular to camera forward axis - Vec3d pos = v - center; - Vec3d proj_on_plane = pos - pos.dot(forward) * forward; + const Vec3d pos = v - center; + const 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); + const double x_on_plane = proj_on_plane.dot(right); + const double y_on_plane = proj_on_plane.dot(up); min_x = std::min(min_x, x_on_plane); min_y = std::min(min_y, y_on_plane); @@ -467,10 +439,10 @@ double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, Vec3d& c center += 0.5 * (max_x + min_x) * right + 0.5 * (max_y + min_y) * up; - double dx = margin_factor * (max_x - min_x); - double dy = margin_factor * (max_y - min_y); + const double dx = margin_factor * (max_x - min_x); + const double dy = margin_factor * (max_y - min_y); - if ((dx <= 0.0) || (dy <= 0.0)) + if (dx <= 0.0 || dy <= 0.0) return -1.0f; return std::min((double)m_viewport[2] / dx, (double)m_viewport[3] / dy); @@ -478,22 +450,21 @@ double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, Vec3d& c void Camera::set_distance(double distance) const { - if (m_distance != distance) - { - m_view_matrix.translate((distance - m_distance) * get_dir_forward()); - m_distance = distance; + if (m_distance != distance) { + const_cast(&m_view_matrix)->translate((distance - m_distance) * get_dir_forward()); + *const_cast(&m_distance) = distance; } } void Camera::look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up) { - Vec3d unit_z = (position - target).normalized(); - Vec3d unit_x = up.cross(unit_z).normalized(); - Vec3d unit_y = unit_z.cross(unit_x).normalized(); + const Vec3d unit_z = (position - target).normalized(); + const Vec3d unit_x = up.cross(unit_z).normalized(); + const Vec3d unit_y = unit_z.cross(unit_x).normalized(); m_target = target; m_distance = (position - target).norm(); - Vec3d new_position = m_target + m_distance * unit_z; + const Vec3d new_position = m_target + m_distance * unit_z; m_view_matrix(0, 0) = unit_x(0); m_view_matrix(0, 1) = unit_x(1); @@ -525,10 +496,10 @@ void Camera::look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up void Camera::set_default_orientation() { m_zenit = 45.0f; - double theta_rad = Geometry::deg2rad(-(double)m_zenit); - double phi_rad = Geometry::deg2rad(45.0); - 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)); + const double theta_rad = Geometry::deg2rad(-(double)m_zenit); + const double phi_rad = Geometry::deg2rad(45.0); + const double sin_theta = ::sin(theta_rad); + const Vec3d camera_pos = m_target + m_distance * Vec3d(sin_theta * ::sin(phi_rad), sin_theta * ::cos(phi_rad), ::cos(theta_rad)); m_view_rotation = Eigen::AngleAxisd(theta_rad, Vec3d::UnitX()) * Eigen::AngleAxisd(phi_rad, Vec3d::UnitZ()); m_view_rotation.normalize(); m_view_matrix.fromPositionOrientationScale(m_view_rotation * (- camera_pos), m_view_rotation, Vec3d(1., 1., 1.)); @@ -543,9 +514,9 @@ Vec3d Camera::validate_target(const Vec3d& target) const test_box.scale(ScaleFactor); test_box.translate(m_scene_box.center()); - return Vec3d(std::clamp(target(0), test_box.min(0), test_box.max(0)), - std::clamp(target(1), test_box.min(1), test_box.max(1)), - std::clamp(target(2), test_box.min(2), test_box.max(2))); + return { std::clamp(target(0), test_box.min(0), test_box.max(0)), + std::clamp(target(1), test_box.min(1), test_box.max(1)), + std::clamp(target(2), test_box.min(2), test_box.max(2)) }; } void Camera::update_zenit() diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index 91f4661b46..1abb430ca9 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -26,7 +26,7 @@ struct Camera Num_types }; - bool requires_zoom_to_bed; + bool requires_zoom_to_bed{ false }; private: EType m_type{ Perspective }; @@ -35,26 +35,26 @@ private: float m_zenit{ 45.0f }; double m_zoom{ 1.0 }; // Distance between camera position and camera target measured along the camera Z axis - mutable double m_distance{ DefaultDistance }; - mutable double m_gui_scale{ 1.0 }; + double m_distance{ DefaultDistance }; + double m_gui_scale{ 1.0 }; - mutable std::array m_viewport; - mutable Transform3d m_view_matrix{ Transform3d::Identity() }; + std::array m_viewport; + Transform3d m_view_matrix{ Transform3d::Identity() }; // We are calculating the rotation part of the m_view_matrix from m_view_rotation. - mutable Eigen::Quaterniond m_view_rotation{ 1.0, 0.0, 0.0, 0.0 }; - mutable Transform3d m_projection_matrix{ Transform3d::Identity() }; - mutable std::pair m_frustrum_zs; + Eigen::Quaterniond m_view_rotation{ 1.0, 0.0, 0.0, 0.0 }; + Transform3d m_projection_matrix{ Transform3d::Identity() }; + std::pair m_frustrum_zs; BoundingBoxf3 m_scene_box; public: - Camera(); + Camera() { set_default_orientation(); } 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 set_type(const std::string& type) { set_type((type == "1") ? Perspective : Ortho); } void select_next_type(); void enable_update_config_on_type_change(bool enable) { m_update_config_on_type_change_enabled = enable; } @@ -67,7 +67,7 @@ public: double get_zoom() const { return m_zoom; } double get_inv_zoom() const { assert(m_zoom != 0.0); return 1.0 / m_zoom; } - void update_zoom(double delta_zoom); + void update_zoom(double delta_zoom) { set_zoom(m_zoom / (1.0 - std::max(std::min(delta_zoom, 4.0), -4.0) * 0.1)); } void set_zoom(double zoom); const BoundingBoxf3& get_scene_box() const { return m_scene_box; } @@ -119,8 +119,7 @@ public: bool is_looking_downward() const { return get_dir_forward().dot(Vec3d::UnitZ()) < 0.0; } // forces camera right vector to be parallel to XY plane - void recover_from_free_camera() - { + void recover_from_free_camera() { if (std::abs(get_dir_right()(2)) > EPSILON) look_at(get_position(), m_target, Vec3d::UnitZ()); } @@ -128,7 +127,7 @@ public: void look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up); double max_zoom() const { return 250.0; } - double min_zoom() const; + double min_zoom() const { return 0.7 * calc_zoom_to_bounding_box_factor(m_scene_box); } private: // returns tight values for nearZ and farZ plane around the given bounding box diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index b7cff7c7fc..cd7805a880 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -278,15 +278,18 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) bool have_support_material_auto = have_support_material && config->opt_bool("support_material_auto"); bool have_support_interface = config->opt_int("support_material_interface_layers") > 0; bool have_support_soluble = have_support_material && config->opt_float("support_material_contact_distance") == 0; - for (auto el : { "support_material_pattern", "support_material_with_sheath", + auto support_material_style = config->opt_enum("support_material_style"); + for (auto el : { "support_material_style", "support_material_pattern", "support_material_with_sheath", "support_material_spacing", "support_material_angle", "support_material_interface_pattern", "support_material_interface_layers", "dont_support_bridges", "support_material_extrusion_width", "support_material_contact_distance", "support_material_xy_spacing" }) toggle_field(el, have_support_material); toggle_field("support_material_threshold", have_support_material_auto); + toggle_field("support_material_bottom_contact_distance", have_support_material && ! have_support_soluble); + toggle_field("support_material_closing_radius", have_support_material && support_material_style == smsSnug); - for (auto el : { "support_material_interface_spacing", "support_material_interface_extruder", + for (auto el : { "support_material_bottom_interface_layers", "support_material_interface_spacing", "support_material_interface_extruder", "support_material_interface_speed", "support_material_interface_contact_loops" }) toggle_field(el, have_support_material && have_support_interface); toggle_field("support_material_synchronize_layers", have_support_soluble); @@ -310,7 +313,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) toggle_field("standby_temperature_delta", have_ooze_prevention); bool have_wipe_tower = config->opt_bool("wipe_tower"); - for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", + for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" }) toggle_field(el, have_wipe_tower); diff --git a/src/slic3r/GUI/ConfigSnapshotDialog.cpp b/src/slic3r/GUI/ConfigSnapshotDialog.cpp index 9f996d378b..6a02e67534 100644 --- a/src/slic3r/GUI/ConfigSnapshotDialog.cpp +++ b/src/slic3r/GUI/ConfigSnapshotDialog.cpp @@ -29,11 +29,20 @@ static wxString format_reason(const Config::Snapshot::Reason reason) } } -static wxString generate_html_row(const Config::Snapshot &snapshot, bool row_even, bool snapshot_active) +static std::string get_color(wxColour colour) { + wxString clr_str = wxString::Format(wxT("#%02X%02X%02X"), colour.Red(), colour.Green(), colour.Blue()); + return clr_str.ToStdString(); +}; + + +static wxString generate_html_row(const Config::Snapshot &snapshot, bool row_even, bool snapshot_active, bool dark_mode) +{ // Start by declaring a row with an alternating background color. wxString text = ""; text += ""; @@ -92,14 +101,15 @@ static wxString generate_html_row(const Config::Snapshot &snapshot, bool row_eve static wxString generate_html_page(const Config::SnapshotDB &snapshot_db, const wxString &on_snapshot) { + bool dark_mode = wxGetApp().dark_mode(); wxString text = "" - "" - ""; + "" + ""; text += ""; for (size_t i_row = 0; i_row < snapshot_db.snapshots().size(); ++ i_row) { const Config::Snapshot &snapshot = snapshot_db.snapshots()[snapshot_db.snapshots().size() - i_row - 1]; - text += generate_html_row(snapshot, i_row & 1, snapshot.id == on_snapshot); + text += generate_html_row(snapshot, i_row & 1, snapshot.id == on_snapshot, dark_mode); } text += "
" @@ -115,8 +125,8 @@ ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX) { this->SetFont(wxGetApp().normal_font()); - this->SetBackgroundColour(*wxWHITE); - + this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL); this->SetSizer(vsizer); diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index b0bb6a8a4a..bd6986d3ef 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -819,7 +819,22 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected wxArrayInt sel_printers; int sel_printers_count = list_printer->GetSelections(sel_printers); - if (sel_printers != sel_printers_prev) { + // Does our wxWidgets version support operator== for wxArrayInt ? + // https://github.com/prusa3d/PrusaSlicer/issues/5152#issuecomment-787208614 +#if wxCHECK_VERSION(3, 1, 1) + if (sel_printers != sel_printers_prev) { +#else + auto are_equal = [](const wxArrayInt& arr_first, const wxArrayInt& arr_second) { + if (arr_first.GetCount() != arr_second.GetCount()) + return false; + for (size_t i = 0; i < arr_first.GetCount(); i++) + if (arr_first[i] != arr_second[i]) + return false; + return true; + }; + if (!are_equal(sel_printers, sel_printers_prev)) { +#endif + // Refresh type list list_type->Clear(); list_type->append(_L("(All)"), &EMPTY); @@ -1186,7 +1201,6 @@ PageReloadFromDisk::PageReloadFromDisk(ConfigWizard* parent) box_pathnames->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->full_pathnames = event.IsChecked(); }); } -#if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN #ifdef _WIN32 PageFilesAssociation::PageFilesAssociation(ConfigWizard* parent) : ConfigWizardPage(parent, _L("Files association"), _L("Files association")) @@ -1200,7 +1214,6 @@ PageFilesAssociation::PageFilesAssociation(ConfigWizard* parent) // append(cb_gcode); } #endif // _WIN32 -#endif // ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN PageMode::PageMode(ConfigWizard *parent) : ConfigWizardPage(parent, _L("View mode"), _L("View mode")) @@ -1813,11 +1826,9 @@ void ConfigWizard::priv::load_pages() index->add_page(page_update); index->add_page(page_reload_from_disk); -#if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN #ifdef _WIN32 index->add_page(page_files_association); #endif // _WIN32 -#endif // ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN index->add_page(page_mode); index->go_to(former_active); // Will restore the active item/page if possible @@ -2411,7 +2422,6 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese app_config->set("preset_update", page_update->preset_update ? "1" : "0"); app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0"); -#if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN #ifdef _WIN32 app_config->set("associate_3mf", page_files_association->associate_3mf() ? "1" : "0"); app_config->set("associate_stl", page_files_association->associate_stl() ? "1" : "0"); @@ -2429,7 +2439,6 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese // } #endif // _WIN32 -#endif // ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN page_mode->serialize_mode(app_config); @@ -2594,11 +2603,9 @@ ConfigWizard::ConfigWizard(wxWindow *parent) p->add_page(p->page_update = new PageUpdate(this)); p->add_page(p->page_reload_from_disk = new PageReloadFromDisk(this)); -#if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN #ifdef _WIN32 p->add_page(p->page_files_association = new PageFilesAssociation(this)); #endif // _WIN32 -#endif // ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN p->add_page(p->page_mode = new PageMode(this)); p->add_page(p->page_firmware = new PageFirmware(this)); p->add_page(p->page_bed = new PageBedShape(this)); diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index 581ec39b18..eee906ae77 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -393,7 +393,6 @@ struct PageReloadFromDisk : ConfigWizardPage PageReloadFromDisk(ConfigWizard* parent); }; -#if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN #ifdef _WIN32 struct PageFilesAssociation : ConfigWizardPage { @@ -410,7 +409,6 @@ public: // bool associate_gcode() const { return cb_gcode->IsChecked(); } }; #endif // _WIN32 -#endif // ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN struct PageMode: ConfigWizardPage { @@ -572,11 +570,9 @@ struct ConfigWizard::priv PageCustom *page_custom = nullptr; PageUpdate *page_update = nullptr; PageReloadFromDisk *page_reload_from_disk = nullptr; -#if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN #ifdef _WIN32 PageFilesAssociation* page_files_association = nullptr; #endif // _WIN32 -#endif // ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN PageMode *page_mode = nullptr; PageVendors *page_vendors = nullptr; Pages3rdparty pages_3rdparty; diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index c12d361120..84a499d6fd 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -328,7 +328,12 @@ double Control::get_double_value(const SelectedSlider& selection) int Control::get_tick_from_value(double value) { - auto it = std::lower_bound(m_values.begin(), m_values.end(), value - epsilon()); + std::vector::iterator it; + if (m_is_wipe_tower) + it = std::find_if(m_values.begin(), m_values.end(), + [value](const double & val) { return fabs(value - val) <= epsilon(); }); + else + it = std::lower_bound(m_values.begin(), m_values.end(), value - epsilon()); if (it == m_values.end()) return -1; @@ -382,7 +387,7 @@ void Control::SetTicksValues(const Info& custom_gcode_per_print_z) Update(); } -void Control::SetLayersTimes(const std::vector& layers_times) +void Control::SetLayersTimes(const std::vector& layers_times, float total_time) { m_layers_times.clear(); if (layers_times.empty()) @@ -391,10 +396,28 @@ void Control::SetLayersTimes(const std::vector& layers_times) m_layers_times[0] = layers_times[0]; for (size_t i = 1; i < layers_times.size(); i++) m_layers_times[i] = m_layers_times[i - 1] + layers_times[i]; + + // Erase duplicates values from m_values and save it to the m_layers_values + // They will be used for show the correct estimated time for MM print, when "No sparce layer" is enabled + // See https://github.com/prusa3d/PrusaSlicer/issues/6232 + if (m_is_wipe_tower && m_values.size() != m_layers_times.size()) { + m_layers_values = m_values; + sort(m_layers_values.begin(), m_layers_values.end()); + m_layers_values.erase(unique(m_layers_values.begin(), m_layers_values.end()), m_layers_values.end()); + + // When whipe tower is used to the end of print, there is one layer which is not marked in layers_times + // So, add this value from the total print time value + if (m_layers_values.size() != m_layers_times.size()) + for (size_t i = m_layers_times.size(); i < m_layers_values.size(); i++) + m_layers_times.push_back(total_time); + Refresh(); + Update(); + } } void Control::SetLayersTimes(const std::vector& layers_times) { + m_is_wipe_tower = false; m_layers_times = layers_times; for (size_t i = 1; i < m_layers_times.size(); i++) m_layers_times[i] += m_layers_times[i - 1]; @@ -417,6 +440,8 @@ void Control::SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, c m_only_extruder = only_extruder; UseDefaultColors(m_mode == SingleExtruder); + + m_is_wipe_tower = m_mode != SingleExtruder; } void Control::SetExtruderColors( const std::vector& extruder_colors) @@ -424,6 +449,22 @@ void Control::SetExtruderColors( const std::vector& extruder_colors m_extruder_colors = extruder_colors; } +bool Control::IsNewPrint() +{ + if (GUI::wxGetApp().plater()->printer_technology() == ptSLA) + return false; + const Print& print = GUI::wxGetApp().plater()->fff_print(); + std::string idxs; + for (auto object : print.objects()) + idxs += std::to_string(object->id().id) + "_"; + + if (idxs == m_print_obj_idxs) + return false; + + m_print_obj_idxs = idxs; + return true; +} + void Control::get_lower_and_higher_position(int& lower_pos, int& higher_pos) { const double step = get_scroll_step(); @@ -495,6 +536,18 @@ void Control::render() } } +bool Control::is_wipe_tower_layer(int tick) const +{ + if (!m_is_wipe_tower || tick >= (int)m_values.size()) + return false; + if (tick == 0 || (tick == (int)m_values.size() - 1 && m_values[tick] > m_values[tick - 1])) + return false; + if (m_values[tick - 1] == m_values[tick + 1] && m_values[tick] < m_values[tick + 1]) + return true; + + return false; +} + void Control::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end) { const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; @@ -506,6 +559,11 @@ void Control::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_ if (tick == 0) return; + if (is_wipe_tower_layer(tick)) { + m_rect_tick_action = wxRect(); + return; + } + wxBitmap* icon = m_focus == fiActionIcon ? &m_bmp_add_tick_off.bmp() : &m_bmp_add_tick_on.bmp(); if (m_ticks.ticks.find(TickCode{tick}) != m_ticks.ticks.end()) icon = m_focus == fiActionIcon ? &m_bmp_del_tick_off.bmp() : &m_bmp_del_tick_on.bmp(); @@ -604,7 +662,7 @@ void Control::draw_tick_on_mouse_position(wxDC& dc) } } -static std::string short_and_splitted_time(const std::string& time) +static wxString short_and_splitted_time(const std::string& time) { // Parse the dhms time format. int days = 0; @@ -642,7 +700,7 @@ static std::string short_and_splitted_time(const std::string& time) } else ::sprintf(buffer, "%ds", seconds); - return buffer; + return wxString::FromUTF8(buffer); } wxString Control::get_label(int tick, LabelType label_type/* = ltHeightWithLayer*/) const @@ -654,6 +712,27 @@ wxString Control::get_label(int tick, LabelType label_type/* = ltHeightWithLayer if (value >= m_values.size()) return "ErrVal"; + // When "Print Settings -> Multiple Extruders -> No sparse layer" is enabled, then "Smart" Wipe Tower is used for wiping. + // As a result, each layer with tool changes is splited for min 3 parts: first tool, wiping, second tool ... + // So, vertical slider have to respect to this case. + // see https://github.com/prusa3d/PrusaSlicer/issues/6232. + // m_values contains data for all layer's parts, + // but m_layers_values contains just unique Z values. + // Use this function for correct conversion slider position to number of printed layer + auto get_layer_number = [this](int value, LabelType label_type) { + if (label_type == ltEstimatedTime && m_layers_times.empty()) + return size_t(-1); + double layer_print_z = m_values[is_wipe_tower_layer(value) ? std::max(value - 1, 0) : value]; + auto it = std::lower_bound(m_layers_values.begin(), m_layers_values.end(), layer_print_z - epsilon()); + if (it == m_layers_values.end()) { + it = std::lower_bound(m_values.begin(), m_values.end(), layer_print_z - epsilon()); + if (it == m_values.end()) + return size_t(-1); + return size_t(value + 1); + } + return size_t(it - m_layers_values.begin()+1); + }; + #if ENABLE_GCODE_LINES_ID_IN_H_SLIDER if (m_draw_mode == dmSequentialGCodeView) { return (Slic3r::GUI::get_app_config()->get("seq_top_gcode_indices") == "1") ? @@ -666,15 +745,21 @@ wxString Control::get_label(int tick, LabelType label_type/* = ltHeightWithLayer #endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER else { if (label_type == ltEstimatedTime) { - return (value < m_layers_times.size()) ? short_and_splitted_time(get_time_dhms(m_layers_times[value])) : ""; + if (m_is_wipe_tower) { + size_t layer_number = get_layer_number(value, label_type); + return layer_number == size_t(-1) ? "" : short_and_splitted_time(get_time_dhms(m_layers_times[layer_number])); + } + return value < m_layers_times.size() ? short_and_splitted_time(get_time_dhms(m_layers_times[value])) : ""; } wxString str = m_values.empty() ? wxString::Format("%.*f", 2, m_label_koef * value) : wxString::Format("%.*f", 2, m_values[value]); if (label_type == ltHeight) return str; - if (label_type == ltHeightWithLayer) - return format_wxstr("%1%\n(%2%)", str, m_values.empty() ? value : value + 1); + if (label_type == ltHeightWithLayer) { + size_t layer_number = m_is_wipe_tower ? get_layer_number(value, label_type) : (m_values.empty() ? value : value + 1); + return format_wxstr("%1%\n(%2%)", str, layer_number); + } } return wxEmptyString; @@ -709,10 +794,17 @@ void Control::draw_tick_text(wxDC& dc, const wxPoint& pos, int tick, LabelType l text_pos = wxPoint(std::max(2, pos.x - text_width - 1 - m_thumb_size.x), pos.y - 0.5 * text_height + 1); } + wxColour old_clr = dc.GetTextForeground(); + const wxPen& pen = is_wipe_tower_layer(tick) && (tick == m_lower_value || tick == m_higher_value) ? DARK_ORANGE_PEN : wxPen(old_clr); + dc.SetPen(pen); + dc.SetTextForeground(pen.GetColour()); + if (label_type == ltEstimatedTime) dc.DrawLabel(label, wxRect(text_pos, wxSize(text_width, text_height)), wxALIGN_RIGHT); else dc.DrawText(label, text_pos); + + dc.SetTextForeground(old_clr); } void Control::draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const @@ -1275,6 +1367,8 @@ wxString Control::get_tooltip(int tick/*=-1*/) if (m_focus == fiColorBand) return m_mode != SingleExtruder ? "" : _L("Edit current color - Right click the colored slider segment"); + if (m_focus == fiSmartWipeTower) + return _L("This is wipe tower layer"); if (m_draw_mode == dmSlaPrint) return ""; // no drawn ticks and no tooltips for them in SlaPrinting mode @@ -1317,7 +1411,12 @@ wxString Control::get_tooltip(int tick/*=-1*/) "This code won't be processed during G-code generation."); // Show custom Gcode as a first string of tooltop - tooltip = " "; + std::string space = " "; + tooltip = space; + auto format_gcode = [space](std::string gcode) { + boost::replace_all(gcode, "\n", "\n" + space); + return gcode; + }; tooltip += tick_code_it->type == ColorChange ? (m_mode == SingleExtruder ? @@ -1329,7 +1428,7 @@ wxString Control::get_tooltip(int tick/*=-1*/) format_wxstr(_L("Custom template (\"%1%\")"), gcode(Template)) : tick_code_it->type == ToolChange ? format_wxstr(_L("Extruder (tool) is changed to Extruder \"%1%\""), tick_code_it->extruder) : - from_u8(tick_code_it->extra);// tick_code_it->type == Custom + from_u8(format_gcode(tick_code_it->extra));// tick_code_it->type == Custom // If tick is marked as a conflict (exclamation icon), // we should to explain why @@ -1403,8 +1502,14 @@ void Control::OnMotion(wxMouseEvent& event) else if (is_point_in_rect(pos, m_rect_higher_thumb)) m_focus = fiHigherThumb; else { - m_focus = fiTick; tick = get_tick_near_point(pos); + if (tick < 0 && m_is_wipe_tower) { + tick = get_value_from_position(pos); + m_focus = tick > 0 && is_wipe_tower_layer(tick) && (tick == m_lower_value || tick == m_higher_value) ? + fiSmartWipeTower : fiTick; + } + else + m_focus = fiTick; } m_moving_pos = pos; } @@ -1925,12 +2030,17 @@ void Control::auto_color_change() double delta_area = scale_(scale_(25)); // equal to 25 mm2 for (auto object : print.objects()) { + if (object->layer_count() == 0) + continue; double prev_area = area(object->get_layer(0)->lslices); for (size_t i = 1; i < object->layers().size(); i++) { Layer* layer = object->get_layer(i); double cur_area = area(layer->lslices); + if (cur_area > prev_area && prev_area - cur_area > scale_(scale_(1))) + break; + if (prev_area - cur_area > delta_area) { int tick = get_tick_from_value(layer->print_z); if (tick >= 0 && !m_ticks.has_tick(tick)) { @@ -1944,6 +2054,10 @@ void Control::auto_color_change() extruder = 1; } } + + // allow max 3 auto color changes + if (m_ticks.ticks.size() == 3) + break; } prev_area = cur_area; @@ -2051,7 +2165,7 @@ static std::string get_custom_code(const std::string& code_in, double height) if (dlg.ShowModal() != wxID_OK) return ""; - value = dlg.GetValue().ToStdString(); + value = into_u8(dlg.GetValue()); valid = GUI::Tab::validate_custom_gcode("Custom G-code", value); } while (!valid); return value; @@ -2059,7 +2173,7 @@ static std::string get_custom_code(const std::string& code_in, double height) if (dlg.ShowModal() != wxID_OK) return ""; - return dlg.GetValue().ToStdString(); + return into_u8(dlg.GetValue()); #endif // ENABLE_VALIDATE_CUSTOM_GCODE } diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp index 52f7be6294..439b03a508 100644 --- a/src/slic3r/GUI/DoubleSlider.hpp +++ b/src/slic3r/GUI/DoubleSlider.hpp @@ -43,6 +43,7 @@ enum FocusedItem { fiActionIcon, fiLowerThumb, fiHigherThumb, + fiSmartWipeTower, fiTick }; @@ -222,7 +223,7 @@ public: Info GetTicksValues() const; void SetTicksValues(const Info &custom_gcode_per_print_z); - void SetLayersTimes(const std::vector& layers_times); + void SetLayersTimes(const std::vector& layers_times, float total_time); void SetLayersTimes(const std::vector& layers_times); void SetDrawMode(bool is_sla_print, bool is_sequential_print); @@ -233,6 +234,8 @@ public: void SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, const int only_extruder); void SetExtruderColors(const std::vector& extruder_colors); + bool IsNewPrint(); + void set_render_as_disabled(bool value) { m_render_as_disabled = value; } bool is_rendering_as_disabled() const { return m_render_as_disabled; } @@ -303,6 +306,7 @@ protected: void correct_higher_value(); void move_current_thumb(const bool condition); void enter_window(wxMouseEvent& event, const bool enter); + bool is_wipe_tower_layer(int tick) const; private: @@ -366,6 +370,7 @@ private: bool m_is_focused = false; bool m_force_mode_apply = true; bool m_enable_action_icon = true; + bool m_is_wipe_tower = false; //This flag indicates that there is multiple extruder print with wipe tower DrawMode m_draw_mode = dmRegular; @@ -394,7 +399,9 @@ private: std::vector m_values; TickCodeInfo m_ticks; std::vector m_layers_times; + std::vector m_layers_values; std::vector m_extruder_colors; + std::string m_print_obj_idxs; #if ENABLE_GCODE_LINES_ID_IN_H_SLIDER std::vector m_alternate_values; diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 7303f4d72d..1fe28eb34a 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -902,7 +902,7 @@ void Choice::BUILD() { if (m_opt.width >= 0) size.SetWidth(m_opt.width*m_em_unit); choice_ctrl* temp; - if (!m_opt.gui_type.empty() && m_opt.gui_type.compare("select_open") != 0) { + if (m_opt.gui_type != ConfigOptionDef::GUIType::undefined && m_opt.gui_type != ConfigOptionDef::GUIType::select_open) { m_is_editable = true; temp = new choice_ctrl(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size); } @@ -965,15 +965,27 @@ void Choice::BUILD() { } if (is_defined_input_value(window, m_opt.type)) { - if (m_opt.type == coFloatOrPercent) { + switch (m_opt.type) { + case coFloatOrPercent: + { std::string old_val = !m_value.empty() ? boost::any_cast(m_value) : ""; if (old_val == boost::any_cast(get_value())) return; + break; } - else { - double old_val = !m_value.empty() ? boost::any_cast(m_value) : -99999; - if (fabs(old_val - boost::any_cast(get_value())) <= 0.0001) + case coInt: + { + int old_val = !m_value.empty() ? boost::any_cast(m_value) : 0; + if (old_val == boost::any_cast(get_value())) return; + break; + } + default: + { + double old_val = !m_value.empty() ? boost::any_cast(m_value) : -99999; + if (fabs(old_val - boost::any_cast(get_value())) <= 0.0001) + return; + } } on_change_field(); } @@ -1100,28 +1112,17 @@ void Choice::set_value(const boost::any& value, bool change_event) int val = boost::any_cast(value); if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "fill_pattern") { - if (!m_opt.enum_values.empty()) { - std::string key; - t_config_enum_values map_names = ConfigOptionEnum::get_enum_values(); - for (auto it : map_names) { - if (val == it.second) { - key = it.first; - break; - } + std::string key; + const t_config_enum_values& map_names = ConfigOptionEnum::get_enum_values(); + for (auto it : map_names) + if (val == it.second) { + key = it.first; + break; } - size_t idx = 0; - for (auto el : m_opt.enum_values) - { - if (el == key) - break; - ++idx; - } - - val = idx == m_opt.enum_values.size() ? 0 : idx; - } - else - val = 0; + const std::vector& values = m_opt.enum_values; + auto it = std::find(values.begin(), values.end(), key); + val = it == values.end() ? 0 : it - values.begin(); } field->SetSelection(val); break; @@ -1187,45 +1188,14 @@ boost::any& Choice::get_value() if (m_opt.type == coEnum) { - int ret_enum = field->GetSelection(); - if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "fill_pattern") - { - if (!m_opt.enum_values.empty()) { - std::string key = m_opt.enum_values[ret_enum]; - t_config_enum_values map_names = ConfigOptionEnum::get_enum_values(); - int value = map_names.at(key); - - m_value = static_cast(value); - } - else - m_value = static_cast(0); + if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "fill_pattern") { + const std::string& key = m_opt.enum_values[field->GetSelection()]; + m_value = int(ConfigOptionEnum::get_enum_values().at(key)); } - else if (m_opt_id.compare("ironing_type") == 0) - m_value = static_cast(ret_enum); - else if (m_opt_id.compare("fuzzy_skin") == 0) - m_value = static_cast(ret_enum); - else if (m_opt_id.compare("gcode_flavor") == 0) - m_value = static_cast(ret_enum); - else if (m_opt_id.compare("machine_limits_usage") == 0) - m_value = static_cast(ret_enum); - else if (m_opt_id.compare("support_material_pattern") == 0) - m_value = static_cast(ret_enum); - else if (m_opt_id.compare("support_material_interface_pattern") == 0) - m_value = static_cast(ret_enum); - else if (m_opt_id.compare("seam_position") == 0) - m_value = static_cast(ret_enum); - else if (m_opt_id.compare("host_type") == 0) - m_value = static_cast(ret_enum); - else if (m_opt_id.compare("display_orientation") == 0) - m_value = static_cast(ret_enum); - else if (m_opt_id.compare("support_pillar_connection_mode") == 0) - m_value = static_cast(ret_enum); - else if (m_opt_id == "printhost_authorization_type") - m_value = static_cast(ret_enum); - else if (m_opt_id == "brim_type") - m_value = static_cast(ret_enum); + else + m_value = field->GetSelection(); } - else if (m_opt.gui_type == "f_enum_open") { + else if (m_opt.gui_type == ConfigOptionDef::GUIType::f_enum_open || m_opt.gui_type == ConfigOptionDef::GUIType::i_enum_open) { const int ret_enum = field->GetSelection(); if (ret_enum < 0 || m_opt.enum_values.empty() || m_opt.type == coStrings || (ret_str != m_opt.enum_values[ret_enum] && ret_str != _(m_opt.enum_labels[ret_enum]))) @@ -1233,6 +1203,8 @@ boost::any& Choice::get_value() get_value_by_opt_type(ret_str); else if (m_opt.type == coFloatOrPercent) m_value = m_opt.enum_values[ret_enum]; + else if (m_opt.type == coInt) + m_value = atoi(m_opt.enum_values[ret_enum].c_str()); else m_value = atof(m_opt.enum_values[ret_enum].c_str()); } diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 71441e037c..eacf69c917 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -21,7 +21,11 @@ #include #include +#if ENABLE_GCODE_WINDOW +#include +#endif // ENABLE_GCODE_WINDOW #include +#include #include #include @@ -180,11 +184,13 @@ void GCodeViewer::TBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsi // use rounding to reduce the number of generated paths #if ENABLE_SPLITTED_VERTEX_BUFFER paths.push_back({ move.type, move.extrusion_role, move.delta_extruder, - round_to_nearest(move.height, 2), round_to_nearest(move.width, 2), move.feedrate, move.fan_speed, + round_to_nearest(move.height, 2), round_to_nearest(move.width, 2), + move.feedrate, move.fan_speed, move.temperature, move.volumetric_rate(), move.extruder_id, move.cp_color_id, { { endpoint, endpoint } } }); #else paths.push_back({ move.type, move.extrusion_role, endpoint, endpoint, move.delta_extruder, - round_to_nearest(move.height, 2), round_to_nearest(move.width, 2), move.feedrate, move.fan_speed, + round_to_nearest(move.height, 2), round_to_nearest(move.width, 2), + move.feedrate, move.fan_speed, move.temperature, move.volumetric_rate(), move.extruder_id, move.cp_color_id }); #endif // ENABLE_SPLITTED_VERTEX_BUFFER } @@ -278,7 +284,7 @@ void GCodeViewer::SequentialView::Marker::render() const imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Tool position") + ":"); ImGui::SameLine(); char buf[1024]; - sprintf(buf, "X: %.2f, Y: %.2f, Z: %.2f", m_world_position(0), m_world_position(1), m_world_position(2)); + sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f", m_world_position(0), m_world_position(1), m_world_position(2)); imgui.text(std::string(buf)); // force extra frame to automatically update window size @@ -295,6 +301,223 @@ void GCodeViewer::SequentialView::Marker::render() const ImGui::PopStyleVar(); } +#if ENABLE_GCODE_WINDOW +void GCodeViewer::SequentialView::GCodeWindow::load_gcode() +{ + if (m_filename.empty()) + return; + + try + { + // generate mapping for accessing data in file by line number + boost::nowide::ifstream f(m_filename); + + f.seekg(0, f.end); + uint64_t file_length = static_cast(f.tellg()); + f.seekg(0, f.beg); + + std::string line; + uint64_t offset = 0; + while (std::getline(f, line)) { + uint64_t line_length = static_cast(line.length()); + m_lines_map.push_back({ offset, line_length }); + offset += static_cast(line_length) + 1; + } + + if (offset != file_length) { + // if the final offset does not match with file length, lines are terminated with CR+LF + // so update all offsets accordingly + for (size_t i = 0; i < m_lines_map.size(); ++i) { + m_lines_map[i].first += static_cast(i); + } + } + } + catch (...) + { + BOOST_LOG_TRIVIAL(error) << "Unable to load data from " << m_filename << ". Cannot show G-code window."; + reset(); + return; + } + + m_selected_line_id = 0; + m_last_lines_size = 0; + + try + { + m_file.open(boost::filesystem::path(m_filename)); + } + catch (...) + { + BOOST_LOG_TRIVIAL(error) << "Unable to map file " << m_filename << ". Cannot show G-code window."; + reset(); + } +} + +void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, uint64_t curr_line_id) const +{ + auto update_lines = [this](uint64_t start_id, uint64_t end_id) { + std::vector ret; + ret.reserve(end_id - start_id + 1); + for (uint64_t id = start_id; id <= end_id; ++id) { + // read line from file + std::string gline(m_file.data() + m_lines_map[id - 1].first, m_lines_map[id - 1].second); + + std::string command; + std::string parameters; + std::string comment; + + // extract comment + std::vector tokens; + boost::split(tokens, gline, boost::is_any_of(";"), boost::token_compress_on); + command = tokens.front(); + if (tokens.size() > 1) + comment = ";" + tokens.back(); + + // extract gcode command and parameters + if (!command.empty()) { + boost::split(tokens, command, boost::is_any_of(" "), boost::token_compress_on); + command = tokens.front(); + if (tokens.size() > 1) { + for (size_t i = 1; i < tokens.size(); ++i) { + parameters += " " + tokens[i]; + } + } + } + ret.push_back({ command, parameters, comment }); + } + return ret; + }; + + static const ImVec4 LINE_NUMBER_COLOR = ImGuiWrapper::COL_ORANGE_LIGHT; + static const ImVec4 SELECTION_RECT_COLOR = ImGuiWrapper::COL_ORANGE_DARK; + static const ImVec4 COMMAND_COLOR = { 0.8f, 0.8f, 0.0f, 1.0f }; + static const ImVec4 PARAMETERS_COLOR = { 1.0f, 1.0f, 1.0f, 1.0f }; + static const ImVec4 COMMENT_COLOR = { 0.7f, 0.7f, 0.7f, 1.0f }; + + if (!m_visible || m_filename.empty() || m_lines_map.empty() || curr_line_id == 0) + return; + + // window height + const float wnd_height = bottom - top; + + // number of visible lines + const float text_height = ImGui::CalcTextSize("0").y; + const ImGuiStyle& style = ImGui::GetStyle(); + const uint64_t lines_count = static_cast((wnd_height - 2.0f * style.WindowPadding.y + style.ItemSpacing.y) / (text_height + style.ItemSpacing.y)); + + if (lines_count == 0) + return; + + // visible range + const uint64_t half_lines_count = lines_count / 2; + uint64_t start_id = (curr_line_id >= half_lines_count) ? curr_line_id - half_lines_count : 0; + uint64_t end_id = start_id + lines_count - 1; + if (end_id >= static_cast(m_lines_map.size())) { + end_id = static_cast(m_lines_map.size()) - 1; + start_id = end_id - lines_count + 1; + } + + // updates list of lines to show, if needed + if (m_selected_line_id != curr_line_id || m_last_lines_size != end_id - start_id + 1) { + try + { + *const_cast*>(&m_lines) = update_lines(start_id, end_id); + } + catch (...) + { + BOOST_LOG_TRIVIAL(error) << "Error while loading from file " << m_filename << ". Cannot show G-code window."; + return; + } + *const_cast(&m_selected_line_id) = curr_line_id; + *const_cast(&m_last_lines_size) = m_lines.size(); + } + + // line number's column width + const float id_width = ImGui::CalcTextSize(std::to_string(end_id).c_str()).x; + + ImGuiWrapper& imgui = *wxGetApp().imgui(); + + imgui.set_next_window_pos(0.0f, top, ImGuiCond_Always, 0.0f, 0.0f); + imgui.set_next_window_size(0.0f, wnd_height, ImGuiCond_Always); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::SetNextWindowBgAlpha(0.6f); + imgui.begin(std::string("G-code"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); + + // center the text in the window by pushing down the first line + const float f_lines_count = static_cast(lines_count); + ImGui::SetCursorPosY(0.5f * (wnd_height - f_lines_count * text_height - (f_lines_count - 1.0f) * style.ItemSpacing.y)); + + // render text lines + for (uint64_t id = start_id; id <= end_id; ++id) { + const Line& line = m_lines[id - start_id]; + + // rect around the current selected line + if (id == curr_line_id) { + const float pos_y = ImGui::GetCursorScreenPos().y; + const float half_ItemSpacing_y = 0.5f * style.ItemSpacing.y; + const float half_padding_x = 0.5f * style.WindowPadding.x; + ImGui::GetWindowDrawList()->AddRect({ half_padding_x, pos_y - half_ItemSpacing_y }, + { ImGui::GetCurrentWindow()->Size.x - half_padding_x, pos_y + text_height + half_ItemSpacing_y }, + ImGui::GetColorU32(SELECTION_RECT_COLOR)); + } + + // render line number + const std::string id_str = std::to_string(id); + // spacer to right align text + ImGui::Dummy({ id_width - ImGui::CalcTextSize(id_str.c_str()).x, text_height }); + ImGui::SameLine(0.0f, 0.0f); + ImGui::PushStyleColor(ImGuiCol_Text, LINE_NUMBER_COLOR); + imgui.text(id_str); + ImGui::PopStyleColor(); + + if (!line.command.empty() || !line.comment.empty()) + ImGui::SameLine(); + + // render command + if (!line.command.empty()) { + ImGui::PushStyleColor(ImGuiCol_Text, COMMAND_COLOR); + imgui.text(line.command); + ImGui::PopStyleColor(); + } + + // render parameters + if (!line.parameters.empty()) { + ImGui::SameLine(0.0f, 0.0f); + ImGui::PushStyleColor(ImGuiCol_Text, PARAMETERS_COLOR); + imgui.text(line.parameters); + ImGui::PopStyleColor(); + } + + // render comment + if (!line.comment.empty()) { + if (!line.command.empty()) + ImGui::SameLine(0.0f, 0.0f); + ImGui::PushStyleColor(ImGuiCol_Text, COMMENT_COLOR); + imgui.text(line.comment); + ImGui::PopStyleColor(); + } + } + + imgui.end(); + ImGui::PopStyleVar(); +} + +void GCodeViewer::SequentialView::GCodeWindow::stop_mapping_file() +{ + if (m_file.is_open()) + m_file.close(); +} + +void GCodeViewer::SequentialView::render(float legend_height) const +{ + marker.render(); + float bottom = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_height(); + if (wxGetApp().is_editor()) + bottom -= wxGetApp().plater()->get_view_toolbar().get_height(); + gcode_window.render(legend_height, bottom, static_cast(gcode_ids[current.last])); +} +#endif // ENABLE_GCODE_WINDOW + const std::vector GCodeViewer::Extrusion_Role_Colors {{ { 0.75f, 0.75f, 0.75f }, // erNone { 1.00f, 0.90f, 0.30f }, // erPerimeter @@ -393,6 +616,11 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& // release gpu memory, if used reset(); +#if ENABLE_GCODE_WINDOW + m_sequential_view.gcode_window.set_filename(gcode_result.filename); + m_sequential_view.gcode_window.load_gcode(); +#endif // ENABLE_GCODE_WINDOW + load_toolpaths(gcode_result); if (m_layers.empty()) @@ -492,6 +720,7 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: m_extrusions.ranges.height.update_from(round_to_nearest(curr.height, 2)); m_extrusions.ranges.width.update_from(round_to_nearest(curr.width, 2)); m_extrusions.ranges.fan_speed.update_from(curr.fan_speed); + m_extrusions.ranges.temperature.update_from(curr.temperature); m_extrusions.ranges.volumetric_rate.update_from(round_to_nearest(curr.volumetric_rate(), 2)); [[fallthrough]]; } @@ -545,7 +774,9 @@ void GCodeViewer::reset() m_layers_z_range = { 0, 0 }; m_roles = std::vector(); m_time_statistics.reset(); - +#if ENABLE_GCODE_WINDOW + m_sequential_view.gcode_window.reset(); +#endif // ENABLE_GCODE_WINDOW #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.reset_all(); #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -605,13 +836,22 @@ void GCodeViewer::render() const glsafe(::glEnable(GL_DEPTH_TEST)); render_toolpaths(); + render_shells(); +#if ENABLE_GCODE_WINDOW + float legend_height = 0.0f; + render_legend(legend_height); +#else + render_legend(); +#endif // ENABLE_GCODE_WINDOW SequentialView* sequential_view = const_cast(&m_sequential_view); if (sequential_view->current.last != sequential_view->endpoints.last) { sequential_view->marker.set_world_position(sequential_view->current_position); +#if ENABLE_GCODE_WINDOW + sequential_view->render(legend_height); +#else sequential_view->marker.render(); +#endif // ENABLE_GCODE_WINDOW } - render_shells(); - render_legend(); #if ENABLE_GCODE_VIEWER_STATISTICS render_statistics(); #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -2678,12 +2918,8 @@ void GCodeViewer::load_shells(const Print& print, bool initialized) const PrintConfig& config = print.config(); size_t extruders_count = config.nozzle_diameter.size(); if ((extruders_count > 1) && config.wipe_tower && !config.complete_objects) { - 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); - double nozzle_diameter = print.config().nozzle_diameter.values[0]; - float depth = print.wipe_tower_data(extruders_count, first_layer_height, nozzle_diameter).depth; - float brim_width = print.wipe_tower_data(extruders_count, first_layer_height, nozzle_diameter).brim_width; + float depth = print.wipe_tower_data(extruders_count).depth; + float brim_width = print.wipe_tower_data(extruders_count).brim_width; m_shells.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, !print.is_step_done(psWipeTower), brim_width, initialized); @@ -2725,6 +2961,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool case EViewType::Width: { color = m_extrusions.ranges.width.get_color_at(path.width); break; } case EViewType::Feedrate: { color = m_extrusions.ranges.feedrate.get_color_at(path.feedrate); break; } case EViewType::FanSpeed: { color = m_extrusions.ranges.fan_speed.get_color_at(path.fan_speed); break; } + case EViewType::Temperature: { color = m_extrusions.ranges.temperature.get_color_at(path.temperature); break; } case EViewType::VolumetricRate: { color = m_extrusions.ranges.volumetric_rate.get_color_at(path.volumetric_rate); break; } case EViewType::Tool: { color = m_tool_colors[path.extruder_id]; break; } case EViewType::ColorPrint: { @@ -2914,7 +3151,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool const Path& path = buffer->paths[path_id]; #endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id]; - if (m_sequential_view.current.last <= sub_path.first.s_id || sub_path.last.s_id <= m_sequential_view.current.first) + if (m_sequential_view.current.last < sub_path.first.s_id || sub_path.last.s_id < m_sequential_view.current.first) continue; Color color; @@ -3184,6 +3421,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool case EViewType::Width: { color = m_extrusions.ranges.width.get_color_at(path.width); break; } case EViewType::Feedrate: { color = m_extrusions.ranges.feedrate.get_color_at(path.feedrate); break; } case EViewType::FanSpeed: { color = m_extrusions.ranges.fan_speed.get_color_at(path.fan_speed); break; } + case EViewType::Temperature: { color = m_extrusions.ranges.temperature.get_color_at(path.temperature); break; } case EViewType::VolumetricRate: { color = m_extrusions.ranges.volumetric_rate.get_color_at(path.volumetric_rate); break; } case EViewType::Tool: { color = m_tool_colors[path.extruder_id]; break; } case EViewType::ColorPrint: { @@ -3518,11 +3756,11 @@ void GCodeViewer::render_toolpaths() const const IBuffer& i_buffer = buffer.indices[j]; glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); - glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_size())); + glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); bool has_normals = buffer.vertices.normal_size_floats() > 0; if (has_normals) { - glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_size())); + glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); } @@ -3564,11 +3802,11 @@ void GCodeViewer::render_toolpaths() const shader->start_using(); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, cap.vbo)); - glsafe(::glVertexPointer(cap.buffer->vertices.position_size_floats(), GL_FLOAT, cap.buffer->vertices.vertex_size_bytes(), (const void*)cap.buffer->vertices.position_offset_size())); + glsafe(::glVertexPointer(cap.buffer->vertices.position_size_floats(), GL_FLOAT, cap.buffer->vertices.vertex_size_bytes(), (const void*)cap.buffer->vertices.position_offset_bytes())); glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); bool has_normals = cap.buffer->vertices.normal_size_floats() > 0; if (has_normals) { - glsafe(::glNormalPointer(GL_FLOAT, cap.buffer->vertices.vertex_size_bytes(), (const void*)cap.buffer->vertices.normal_offset_size())); + glsafe(::glNormalPointer(GL_FLOAT, cap.buffer->vertices.vertex_size_bytes(), (const void*)cap.buffer->vertices.normal_offset_bytes())); glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); } @@ -3766,7 +4004,11 @@ void GCodeViewer::render_shells() const // glsafe(::glDepthMask(GL_TRUE)); } +#if ENABLE_GCODE_WINDOW +void GCodeViewer::render_legend(float& legend_height) const +#else void GCodeViewer::render_legend() const +#endif // ENABLE_GCODE_WINDOW { if (!m_legend_enabled) return; @@ -4014,6 +4256,7 @@ void GCodeViewer::render_legend() const case EViewType::Width: { imgui.title(_u8L("Width (mm)")); break; } case EViewType::Feedrate: { imgui.title(_u8L("Speed (mm/s)")); break; } case EViewType::FanSpeed: { imgui.title(_u8L("Fan Speed (%)")); break; } + case EViewType::Temperature: { imgui.title(_u8L("Temperature (°C)")); break; } case EViewType::VolumetricRate: { imgui.title(_u8L("Volumetric flow rate (mm³/s)")); break; } case EViewType::Tool: { imgui.title(_u8L("Tool")); break; } case EViewType::ColorPrint: { imgui.title(_u8L("Color Print")); break; } @@ -4048,6 +4291,7 @@ void GCodeViewer::render_legend() const case EViewType::Width: { append_range(m_extrusions.ranges.width, 3); break; } case EViewType::Feedrate: { append_range(m_extrusions.ranges.feedrate, 1); break; } case EViewType::FanSpeed: { append_range(m_extrusions.ranges.fan_speed, 0); break; } + case EViewType::Temperature: { append_range(m_extrusions.ranges.temperature, 0); break; } case EViewType::VolumetricRate: { append_range(m_extrusions.ranges.volumetric_rate, 3); break; } case EViewType::Tool: { @@ -4449,6 +4693,10 @@ void GCodeViewer::render_legend() const } } +#if ENABLE_GCODE_WINDOW + legend_height = ImGui::GetCurrentWindow()->Size.y; +#endif // ENABLE_GCODE_WINDOW + imgui.end(); ImGui::PopStyleVar(); } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 290c13d51d..051260b72c 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -5,6 +5,10 @@ #include "libslic3r/GCode/GCodeProcessor.hpp" #include "GLModel.hpp" +#if ENABLE_GCODE_WINDOW +#include +#endif // ENABLE_GCODE_WINDOW + #include #include #include @@ -85,35 +89,20 @@ class GCodeViewer size_t vertex_size_bytes() const { return vertex_size_floats() * sizeof(float); } size_t position_offset_floats() const { return 0; } - size_t position_offset_size() const { return position_offset_floats() * sizeof(float); } - size_t position_size_floats() const { - switch (format) - { - case EFormat::Position: - case EFormat::PositionNormal3: { return 3; } - case EFormat::PositionNormal1: { return 4; } - default: { return 0; } - } - } + size_t position_offset_bytes() const { return position_offset_floats() * sizeof(float); } + + size_t position_size_floats() const { return 3; } size_t position_size_bytes() const { return position_size_floats() * sizeof(float); } - size_t normal_offset_floats() const { - switch (format) - { - case EFormat::Position: - case EFormat::PositionNormal1: { return 0; } - case EFormat::PositionNormal3: { return 3; } - default: { return 0; } - } - } - size_t normal_offset_size() const { return normal_offset_floats() * sizeof(float); } + size_t normal_offset_floats() const { return position_size_floats(); } + size_t normal_offset_bytes() const { return normal_offset_floats() * sizeof(float); } + size_t normal_size_floats() const { switch (format) { - default: - case EFormat::Position: - case EFormat::PositionNormal1: { return 0; } + case EFormat::PositionNormal1: { return 1; } case EFormat::PositionNormal3: { return 3; } + default: { return 0; } } } size_t normal_size_bytes() const { return normal_size_floats() * sizeof(float); } @@ -179,6 +168,7 @@ class GCodeViewer float width{ 0.0f }; float feedrate{ 0.0f }; float fan_speed{ 0.0f }; + float temperature{ 0.0f }; float volumetric_rate{ 0.0f }; unsigned char extruder_id{ 0 }; unsigned char cp_color_id{ 0 }; @@ -407,6 +397,8 @@ class GCodeViewer Range fan_speed; // Color mapping by volumetric extrusion rate. Range volumetric_rate; + // Color mapping by extrusion temperature. + Range temperature; void reset() { height.reset(); @@ -414,6 +406,7 @@ class GCodeViewer feedrate.reset(); fan_speed.reset(); volumetric_rate.reset(); + temperature.reset(); } }; @@ -602,6 +595,46 @@ public: void render() const; }; +#if ENABLE_GCODE_WINDOW + class GCodeWindow + { + struct Line + { + std::string command; + std::string parameters; + std::string comment; + }; + bool m_visible{ true }; + uint64_t m_selected_line_id{ 0 }; + size_t m_last_lines_size{ 0 }; + std::string m_filename; + boost::iostreams::mapped_file_source m_file; + // map for accessing data in file by line number + std::vector> m_lines_map; + // current visible lines + std::vector m_lines; + + public: + GCodeWindow() = default; + ~GCodeWindow() { stop_mapping_file(); } + void set_filename(const std::string& filename) { m_filename = filename; } + void load_gcode(); + void reset() { + stop_mapping_file(); + m_lines_map.clear(); + m_lines.clear(); + m_filename.clear(); + } + + void toggle_visibility() { m_visible = !m_visible; } + + void render(float top, float bottom, uint64_t curr_line_id) const; + + private: + void stop_mapping_file(); + }; +#endif // ENABLE_GCODE_WINDOW + struct Endpoints { size_t first{ 0 }; @@ -614,9 +647,16 @@ public: Endpoints last_current; Vec3f current_position{ Vec3f::Zero() }; Marker marker; +#if ENABLE_GCODE_WINDOW + GCodeWindow gcode_window; +#endif // ENABLE_GCODE_WINDOW #if ENABLE_GCODE_LINES_ID_IN_H_SLIDER std::vector gcode_ids; #endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER + +#if ENABLE_GCODE_WINDOW + void render(float legend_height) const; +#endif // ENABLE_GCODE_WINDOW }; enum class EViewType : unsigned char @@ -626,6 +666,7 @@ public: Width, Feedrate, FanSpeed, + Temperature, VolumetricRate, Tool, ColorPrint, @@ -710,13 +751,21 @@ public: void export_toolpaths_to_obj(const char* filename) const; +#if ENABLE_GCODE_WINDOW + void toggle_gcode_window_visibility() { m_sequential_view.gcode_window.toggle_visibility(); } +#endif // ENABLE_GCODE_WINDOW + private: void load_toolpaths(const GCodeProcessor::Result& gcode_result); void load_shells(const Print& print, bool initialized); void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const; void render_toolpaths() const; void render_shells() const; +#if ENABLE_GCODE_WINDOW + void render_legend(float& legend_height) const; +#else void render_legend() const; +#endif // ENABLE_GCODE_WINDOW #if ENABLE_GCODE_VIEWER_STATISTICS void render_statistics() const; #endif // ENABLE_GCODE_VIEWER_STATISTICS diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 3267320df2..97038723bb 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -508,6 +508,7 @@ void GLCanvas3D::LayersEditing::reset_layer_height_profile(GLCanvas3D& canvas) m_layer_height_profile.clear(); m_layers_texture.valid = false; canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + wxGetApp().obj_list()->update_info_items(last_object_id); } void GLCanvas3D::LayersEditing::adaptive_layer_height_profile(GLCanvas3D& canvas, float quality_factor) @@ -517,6 +518,7 @@ void GLCanvas3D::LayersEditing::adaptive_layer_height_profile(GLCanvas3D& canvas const_cast(m_model_object)->layer_height_profile.set(m_layer_height_profile); m_layers_texture.valid = false; canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + wxGetApp().obj_list()->update_info_items(last_object_id); } void GLCanvas3D::LayersEditing::smooth_layer_height_profile(GLCanvas3D& canvas, const HeightProfileSmoothingParams& smoothing_params) @@ -526,6 +528,7 @@ void GLCanvas3D::LayersEditing::smooth_layer_height_profile(GLCanvas3D& canvas, const_cast(m_model_object)->layer_height_profile.set(m_layer_height_profile); m_layers_texture.valid = false; canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + wxGetApp().obj_list()->update_info_items(last_object_id); } void GLCanvas3D::LayersEditing::generate_layer_height_texture() @@ -565,6 +568,7 @@ void GLCanvas3D::LayersEditing::accept_changes(GLCanvas3D& canvas) wxGetApp().plater()->take_snapshot(_(L("Variable layer height - Manual edit"))); const_cast(m_model_object)->layer_height_profile.set(m_layer_height_profile); canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + wxGetApp().obj_list()->update_info_items(last_object_id); } } m_layer_height_profile_modified = false; @@ -641,6 +645,7 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool error = true; break; } + BOOST_LOG_TRIVIAL(error) << state << " : " << text ; auto ¬ification_manager = *wxGetApp().plater()->get_notification_manager(); if (state) { if(error) @@ -1620,9 +1625,6 @@ void GLCanvas3D::render() wxGetApp().plater()->init_environment_texture(); #endif // ENABLE_ENVIRONMENT_MAP - m_render_timer.Stop(); - m_extra_frame_requested_delayed = std::numeric_limits::max(); - const Size& cnv_size = get_canvas_size(); // Probably due to different order of events on Linux/GTK2, when one switched from 3D scene // to preview, this was called before canvas had its final size. It reported zero width @@ -1754,7 +1756,8 @@ void GLCanvas3D::render() m_tooltip.render(m_mouse.position, *this); wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this); - wxGetApp().plater()->get_notification_manager()->render_notifications(get_overlay_window_width()); + + wxGetApp().plater()->get_notification_manager()->render_notifications(*this, get_overlay_window_width()); wxGetApp().imgui()->render(); @@ -2211,12 +2214,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re const Print *print = m_process->fff_print(); - 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); - double nozzle_diameter = print->config().nozzle_diameter.values[0]; - float depth = print->wipe_tower_data(extruders_count, first_layer_height, nozzle_diameter).depth; - float brim_width = print->wipe_tower_data(extruders_count, first_layer_height, nozzle_diameter).brim_width; + float depth = print->wipe_tower_data(extruders_count).depth; + float brim_width = print->wipe_tower_data(extruders_count).brim_width; 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), @@ -2242,24 +2241,24 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re // checks for geometry outside the print volume to render it accordingly if (!m_volumes.empty()) { - ModelInstanceEPrintVolumeState state; - - const bool contained_min_one = m_volumes.check_outside_state(m_config, &state); + bool partlyOut = false; + bool fullyOut = false; + const bool contained_min_one = m_volumes.check_outside_state(m_config, partlyOut, fullyOut); #if ENABLE_WARNING_TEXTURE_REMOVAL - _set_warning_notification(EWarning::ObjectClashed, state == ModelInstancePVS_Partly_Outside); - _set_warning_notification(EWarning::ObjectOutside, state == ModelInstancePVS_Fully_Outside); - if (printer_technology != ptSLA || state == ModelInstancePVS_Inside) + _set_warning_notification(EWarning::ObjectClashed, partlyOut); + _set_warning_notification(EWarning::ObjectOutside, fullyOut); + if (printer_technology != ptSLA || !contained_min_one) _set_warning_notification(EWarning::SlaSupportsOutside, false); #else - _set_warning_texture(WarningTexture::ObjectClashed, state == ModelInstancePVS_Partly_Outside); - _set_warning_texture(WarningTexture::ObjectOutside, state == ModelInstancePVS_Fully_Outside); - if(printer_technology != ptSLA || state == ModelInstancePVS_Inside) + _set_warning_texture(WarningTexture::ObjectClashed, partlyOut); + _set_warning_texture(WarningTexture::ObjectOutside, fullyOut); + if(printer_technology != ptSLA || !contained_min_one) _set_warning_texture(WarningTexture::SlaSupportsOutside, false); #endif // ENABLE_WARNING_TEXTURE_REMOVAL post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, - contained_min_one && !m_model->objects.empty() && state != ModelInstancePVS_Partly_Outside)); + contained_min_one && !m_model->objects.empty() && !partlyOut)); } else { #if ENABLE_WARNING_TEXTURE_REMOVAL @@ -2446,13 +2445,13 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt) if (!m_initialized) return; - // FIXME m_dirty |= m_main_toolbar.update_items_state(); m_dirty |= m_undoredo_toolbar.update_items_state(); m_dirty |= wxGetApp().plater()->get_view_toolbar().update_items_state(); m_dirty |= wxGetApp().plater()->get_collapse_toolbar().update_items_state(); bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(wxGetApp().plater()->get_camera()); m_dirty |= mouse3d_controller_applied; + m_dirty |= wxGetApp().plater()->get_notification_manager()->update_notifications(*this); if (!m_dirty) return; @@ -2607,6 +2606,10 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) case 'a': { post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE)); break; } case 'B': case 'b': { zoom_to_bed(); break; } +#if ENABLE_GCODE_WINDOW + case 'C': + case 'c': { m_gcode_viewer.toggle_gcode_window_visibility(); m_dirty = true; request_extra_frame(); break; } +#endif // ENABLE_GCODE_WINDOW case 'E': case 'e': { m_labels.show(!m_labels.is_shown()); m_dirty = true; break; } case 'G': @@ -2986,30 +2989,39 @@ void GLCanvas3D::on_timer(wxTimerEvent& evt) void GLCanvas3D::on_render_timer(wxTimerEvent& evt) { - // If slicer is not top window -> restart timer with one second to try again - wxWindow* p = dynamic_cast(wxGetApp().plater()); - while (p->GetParent() != nullptr) - p = p->GetParent(); - wxTopLevelWindow* top_level_wnd = dynamic_cast(p); - if (!top_level_wnd->IsActive()) { - request_extra_frame_delayed(1000); - return; - } - //render(); - m_dirty = true; - wxWakeUpIdle(); + // no need to do anything here + // right after this event is recieved, idle event is fired + + //m_dirty = true; + //wxWakeUpIdle(); } -void GLCanvas3D::request_extra_frame_delayed(int miliseconds) + +void GLCanvas3D::schedule_extra_frame(int miliseconds) { + // Schedule idle event right now + if (miliseconds == 0) + { + // We want to wakeup idle evnt but most likely this is call inside render cycle so we need to wait + if (m_in_render) + miliseconds = 33; + else { + m_dirty = true; + wxWakeUpIdle(); + return; + } + } + // Start timer int64_t now = timestamp_now(); + // Timer is not running if (! m_render_timer.IsRunning()) { m_extra_frame_requested_delayed = miliseconds; m_render_timer.StartOnce(miliseconds); m_render_timer_start = now; + // Timer is running - restart only if new period is shorter than remaning period } else { const int64_t remaining_time = (m_render_timer_start + m_extra_frame_requested_delayed) - now; - if (miliseconds < remaining_time) { + if (miliseconds + 20 < remaining_time) { m_render_timer.Stop(); m_extra_frame_requested_delayed = miliseconds; m_render_timer.StartOnce(miliseconds); @@ -3516,7 +3528,7 @@ Vec2d GLCanvas3D::get_local_mouse_position() const void GLCanvas3D::set_tooltip(const std::string& tooltip) const { if (m_canvas != nullptr) - m_tooltip.set_text(tooltip); + const_cast(&m_tooltip)->set_text(tooltip); } void GLCanvas3D::do_move(const std::string& snapshot_type) @@ -3533,22 +3545,19 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) Selection::EMode selection_mode = m_selection.get_mode(); - for (const GLVolume* v : m_volumes.volumes) - { + for (const GLVolume* v : m_volumes.volumes) { int object_idx = v->object_idx(); int instance_idx = v->instance_idx(); int volume_idx = v->volume_idx(); std::pair done_id(object_idx, instance_idx); - if ((0 <= object_idx) && (object_idx < (int)m_model->objects.size())) - { + if (0 <= object_idx && object_idx < (int)m_model->objects.size()) { done.insert(done_id); // Move instances/volumes ModelObject* model_object = m_model->objects[object_idx]; - if (model_object != nullptr) - { + if (model_object != nullptr) { if (selection_mode == Selection::Instance) model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); else if (selection_mode == Selection::Volume) @@ -3564,8 +3573,7 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) } // Fixes sinking/flying instances - for (const std::pair& i : done) - { + for (const std::pair& i : done) { ModelObject* m = m_model->objects[i.first]; Vec3d shift(0.0, 0.0, -m->get_instance_min_z(i.second)); m_selection.translate(i.first, i.second, shift); @@ -3924,13 +3932,13 @@ bool GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) const 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_mouse_wheel)) - m_imgui_undo_redo_hovered_pos = hovered; + int* mouse_wheel = const_cast(&m_mouse_wheel); + if (imgui->undo_redo_list(ImVec2(18 * em, 26 * em), is_undo, &string_getter, hovered, selected, *mouse_wheel)) + *const_cast(&m_imgui_undo_redo_hovered_pos) = hovered; else - m_imgui_undo_redo_hovered_pos = -1; + *const_cast(&m_imgui_undo_redo_hovered_pos) = -1; - if (selected >= 0) - { + if (selected >= 0) { is_undo ? wxGetApp().plater()->undo_to(selected) : wxGetApp().plater()->redo_to(selected); action_taken = true; } @@ -3971,9 +3979,10 @@ bool GLCanvas3D::_render_search_list(float pos_x) const char *s = new char[255]; strcpy(s, search_line.empty() ? _u8L("Enter a search term").c_str() : search_line.c_str()); - imgui->search_list(ImVec2(45 * em, 30 * em), &search_string_getter, s, - sidebar.get_searcher().view_params, - selected, edited, m_mouse_wheel, wxGetApp().is_localized()); + int* mouse_wheel = const_cast(&m_mouse_wheel); + imgui->search_list(ImVec2(45 * em, 30 * em), &search_string_getter, s, + sidebar.get_searcher().view_params, + selected, edited, *mouse_wheel, wxGetApp().is_localized()); search_line = s; delete [] s; @@ -4832,8 +4841,10 @@ void GLCanvas3D::_refresh_if_shown_on_screen() void GLCanvas3D::_picking_pass() const { + std::vector* hover_volume_idxs = const_cast*>(&m_hover_volume_idxs); + if (m_picking_enabled && !m_mouse.dragging && m_mouse.position != Vec2d(DBL_MAX, DBL_MAX)) { - m_hover_volume_idxs.clear(); + hover_volume_idxs->clear(); // Render the object for picking. // FIXME This cannot possibly work in a multi - sampled context as the color gets mangled by the anti - aliasing. @@ -4848,9 +4859,10 @@ void GLCanvas3D::_picking_pass() const glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); - m_camera_clipping_plane = m_gizmos.get_clipping_plane(); - if (m_camera_clipping_plane.is_active()) { - ::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data()); + ClippingPlane* camera_clipping_plane = const_cast(&m_camera_clipping_plane); + *camera_clipping_plane = m_gizmos.get_clipping_plane(); + if (camera_clipping_plane->is_active()) { + ::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)camera_clipping_plane->get_data()); ::glEnable(GL_CLIP_PLANE0); } _render_volumes_for_picking(); @@ -4876,11 +4888,11 @@ void GLCanvas3D::_picking_pass() const if (0 <= volume_id && volume_id < (int)m_volumes.volumes.size()) { // do not add the volume id if any gizmo is active and CTRL is pressed if (m_gizmos.get_current_type() == GLGizmosManager::EType::Undefined || !wxGetKeyState(WXK_CONTROL)) - m_hover_volume_idxs.emplace_back(volume_id); - m_gizmos.set_hover_id(-1); + hover_volume_idxs->emplace_back(volume_id); + const_cast(&m_gizmos)->set_hover_id(-1); } else - m_gizmos.set_hover_id(inside && (unsigned int)volume_id <= GLGizmoBase::BASE_ID ? ((int)GLGizmoBase::BASE_ID - volume_id) : -1); + const_cast(&m_gizmos)->set_hover_id(inside && (unsigned int)volume_id <= GLGizmoBase::BASE_ID ? ((int)GLGizmoBase::BASE_ID - volume_id) : -1); _update_volumes_hover_state(); } @@ -4888,12 +4900,11 @@ void GLCanvas3D::_picking_pass() const void GLCanvas3D::_rectangular_selection_picking_pass() const { - m_gizmos.set_hover_id(-1); + const_cast(&m_gizmos)->set_hover_id(-1); std::set idxs; - if (m_picking_enabled) - { + 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)); @@ -4914,8 +4925,7 @@ void GLCanvas3D::_rectangular_selection_picking_pass() const int left = (int)m_rectangle_selection.get_left(); int top = get_canvas_size().get_height() - (int)m_rectangle_selection.get_top(); - if ((left >= 0) && (top >= 0)) - { + if (left >= 0 && top >= 0) { #define USE_PARALLEL 1 #if USE_PARALLEL struct Pixel @@ -4935,7 +4945,7 @@ void GLCanvas3D::_rectangular_selection_picking_pass() const for (size_t i = range.begin(); i < range.end(); ++i) if (frame[i].valid()) { int volume_id = frame[i].id(); - if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size())) { + if (0 <= volume_id && volume_id < (int)m_volumes.volumes.size()) { mutex.lock(); idxs.insert(volume_id); mutex.unlock(); @@ -4950,14 +4960,14 @@ void GLCanvas3D::_rectangular_selection_picking_pass() const { int px_id = 4 * i; int volume_id = frame[px_id] + (frame[px_id + 1] << 8) + (frame[px_id + 2] << 16); - if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size())) + if (0 <= volume_id && volume_id < (int)m_volumes.volumes.size()) idxs.insert(volume_id); } #endif // USE_PARALLEL } } - m_hover_volume_idxs.assign(idxs.begin(), idxs.end()); + const_cast*>(&m_hover_volume_idxs)->assign(idxs.begin(), idxs.end()); _update_volumes_hover_state(); } @@ -5050,7 +5060,9 @@ void GLCanvas3D::_render_objects() const glsafe(::glEnable(GL_DEPTH_TEST)); - m_camera_clipping_plane = m_gizmos.get_clipping_plane(); + ClippingPlane* camera_clipping_plane = const_cast(&m_camera_clipping_plane); + GLVolumeCollection* volumes = const_cast(&m_volumes); + *camera_clipping_plane = m_gizmos.get_clipping_plane(); if (m_picking_enabled) { // Update the layer editing selection to the first object selected, update the current object maximum Z. @@ -5058,17 +5070,17 @@ void GLCanvas3D::_render_objects() const if (m_config != nullptr) { const BoundingBoxf3& bed_bb = wxGetApp().plater()->get_bed().get_bounding_box(false); - m_volumes.set_print_box((float)bed_bb.min(0) - BedEpsilon, (float)bed_bb.min(1) - BedEpsilon, 0.0f, (float)bed_bb.max(0) + BedEpsilon, (float)bed_bb.max(1) + BedEpsilon, (float)m_config->opt_float("max_print_height")); - m_volumes.check_outside_state(m_config, nullptr); + volumes->set_print_box((float)bed_bb.min(0) - BedEpsilon, (float)bed_bb.min(1) - BedEpsilon, 0.0f, (float)bed_bb.max(0) + BedEpsilon, (float)bed_bb.max(1) + BedEpsilon, (float)m_config->opt_float("max_print_height")); + 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]); + 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); + volumes->set_z_range(-FLT_MAX, FLT_MAX); - m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data()); + volumes->set_clipping_plane(camera_clipping_plane->get_data()); GLShaderProgram* shader = wxGetApp().get_shader("gouraud"); if (shader != nullptr) { @@ -5076,16 +5088,16 @@ void GLCanvas3D::_render_objects() const 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, wxGetApp().plater()->get_camera().get_view_matrix(), [object_id](const GLVolume& volume) { + volumes->render(GLVolumeCollection::Opaque, false, wxGetApp().plater()->get_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); + m_layers_editing.render_volumes(*this, *volumes); } else { - // do not cull backfaces to show broken geometry, if any - m_volumes.render(GLVolumeCollection::Opaque, m_picking_enabled, wxGetApp().plater()->get_camera().get_view_matrix(), [this](const GLVolume& volume) { - return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); + // do not cull backfaces to show broken geometry, if any + volumes->render(GLVolumeCollection::Opaque, m_picking_enabled, wxGetApp().plater()->get_camera().get_view_matrix(), [this](const GLVolume& volume) { + return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); }); } @@ -5103,11 +5115,11 @@ void GLCanvas3D::_render_objects() const } } - m_volumes.render(GLVolumeCollection::Transparent, false, wxGetApp().plater()->get_camera().get_view_matrix()); + volumes->render(GLVolumeCollection::Transparent, false, wxGetApp().plater()->get_camera().get_view_matrix()); shader->stop_using(); } - m_camera_clipping_plane = ClippingPlane::ClipsNothing(); + *camera_clipping_plane = ClippingPlane::ClipsNothing(); } void GLCanvas3D::_render_gcode() const @@ -5148,13 +5160,13 @@ void GLCanvas3D::_check_and_update_toolbar_icon_scale() const GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); #if ENABLE_RETINA_GL const float sc = m_retina_helper->get_scale_factor() * scale; - m_main_toolbar.set_scale(sc); - m_undoredo_toolbar.set_scale(sc); + const_cast(&m_main_toolbar)->set_scale(sc); + const_cast(&m_undoredo_toolbar)->set_scale(sc); collapse_toolbar.set_scale(sc); size *= m_retina_helper->get_scale_factor(); #else - m_main_toolbar.set_icons_size(size); - m_undoredo_toolbar.set_icons_size(size); + const_cast(&m_main_toolbar)->set_icons_size(size); + const_cast(&m_undoredo_toolbar)->set_icons_size(size); collapse_toolbar.set_icons_size(size); #endif // ENABLE_RETINA_GL @@ -5202,13 +5214,13 @@ void GLCanvas3D::_render_overlays() const // to correctly place them #if ENABLE_RETINA_GL const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(/*true*/); - m_main_toolbar.set_scale(scale); - m_undoredo_toolbar.set_scale(scale); + const_cast(&m_main_toolbar)->set_scale(scale); + const_cast(&m_undoredo_toolbar)->set_scale(scale); wxGetApp().plater()->get_collapse_toolbar().set_scale(scale); #else const float size = int(GLToolbar::Default_Icons_Size * wxGetApp().toolbar_icon_scale(/*true*/)); - m_main_toolbar.set_icons_size(size); - m_undoredo_toolbar.set_icons_size(size); + const_cast(&m_main_toolbar)->set_icons_size(size); + const_cast(&m_undoredo_toolbar)->set_icons_size(size); wxGetApp().plater()->get_collapse_toolbar().set_icons_size(size); #endif // ENABLE_RETINA_GL @@ -5283,12 +5295,12 @@ void GLCanvas3D::_render_gizmos_overlay() const #if ENABLE_RETINA_GL // m_gizmos.set_overlay_scale(m_retina_helper->get_scale_factor()); const float scale = m_retina_helper->get_scale_factor()*wxGetApp().toolbar_icon_scale(); - m_gizmos.set_overlay_scale(scale); //! #ys_FIXME_experiment + const_cast(&m_gizmos)->set_overlay_scale(scale); //! #ys_FIXME_experiment #else // m_gizmos.set_overlay_scale(m_canvas->GetContentScaleFactor()); // m_gizmos.set_overlay_scale(wxGetApp().em_unit()*0.1f); - const float size = int(GLGizmosManager::Default_Icons_Size*wxGetApp().toolbar_icon_scale()); - m_gizmos.set_overlay_icon_size(size); //! #ys_FIXME_experiment + const float size = int(GLGizmosManager::Default_Icons_Size * wxGetApp().toolbar_icon_scale()); + const_cast(&m_gizmos)->set_overlay_icon_size(size); //! #ys_FIXME_experiment #endif /* __WXMSW__ */ m_gizmos.render_overlay(); @@ -5307,8 +5319,9 @@ void GLCanvas3D::_render_main_toolbar() const float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; float left = -0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width) * inv_zoom; - m_main_toolbar.set_position(top, left); - m_main_toolbar.render(*this); + GLToolbar* main_toolbar = const_cast(&m_main_toolbar); + main_toolbar->set_position(top, left); + main_toolbar->render(*this); } void GLCanvas3D::_render_undoredo_toolbar() const @@ -5323,8 +5336,10 @@ void GLCanvas3D::_render_undoredo_toolbar() const const GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; float left = (m_main_toolbar.get_width() - 0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width)) * inv_zoom; - m_undoredo_toolbar.set_position(top, left); - m_undoredo_toolbar.render(*this); + + GLToolbar* undoredo_toolbar = const_cast(&m_undoredo_toolbar); + undoredo_toolbar->set_position(top, left); + undoredo_toolbar->render(*this); } void GLCanvas3D::_render_collapse_toolbar() const @@ -5415,20 +5430,21 @@ void GLCanvas3D::_render_sla_slices() const if (!obj->is_step_done(slaposSliceSupports)) continue; - SlaCap::ObjectIdToTrianglesMap::iterator it_caps_bottom = m_sla_caps[0].triangles.find(i); - SlaCap::ObjectIdToTrianglesMap::iterator it_caps_top = m_sla_caps[1].triangles.find(i); + SlaCap* sla_caps = const_cast(m_sla_caps); + SlaCap::ObjectIdToTrianglesMap::iterator it_caps_bottom = sla_caps[0].triangles.find(i); + SlaCap::ObjectIdToTrianglesMap::iterator it_caps_top = sla_caps[1].triangles.find(i); { - if (it_caps_bottom == m_sla_caps[0].triangles.end()) - it_caps_bottom = m_sla_caps[0].triangles.emplace(i, SlaCap::Triangles()).first; - if (! m_sla_caps[0].matches(clip_min_z)) { - m_sla_caps[0].z = clip_min_z; + if (it_caps_bottom == sla_caps[0].triangles.end()) + it_caps_bottom = sla_caps[0].triangles.emplace(i, SlaCap::Triangles()).first; + if (!sla_caps[0].matches(clip_min_z)) { + sla_caps[0].z = clip_min_z; it_caps_bottom->second.object.clear(); it_caps_bottom->second.supports.clear(); } - if (it_caps_top == m_sla_caps[1].triangles.end()) - it_caps_top = m_sla_caps[1].triangles.emplace(i, SlaCap::Triangles()).first; - if (! m_sla_caps[1].matches(clip_max_z)) { - m_sla_caps[1].z = clip_max_z; + if (it_caps_top == sla_caps[1].triangles.end()) + it_caps_top = sla_caps[1].triangles.emplace(i, SlaCap::Triangles()).first; + if (!sla_caps[1].matches(clip_max_z)) { + sla_caps[1].z = clip_max_z; it_caps_top->second.object.clear(); it_caps_top->second.supports.clear(); } @@ -5534,7 +5550,7 @@ void GLCanvas3D::_update_volumes_hover_state() const if (alt_pressed && (shift_pressed || ctrl_pressed)) { // illegal combinations of keys - m_hover_volume_idxs.clear(); + const_cast*>(&m_hover_volume_idxs)->clear(); return; } @@ -5558,7 +5574,7 @@ void GLCanvas3D::_update_volumes_hover_state() const if (hover_modifiers_only && !hover_from_single_instance) { // do not allow to select volumes from different instances - m_hover_volume_idxs.clear(); + const_cast*>(&m_hover_volume_idxs)->clear(); return; } @@ -5579,23 +5595,15 @@ void GLCanvas3D::_update_volumes_hover_state() const (deselect && !m_selection.is_single_full_instance() && (volume.object_idx() == m_selection.get_object_idx()) && (volume.instance_idx() == m_selection.get_instance_idx())) ); - if (as_volume) { - if (deselect) - volume.hover = GLVolume::HS_Deselect; - else - volume.hover = GLVolume::HS_Select; - } + if (as_volume) + volume.hover = deselect ? GLVolume::HS_Deselect : GLVolume::HS_Select; else { int object_idx = volume.object_idx(); int instance_idx = volume.instance_idx(); for (GLVolume* v : m_volumes.volumes) { - if (v->object_idx() == object_idx && v->instance_idx() == instance_idx) { - if (deselect) - v->hover = GLVolume::HS_Deselect; - else - v->hover = GLVolume::HS_Select; - } + if (v->object_idx() == object_idx && v->instance_idx() == instance_idx) + v->hover = deselect ? GLVolume::HS_Deselect : GLVolume::HS_Select; } } } @@ -6302,31 +6310,47 @@ std::vector GLCanvas3D::_parse_colors(const std::vector& col #if ENABLE_WARNING_TEXTURE_REMOVAL void GLCanvas3D::_set_warning_notification(EWarning warning, bool state) { + enum ErrorType{ + PLATER_WARNING, + PLATER_ERROR, + SLICING_ERROR + }; std::string text; - bool error = false; + ErrorType error = ErrorType::PLATER_WARNING; switch (warning) { case EWarning::ObjectOutside: text = _u8L("An object outside the print area was detected."); break; - case EWarning::ToolpathOutside: text = _u8L("A toolpath outside the print area was detected."); error = true; break; - case EWarning::SlaSupportsOutside: text = _u8L("SLA supports outside the print area were detected."); error = true; break; + case EWarning::ToolpathOutside: text = _u8L("A toolpath outside the print area was detected."); error = ErrorType::SLICING_ERROR; break; + case EWarning::SlaSupportsOutside: text = _u8L("SLA supports outside the print area were detected."); error = ErrorType::PLATER_ERROR; break; case EWarning::SomethingNotShown: text = _u8L("Some objects are not visible."); break; case EWarning::ObjectClashed: text = _u8L("An object outside the print area was detected.\n" "Resolve the current problem to continue slicing."); - error = true; + error = ErrorType::PLATER_ERROR; break; } auto& notification_manager = *wxGetApp().plater()->get_notification_manager(); - if (state) { - if (error) - notification_manager.push_plater_error_notification(text); - else + switch (error) + { + case PLATER_WARNING: + if (state) notification_manager.push_plater_warning_notification(text); - } - else { - if (error) - notification_manager.close_plater_error_notification(text); else notification_manager.close_plater_warning_notification(text); + break; + case PLATER_ERROR: + if (state) + notification_manager.push_plater_error_notification(text); + else + notification_manager.close_plater_error_notification(text); + break; + case SLICING_ERROR: + if (state) + notification_manager.push_slicing_error_notification(text); + else + notification_manager.close_slicing_error_notification(text); + break; + default: + break; } } #else diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 10294931fe..9e9a2501e1 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -449,13 +449,13 @@ private: wxTimer m_timer; LayersEditing m_layers_editing; Mouse m_mouse; - mutable GLGizmosManager m_gizmos; - mutable GLToolbar m_main_toolbar; - mutable GLToolbar m_undoredo_toolbar; + GLGizmosManager m_gizmos; + GLToolbar m_main_toolbar; + GLToolbar m_undoredo_toolbar; ClippingPlane m_clipping_planes[2]; - mutable ClippingPlane m_camera_clipping_plane; + ClippingPlane m_camera_clipping_plane; bool m_use_clipping_planes; - mutable SlaCap m_sla_caps[2]; + SlaCap m_sla_caps[2]; std::string m_sidebar_field; // when true renders an extra frame by not resetting m_dirty to false // see request_extra_frame() @@ -463,7 +463,7 @@ private: int m_extra_frame_requested_delayed { std::numeric_limits::max() }; bool m_event_handlers_bound{ false }; - mutable GLVolumeCollection m_volumes; + GLVolumeCollection m_volumes; GCodeViewer m_gcode_viewer; RenderTimer m_render_timer; @@ -478,7 +478,6 @@ private: bool m_dirty; bool m_initialized; bool m_apply_zoom_to_volumes_filter; - mutable std::vector m_hover_volume_idxs; bool m_picking_enabled; bool m_moving_enabled; bool m_dynamic_background_enabled; @@ -487,6 +486,7 @@ private: bool m_tab_down; ECursorType m_cursor_type; GLSelectionRectangle m_rectangle_selection; + std::vector m_hover_volume_idxs; // Following variable is obsolete and it should be safe to remove it. // I just don't want to do it now before a release (Lukas Matena 24.3.2019) @@ -504,13 +504,13 @@ private: RenderStats m_render_stats; #endif // ENABLE_RENDER_STATISTICS - mutable int m_imgui_undo_redo_hovered_pos{ -1 }; - mutable int m_mouse_wheel {0}; + int m_imgui_undo_redo_hovered_pos{ -1 }; + int m_mouse_wheel{ 0 }; int m_selected_extruder; Labels m_labels; - mutable Tooltip m_tooltip; - mutable bool m_tooltip_enabled{ true }; + Tooltip m_tooltip; + bool m_tooltip_enabled{ true }; Slope m_slope; ArrangeSettings m_arrange_settings_fff, m_arrange_settings_sla, @@ -519,8 +519,7 @@ private: PrinterTechnology current_printer_technology() const; template - static auto & get_arrange_settings(Self *self) - { + static auto & get_arrange_settings(Self *self) { PrinterTechnology ptech = self->current_printer_technology(); auto *ptr = &self->m_arrange_settings_fff; @@ -529,11 +528,10 @@ private: ptr = &self->m_arrange_settings_sla; } else if (ptech == ptFFF) { auto co_opt = self->m_config->template option("complete_objects"); - if (co_opt && co_opt->value) { + if (co_opt && co_opt->value) ptr = &self->m_arrange_settings_fff_seq_print; - } else { + else ptr = &self->m_arrange_settings_fff; - } } return *ptr; @@ -715,10 +713,9 @@ public: double m_rotation = 0.; BoundingBoxf m_bb; friend class GLCanvas3D; - public: - - inline operator bool() const - { + + public: + inline operator bool() const { return !std::isnan(m_pos.x()) && !std::isnan(m_pos.y()); } @@ -743,7 +740,8 @@ public: void msw_rescale(); void request_extra_frame() { m_extra_frame_requested = true; } - void request_extra_frame_delayed(int miliseconds); + + void schedule_extra_frame(int miliseconds); 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(int item_id) { m_main_toolbar.force_left_action(item_id, *this); } @@ -762,8 +760,7 @@ public: void use_slope(bool use) { m_slope.use(use); } void set_slope_normal_angle(float angle_in_deg) { m_slope.set_normal_angle(angle_in_deg); } - ArrangeSettings get_arrange_settings() const - { + ArrangeSettings get_arrange_settings() const { const ArrangeSettings &settings = get_arrange_settings(this); ArrangeSettings ret = settings; if (&settings == &m_arrange_settings_fff_seq_print) { diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 79e7ea1c64..0e6a4ce291 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -428,8 +428,7 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) bool processed = false; // mouse anywhere - if (!evt.Dragging() && !evt.Leaving() && !evt.Entering() && (m_mouse_capture.parent != nullptr)) - { + if (!evt.Dragging() && !evt.Leaving() && !evt.Entering() && m_mouse_capture.parent != nullptr) { if (m_mouse_capture.any() && (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())) { // prevents loosing selection into the scene if mouse down was done inside the toolbar and mouse up was down outside it, // as when switching between views @@ -441,38 +440,31 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) if (evt.Moving()) update_hover_state(mouse_pos, parent); - else if (evt.LeftUp()) - { - if (m_mouse_capture.left) - { + else if (evt.LeftUp()) { + if (m_mouse_capture.left) { processed = true; m_mouse_capture.left = false; } else return false; } - else if (evt.MiddleUp()) - { - if (m_mouse_capture.middle) - { + else if (evt.MiddleUp()) { + if (m_mouse_capture.middle) { processed = true; m_mouse_capture.middle = false; } else return false; } - else if (evt.RightUp()) - { - if (m_mouse_capture.right) - { + else if (evt.RightUp()) { + if (m_mouse_capture.right) { processed = true; m_mouse_capture.right = false; } else return false; } - else if (evt.Dragging()) - { + else if (evt.Dragging()) { if (m_mouse_capture.any()) // if the button down was done on this toolbar, prevent from dragging into the scene processed = true; @@ -481,35 +473,29 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) } int item_id = contains_mouse(mouse_pos, parent); - if (item_id != -1) - { + if (item_id != -1) { // mouse inside toolbar - if (evt.LeftDown() || evt.LeftDClick()) - { + if (evt.LeftDown() || evt.LeftDClick()) { m_mouse_capture.left = true; m_mouse_capture.parent = &parent; processed = true; - if ((item_id != -2) && !m_items[item_id]->is_separator() && !m_items[item_id]->is_disabled() && - ((m_pressed_toggable_id == -1) || (m_items[item_id]->get_last_action_type() == GLToolbarItem::Left))) - { + if (item_id != -2 && !m_items[item_id]->is_separator() && !m_items[item_id]->is_disabled() && + (m_pressed_toggable_id == -1 || m_items[item_id]->get_last_action_type() == GLToolbarItem::Left)) { // mouse is inside an icon do_action(GLToolbarItem::Left, item_id, parent, true); parent.set_as_dirty(); } } - else if (evt.MiddleDown()) - { + else if (evt.MiddleDown()) { m_mouse_capture.middle = true; m_mouse_capture.parent = &parent; } - else if (evt.RightDown()) - { + else if (evt.RightDown()) { m_mouse_capture.right = true; m_mouse_capture.parent = &parent; processed = true; - if ((item_id != -2) && !m_items[item_id]->is_separator() && !m_items[item_id]->is_disabled() && - ((m_pressed_toggable_id == -1) || (m_items[item_id]->get_last_action_type() == GLToolbarItem::Right))) - { + if (item_id != -2 && !m_items[item_id]->is_separator() && !m_items[item_id]->is_disabled() && + (m_pressed_toggable_id == -1 || m_items[item_id]->get_last_action_type() == GLToolbarItem::Right)) { // mouse is inside an icon do_action(GLToolbarItem::Right, item_id, parent, true); parent.set_as_dirty(); @@ -522,24 +508,26 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) void GLToolbar::calc_layout() const { - switch (m_layout.type) + Layout* layout = const_cast(&m_layout); + + switch (layout->type) { default: case Layout::Horizontal: { - m_layout.width = get_width_horizontal(); - m_layout.height = get_height_horizontal(); + layout->width = get_width_horizontal(); + layout->height = get_height_horizontal(); break; } case Layout::Vertical: { - m_layout.width = get_width_vertical(); - m_layout.height = get_height_vertical(); + layout->width = get_width_vertical(); + layout->height = get_height_vertical(); break; } } - m_layout.dirty = false; + layout->dirty = false; } float GLToolbar::get_width_horizontal() const @@ -1196,19 +1184,17 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) const left += scaled_border; top -= scaled_border; - if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) + if (tex_id == 0 || tex_width <= 0 || tex_height <= 0) return; // renders icons - for (const GLToolbarItem* item : m_items) - { + for (const GLToolbarItem* item : m_items) { if (!item->is_visible()) continue; if (item->is_separator()) top -= separator_stride; - else - { + else { 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)); top -= icon_stride; } @@ -1219,16 +1205,14 @@ bool GLToolbar::generate_icons_texture() const { std::string path = resources_dir() + "/icons/"; std::vector filenames; - for (GLToolbarItem* item : m_items) - { + for (GLToolbarItem* item : m_items) { const std::string& icon_filename = item->get_icon_filename(); if (!icon_filename.empty()) filenames.push_back(path + icon_filename); } std::vector> states; - if (m_type == Normal) - { + if (m_type == Normal) { states.push_back({ 1, false }); // Normal states.push_back({ 0, false }); // Pressed states.push_back({ 2, false }); // Disabled @@ -1236,8 +1220,7 @@ bool GLToolbar::generate_icons_texture() const states.push_back({ 0, false }); // HoverPressed states.push_back({ 2, false }); // HoverDisabled } - else - { + else { states.push_back({ 1, false }); // Normal states.push_back({ 1, true }); // Pressed states.push_back({ 1, false }); // Disabled @@ -1251,9 +1234,9 @@ bool GLToolbar::generate_icons_texture() const // if (sprite_size_px % 2 != 0) // sprite_size_px += 1; - bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, sprite_size_px, false); + bool res = const_cast(&m_icons_texture)->load_from_svg_files_as_sprites_array(filenames, states, sprite_size_px, false); if (res) - m_icons_texture_dirty = false; + *const_cast(&m_icons_texture_dirty) = false; return res; } @@ -1262,8 +1245,7 @@ bool GLToolbar::update_items_visibility() { bool ret = false; - for (GLToolbarItem* item : m_items) - { + for (GLToolbarItem* item : m_items) { ret |= item->update_visibility(); } @@ -1272,12 +1254,10 @@ bool GLToolbar::update_items_visibility() // updates separators visibility to avoid having two of them consecutive bool any_item_visible = false; - for (GLToolbarItem* item : m_items) - { + for (GLToolbarItem* item : m_items) { if (!item->is_separator()) any_item_visible |= item->is_visible(); - else - { + else { item->set_visible(any_item_visible); any_item_visible = false; } diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index 74e18de975..3237c44953 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -233,10 +233,10 @@ private: EType m_type; std::string m_name; bool m_enabled; - mutable GLTexture m_icons_texture; - mutable bool m_icons_texture_dirty; + GLTexture m_icons_texture; + bool m_icons_texture_dirty; BackgroundTexture m_background_texture; - mutable Layout m_layout; + Layout m_layout; ItemsList m_items; diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 61ec4d2331..f3a8f6ba1a 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -177,41 +177,10 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt } break; case coEnum:{ -#if 0 auto *opt = opt_def->default_value.get()->clone(); - opt->setInt(0); + opt->setInt(boost::any_cast(value)); config.set_key_value(opt_key, opt); -#else - if (opt_key == "top_fill_pattern" || - opt_key == "bottom_fill_pattern" || - opt_key == "fill_pattern") - config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); - else if (opt_key.compare("ironing_type") == 0) - config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); - else if (opt_key.compare("fuzzy_skin") == 0) - config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); - else if (opt_key.compare("gcode_flavor") == 0) - config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); - else if (opt_key.compare("machine_limits_usage") == 0) - config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); - else if (opt_key.compare("support_material_pattern") == 0) - config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); - else if (opt_key.compare("support_material_interface_pattern") == 0) - config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); - else if (opt_key.compare("seam_position") == 0) - config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); - else if (opt_key.compare("host_type") == 0) - config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); - else if (opt_key.compare("display_orientation") == 0) - config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); - else if(opt_key.compare("support_pillar_connection_mode") == 0) - config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); - else if(opt_key == "printhost_authorization_type") - config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); - else if(opt_key == "brim_type") - config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); } -#endif break; case coPoints:{ if (opt_key == "bed_shape" || opt_key == "thumbnails") { diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 90e1780903..3023041f6d 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -690,8 +690,8 @@ bool GUI_App::init_opengl() void GUI_App::init_app_config() { // Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release. - SetAppName(SLIC3R_APP_KEY); -// SetAppName(SLIC3R_APP_KEY "-beta"); +// SetAppName(SLIC3R_APP_KEY); + SetAppName(SLIC3R_APP_KEY "-alpha"); // SetAppDisplayName(SLIC3R_APP_NAME); // Set the Slic3r data directory at the Slic3r XS module. @@ -835,14 +835,10 @@ bool GUI_App::on_init_inner() if (is_editor()) { #ifdef __WXMSW__ -#if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN if (app_config->get("associate_3mf") == "1") -#endif // ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN associate_3mf_files(); -#if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN if (app_config->get("associate_stl") == "1") associate_stl_files(); -#endif // ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN #endif // __WXMSW__ preset_updater = new PresetUpdater(); @@ -858,9 +854,7 @@ bool GUI_App::on_init_inner() } else { #ifdef __WXMSW__ -#if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN if (app_config->get("associate_gcode") == "1") -#endif // ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN associate_gcode_files(); #endif // __WXMSW__ } @@ -1722,7 +1716,6 @@ void GUI_App::add_config_menu(wxMenuBar *menu) if (dlg.seq_top_layer_only_changed()) #endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER this->plater_->refresh_print(); -#if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN #ifdef _WIN32 if (is_editor()) { if (app_config->get("associate_3mf") == "1") @@ -1735,7 +1728,6 @@ void GUI_App::add_config_menu(wxMenuBar *menu) associate_gcode_files(); } #endif // _WIN32 -#endif // ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN } if (app_layout_changed) { // hide full main_sizer for mainFrame @@ -1986,6 +1978,11 @@ wxNotebook* GUI_App::tab_panel() const return mainframe->m_tabpanel; } +NotificationManager* GUI_App::notification_manager() +{ + return plater_->get_notification_manager(); +} + // extruders count from selected printer preset int GUI_App::extruders_cnt() const { @@ -2303,7 +2300,6 @@ void GUI_App::associate_3mf_files() ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); } -#if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN void GUI_App::associate_stl_files() { wchar_t app_path[MAX_PATH]; @@ -2327,7 +2323,6 @@ void GUI_App::associate_stl_files() // notify Windows only when any of the values gets changed ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); } -#endif // ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN void GUI_App::associate_gcode_files() { diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 132776dcb3..f1ee0746a0 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -43,6 +43,7 @@ class ObjectSettings; class ObjectList; class ObjectLayers; class Plater; +class NotificationManager; struct GUI_InitParams; @@ -226,14 +227,14 @@ public: void MacOpenFiles(const wxArrayString &fileNames) override; #endif /* __APPLE */ - Sidebar& sidebar(); - ObjectManipulation* obj_manipul(); - ObjectSettings* obj_settings(); - ObjectList* obj_list(); - ObjectLayers* obj_layers(); - Plater* plater(); - Model& model(); - + Sidebar& sidebar(); + ObjectManipulation* obj_manipul(); + ObjectSettings* obj_settings(); + ObjectList* obj_list(); + ObjectLayers* obj_layers(); + Plater* plater(); + Model& model(); + NotificationManager* notification_manager(); // Parameters extracted from the command line to be passed to GUI after initialization. const GUI_InitParams* init_params { nullptr }; @@ -279,13 +280,11 @@ public: bool is_gl_version_greater_or_equal_to(unsigned int major, unsigned int minor) const { return m_opengl_mgr.get_gl_info().is_version_greater_or_equal_to(major, minor); } bool is_glsl_version_greater_or_equal_to(unsigned int major, unsigned int minor) const { return m_opengl_mgr.get_gl_info().is_glsl_version_greater_or_equal_to(major, minor); } -#if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN #ifdef __WXMSW__ void associate_3mf_files(); void associate_stl_files(); void associate_gcode_files(); #endif // __WXMSW__ -#endif // ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN private: bool on_init_inner(); @@ -297,13 +296,6 @@ private: bool config_wizard_startup(); void check_updates(const bool verbose); - -#if !ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN -#ifdef __WXMSW__ - void associate_3mf_files(); - void associate_gcode_files(); -#endif // __WXMSW__ -#endif // !ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN }; DECLARE_APP(GUI_App) diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp new file mode 100644 index 0000000000..01ea53ad37 --- /dev/null +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -0,0 +1,1034 @@ +#include "libslic3r/libslic3r.h" +#include "libslic3r/PresetBundle.hpp" +#include "libslic3r/Model.hpp" + +#include "GUI_Factories.hpp" +#include "GUI_ObjectList.hpp" +#include "GUI_App.hpp" +#include "I18N.hpp" +#include "Plater.hpp" +#include "ObjectDataViewModel.hpp" + +#include "OptionsGroup.hpp" +#include "GLCanvas3D.hpp" +#include "Selection.hpp" +#include "format.hpp" + +#include +#include "slic3r/Utils/FixModelByWin10.hpp" + +namespace Slic3r +{ +namespace GUI +{ + +static PrinterTechnology printer_technology() +{ + return wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology(); +} + +static int extruders_count() +{ + return wxGetApp().extruders_edited_cnt(); +} + +static bool is_improper_category(const std::string& category, const int extruders_cnt, const bool is_object_settings = true) +{ + return category.empty() || + (extruders_cnt == 1 && (category == "Extruders" || category == "Wipe options")) || + (!is_object_settings && category == "Support material"); +} + + +//------------------------------------- +// SettingsFactory +//------------------------------------- + +// pt_FFF +static SettingsFactory::Bundle 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_interface_pattern", "support_material_buildplate_only", + "support_material_spacing" } }, + { L("Wipe options") , { "wipe_into_infill", "wipe_into_objects" } } +}; + +// pt_SLA +static SettingsFactory::Bundle FREQ_SETTINGS_BUNDLE_SLA = +{ + { L("Pad and Support") , { "supports_enable", "pad_enable" } } +}; + +std::vector SettingsFactory::get_options(const bool is_part) +{ + if (printer_technology() == ptSLA) { + SLAPrintObjectConfig full_sla_config; + auto options = full_sla_config.keys(); + options.erase(find(options.begin(), options.end(), "layer_height")); + return options; + } + + PrintRegionConfig reg_config; + auto options = reg_config.keys(); + if (!is_part) { + PrintObjectConfig obj_config; + std::vector obj_options = obj_config.keys(); + options.insert(options.end(), obj_options.begin(), obj_options.end()); + } + return options; +} + +SettingsFactory::Bundle SettingsFactory::get_bundle(const DynamicPrintConfig* config, bool is_object_settings) +{ + auto opt_keys = config->keys(); + if (opt_keys.empty()) + return Bundle(); + + // update options list according to print technology + auto full_current_opts = get_options(!is_object_settings); + 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); + + if (opt_keys.empty()) + return Bundle(); + + const int extruders_cnt = wxGetApp().extruders_edited_cnt(); + + Bundle bundle; + for (auto& opt_key : opt_keys) + { + auto category = config->def()->get(opt_key)->category; + if (is_improper_category(category, extruders_cnt, is_object_settings)) + 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; +} + +// Fill CategoryItem +std::map SettingsFactory::CATEGORY_ICON = +{ +// settings category name related bitmap name + // ptFFF + { L("Layers and Perimeters"), "layers" }, + { L("Infill") , "infill" }, + { L("Ironing") , "ironing" }, + { L("Fuzzy Skin") , "fuzzy_skin" }, + { L("Support material") , "support" }, + { L("Speed") , "time" }, + { L("Extruders") , "funnel" }, + { L("Extrusion Width") , "funnel" }, + { L("Wipe options") , "funnel" }, + { L("Skirt and brim") , "skirt+brim" }, +// { L("Speed > Acceleration") , "time" }, + { L("Advanced") , "wrench" }, + // ptSLA , + { L("Supports") , "support" }, + { L("Pad") , "pad" }, + { L("Hollowing") , "hollowing" } +}; + +wxBitmap SettingsFactory::get_category_bitmap(const std::string& category_name) +{ + if (CATEGORY_ICON.find(category_name) == CATEGORY_ICON.end()) + return wxNullBitmap; + return create_scaled_bitmap(CATEGORY_ICON.at(category_name)); +} + + +//------------------------------------- +// MenuFactory +//------------------------------------- + +// Note: id accords to type of the sub-object (adding volume), so sequence of the menu items is important +std::vector> MenuFactory::ADD_VOLUME_MENU_ITEMS = { +// menu_item Name menu_item bitmap name + {L("Add part"), "add_part" }, // ~ModelVolumeType::MODEL_PART + {L("Add modifier"), "add_modifier"}, // ~ModelVolumeType::PARAMETER_MODIFIER + {L("Add support enforcer"), "support_enforcer"}, // ~ModelVolumeType::SUPPORT_ENFORCER + {L("Add support blocker"), "support_blocker"} // ~ModelVolumeType::SUPPORT_BLOCKER +}; + +static Plater* plater() +{ + return wxGetApp().plater(); +} + +static ObjectList* obj_list() +{ + return wxGetApp().obj_list(); +} + +static ObjectDataViewModel* list_model() +{ + return wxGetApp().obj_list()->GetModel(); +} + +static const Selection& get_selection() +{ + return plater()->canvas3D()->get_selection(); +} + +// category -> vector ( option ; label ) +typedef std::map< std::string, std::vector< std::pair > > FullSettingsHierarchy; +static void get_full_settings_hierarchy(FullSettingsHierarchy& settings_menu, const bool is_part) +{ + auto options = SettingsFactory::get_options(is_part); + + const int extruders_cnt = extruders_count(); + + DynamicPrintConfig config; + for (auto& option : options) + { + auto const opt = config.def()->get(option); + auto category = opt->category; + if (is_improper_category(category, extruders_cnt, !is_part)) + continue; + + const std::string& label = !opt->full_label.empty() ? opt->full_label : opt->label; + std::pair option_label(option, label); + std::vector< std::pair > new_category; + auto& cat_opt_label = settings_menu.find(category) == settings_menu.end() ? new_category : settings_menu.at(category); + cat_opt_label.push_back(option_label); + if (cat_opt_label.size() == 1) + settings_menu[category] = cat_opt_label; + } +} + +static wxMenu* create_settings_popupmenu(wxMenu* parent_menu, const bool is_object_settings, wxDataViewItem item/*, ModelConfig& config*/) +{ + wxMenu* menu = new wxMenu; + + FullSettingsHierarchy categories; + get_full_settings_hierarchy(categories, !is_object_settings); + + auto get_selected_options_for_category = [categories, item](const wxString& category_name) { + wxArrayString names; + wxArrayInt selections; + + std::vector< std::pair > category_options; + for (auto& cat : categories) { + if (_(cat.first) == category_name) { + ModelConfig& config = obj_list()->get_item_config(item); + auto opt_keys = config.keys(); + + int sel = 0; + for (const std::pair& pair : cat.second) { + names.Add(_(pair.second)); + if (find(opt_keys.begin(), opt_keys.end(), pair.first) != opt_keys.end()) + selections.Add(sel); + sel++; + category_options.push_back(std::make_pair(pair.first, false)); + } + break; + } + } + + if (!category_options.empty() && + wxGetSelectedChoices(selections, _L("Select showing settings"), category_name, names) != -1) { + for (auto sel : selections) + category_options[sel].second = true; + } + return category_options; + +#if 0 + if (selections.size() > 0) + { + // Add selected items to the "Quick menu" + SettingsFactory::Bundle& freq_settings = printer_technology() == ptSLA ? + m_freq_settings_sla : m_freq_settings_fff; + bool changed_existing = false; + + std::vector tmp_freq_cat = {}; + + for (auto& cat : freq_settings) + { + if (_(cat.first) == category_name) + { + std::vector& freq_settings_category = cat.second; + freq_settings_category.clear(); + freq_settings_category.reserve(selection_cnt); + for (auto sel : selections) + freq_settings_category.push_back((*settings_list)[sel].first); + + changed_existing = true; + break; + } + } + + if (!changed_existing) + { + // Create new "Quick menu" item + for (auto& cat : settings_menu) + { + if (_(cat.first) == category_name) + { + freq_settings[cat.first] = std::vector{}; + + std::vector& freq_settings_category = freq_settings.find(cat.first)->second; + freq_settings_category.reserve(selection_cnt); + for (auto sel : selections) + freq_settings_category.push_back((*settings_list)[sel].first); + break; + } + } + } + } +#endif + }; + + for (auto cat : categories) { + append_menu_item(menu, wxID_ANY, _(cat.first), "", + [menu, item, get_selected_options_for_category](wxCommandEvent& event) { + std::vector< std::pair > category_options = get_selected_options_for_category(menu->GetLabel(event.GetId())); + obj_list()->add_category_to_settings_from_selection(category_options, item); + }, SettingsFactory::get_category_bitmap(cat.first), parent_menu, + []() { return true; }, plater()); + } + + return menu; +} + +static void create_freq_settings_popupmenu(wxMenu* menu, const bool is_object_settings, wxDataViewItem item) +{ + // Add default settings bundles + const SettingsFactory::Bundle& bundle = printer_technology() == ptFFF ? FREQ_SETTINGS_BUNDLE_FFF : FREQ_SETTINGS_BUNDLE_SLA; + + const int extruders_cnt = extruders_count(); + + for (auto& category : bundle) { + if (is_improper_category(category.first, extruders_cnt, is_object_settings)) + continue; + + append_menu_item(menu, wxID_ANY, _(category.first), "", + [menu, item, is_object_settings, bundle](wxCommandEvent& event) { + wxString category_name = menu->GetLabel(event.GetId()); + std::vector options; + for (auto& category : bundle) + if (category_name == _(category.first)) { + options = category.second; + break; + } + if (options.empty()) + return; + // 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 (!is_object_settings && category_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); + } + + obj_list()->add_category_to_settings_from_frequent(options, item); + }, + SettingsFactory::get_category_bitmap(category.first), menu, + []() { return true; }, plater()); + } +#if 0 + // Add "Quick" settings bundles + const SettingsFactory::Bundle& bundle_quick = printer_technology() == ptFFF ? m_freq_settings_fff : m_freq_settings_sla; + + for (auto& category : bundle_quick) { + if (is_improper_category(category.first, extruders_cnt)) + continue; + + append_menu_item(menu, wxID_ANY, from_u8((boost::format(_utf8(L("Quick Add Settings (%s)"))) % _(it.first)).str()), "", + [menu, item, is_object_settings, bundle](wxCommandEvent& event) { + wxString category_name = menu->GetLabel(event.GetId()); + std::vector options; + for (auto& category : bundle) + if (category_name == from_u8((boost::format(_L("Quick Add Settings (%s)")) % _(category.first)).str())) { + options = category.second; + break; + } + if (options.empty()) + return; + // 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 (!is_object_settings) { + const auto layer_height_it = std::find(options.begin(), options.end(), "layer_height"); + if (layer_height_it != options.end()) + options.erase(layer_height_it); + } + obj_list()->add_category_to_settings_from_frequent(options, item); + }, + SettingsFactory::get_category_bitmap(category.first), menu, + [this]() { return true; }, plater()); + } +#endif +} + +std::vector MenuFactory::get_volume_bitmaps() +{ + std::vector volume_bmps; + volume_bmps.reserve(ADD_VOLUME_MENU_ITEMS.size()); + for (auto item : ADD_VOLUME_MENU_ITEMS) + volume_bmps.push_back(create_scaled_bitmap(item.second)); + return volume_bmps; +} + +void MenuFactory::append_menu_item_delete(wxMenu* menu) +{ + append_menu_item(menu, wxID_ANY, _L("Delete") + "\tDel", _L("Remove the selected object"), + [](wxCommandEvent&) { plater()->remove_selected(); }, "delete", nullptr, + []() { return plater()->can_delete(); }, m_parent); + + menu->AppendSeparator(); + +} + +wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType type) { + auto sub_menu = new wxMenu; + + if (wxGetApp().get_mode() == comExpert && type != ModelVolumeType::INVALID) { + append_menu_item(sub_menu, wxID_ANY, _L("Load") + " " + dots, "", + [type](wxCommandEvent&) { obj_list()->load_subobject(type); }, "", menu); + sub_menu->AppendSeparator(); + } + + for (auto& item : { L("Box"), L("Cylinder"), L("Sphere"), L("Slab") }) + { + if (type == ModelVolumeType::INVALID && strncmp(item, "Slab", 4) == 0) + continue; + append_menu_item(sub_menu, wxID_ANY, _(item), "", + [type, item](wxCommandEvent&) { obj_list()->load_generic_subobject(item, type); }, "", menu); + } + + return sub_menu; +} + +void MenuFactory::append_menu_items_add_volume(wxMenu* menu) +{ + // Update "add" items(delete old & create new) settings popupmenu + for (auto& item : ADD_VOLUME_MENU_ITEMS) { + const auto settings_id = menu->FindItem(_(item.first)); + if (settings_id != wxNOT_FOUND) + menu->Destroy(settings_id); + } + + const ConfigOptionMode mode = wxGetApp().get_mode(); + + if (mode == comAdvanced) { + append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].first), "", + [](wxCommandEvent&) { obj_list()->load_subobject(ModelVolumeType::MODEL_PART); }, + ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].second, nullptr, + []() { return obj_list()->is_instance_or_object_selected(); }, m_parent); + } + if (mode == comSimple) { + append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)].first), "", + [](wxCommandEvent&) { obj_list()->load_generic_subobject(L("Box"), ModelVolumeType::SUPPORT_ENFORCER); }, + ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)].second, nullptr, + []() { return obj_list()->is_instance_or_object_selected(); }, m_parent); + append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER)].first), "", + [](wxCommandEvent&) { obj_list()->load_generic_subobject(L("Box"), ModelVolumeType::SUPPORT_BLOCKER); }, + ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER)].second, nullptr, + []() { return obj_list()->is_instance_or_object_selected(); }, m_parent); + + return; + } + + for (size_t type = (mode == comExpert ? 0 : 1); type < ADD_VOLUME_MENU_ITEMS.size(); type++) + { + auto& item = ADD_VOLUME_MENU_ITEMS[type]; + + wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType(type)); + append_submenu(menu, sub_menu, wxID_ANY, _(item.first), "", item.second, + []() { return obj_list()->is_instance_or_object_selected(); }, m_parent); + } +} + +wxMenuItem* MenuFactory::append_menu_item_layers_editing(wxMenu* menu) +{ + return append_menu_item(menu, wxID_ANY, _L("Height range Modifier"), "", + [](wxCommandEvent&) { obj_list()->layers_editing(); }, "edit_layers_all", menu, + []() { return obj_list()->is_instance_or_object_selected(); }, m_parent); +} + +wxMenuItem* MenuFactory::append_menu_item_settings(wxMenu* menu_) +{ + MenuWithSeparators* menu = dynamic_cast(menu_); + + const wxString menu_name = _L("Add settings"); + // Delete old items from settings popupmenu + auto settings_id = menu->FindItem(menu_name); + if (settings_id != wxNOT_FOUND) + menu->Destroy(settings_id); + + for (auto& it : FREQ_SETTINGS_BUNDLE_FFF) + { + settings_id = menu->FindItem(_(it.first)); + if (settings_id != wxNOT_FOUND) + menu->Destroy(settings_id); + } + for (auto& it : FREQ_SETTINGS_BUNDLE_SLA) + { + settings_id = menu->FindItem(_(it.first)); + if (settings_id != wxNOT_FOUND) + menu->Destroy(settings_id); + } +#if 0 + for (auto& it : m_freq_settings_fff) + { + settings_id = menu->FindItem(from_u8((boost::format(_utf8(L("Quick Add Settings (%s)"))) % _(it.first)).str())); + if (settings_id != wxNOT_FOUND) + menu->Destroy(settings_id); + } + for (auto& it : m_freq_settings_sla) + { + settings_id = menu->FindItem(from_u8((boost::format(_utf8(L("Quick Add Settings (%s)"))) % _(it.first)).str())); + if (settings_id != wxNOT_FOUND) + menu->Destroy(settings_id); + } +#endif + menu->DestroySeparators(); // delete old separators + + // If there are selected more then one instance but not all of them + // don't add settings menu items + const Selection& selection = get_selection(); + if ((selection.is_multiple_full_instance() && !selection.is_single_full_object()) || + selection.is_multiple_volume() || selection.is_mixed()) // more than one volume(part) is selected on the scene + return nullptr; + + const auto sel_vol = obj_list()->get_selected_model_volume(); + if (sel_vol && sel_vol->type() >= ModelVolumeType::SUPPORT_ENFORCER) + return nullptr; + + const ConfigOptionMode mode = wxGetApp().get_mode(); + if (mode == comSimple) + return nullptr; + + // Create new items for settings popupmenu + + if (printer_technology() == ptFFF || + (menu->GetMenuItems().size() > 0 && !menu->GetMenuItems().back()->IsSeparator())) + menu->SetFirstSeparator(); + + // detect itemm for adding of the setting + ObjectList* object_list = obj_list(); + ObjectDataViewModel* obj_model = list_model(); + + const wxDataViewItem sel_item = // when all instances in object are selected + object_list->GetSelectedItemsCount() > 1 && selection.is_single_full_object() ? + obj_model->GetItemById(selection.get_object_idx()) : + object_list->GetSelection(); + if (!sel_item) + return nullptr; + + // If we try to add settings for object/part from 3Dscene, + // for the second try there is selected ItemSettings in ObjectList. + // So, check if selected item isn't SettingsItem. And get a SettingsItem's parent item, if yes + wxDataViewItem item = obj_model->GetItemType(sel_item) & itSettings ? obj_model->GetParent(sel_item) : sel_item; + const ItemType item_type = obj_model->GetItemType(item); + const bool is_object_settings = !(item_type& itVolume || item_type & itLayer); + + // Add frequently settings + create_freq_settings_popupmenu(menu, is_object_settings, item); + + if (mode == comAdvanced) + return nullptr; + + menu->SetSecondSeparator(); + + // Add full settings list + auto menu_item = new wxMenuItem(menu, wxID_ANY, menu_name); + menu_item->SetBitmap(create_scaled_bitmap("cog")); + menu_item->SetSubMenu(create_settings_popupmenu(menu, is_object_settings, item)); + + return menu->Append(menu_item); +} + +wxMenuItem* MenuFactory::append_menu_item_change_type(wxMenu* menu) +{ + return append_menu_item(menu, wxID_ANY, _L("Change type"), "", + [](wxCommandEvent&) { obj_list()->change_part_type(); }, "", menu, + []() { + wxDataViewItem item = obj_list()->GetSelection(); + return item.IsOk() || obj_list()->GetModel()->GetItemType(item) == itVolume; + }, m_parent); +} + +wxMenuItem* MenuFactory::append_menu_item_instance_to_object(wxMenu* menu) +{ + wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _L("Set as a Separated Object"), "", + [](wxCommandEvent&) { obj_list()->split_instances(); }, "", menu); + + /* New behavior logic: + * 1. Split Object to several separated object, if ALL instances are selected + * 2. Separate selected instances from the initial object to the separated object, + * if some (not all) instances are selected + */ + m_parent->Bind(wxEVT_UPDATE_UI, [](wxUpdateUIEvent& evt) + { + const Selection& selection = plater()->canvas3D()->get_selection(); + evt.SetText(selection.is_single_full_object() ? + _L("Set as a Separated Objects") : _L("Set as a Separated Object")); + + evt.Enable(plater()->can_set_instance_to_object()); + }, menu_item->GetId()); + + return menu_item; +} + +wxMenuItem* MenuFactory::append_menu_item_printable(wxMenu* menu) +{ + wxMenuItem* menu_item_printable = append_menu_check_item(menu, wxID_ANY, _L("Printable"), "", + [](wxCommandEvent& ) { obj_list()->toggle_printable_state(); }, menu); + + m_parent->Bind(wxEVT_UPDATE_UI, [](wxUpdateUIEvent& evt) { + ObjectList* list = obj_list(); + wxDataViewItemArray sels; + list->GetSelections(sels); + wxDataViewItem frst_item = sels[0]; + ItemType type = list->GetModel()->GetItemType(frst_item); + bool check; + if (type != itInstance && type != itObject) + check = false; + else { + int obj_idx = list->GetModel()->GetObjectIdByItem(frst_item); + int inst_idx = type == itObject ? 0 : list->GetModel()->GetInstanceIdByItem(frst_item); + check = list->object(obj_idx)->instances[inst_idx]->printable; + } + + evt.Check(check); + plater()->set_current_canvas_as_dirty(); + + }, menu_item_printable->GetId()); + + return menu_item_printable; +} + +void MenuFactory::append_menu_items_osx(wxMenu* menu) +{ + append_menu_item(menu, wxID_ANY, _L("Rename"), "", + [](wxCommandEvent&) { obj_list()->rename_item(); }, "", menu); + + menu->AppendSeparator(); +} + +wxMenuItem* MenuFactory::append_menu_item_fix_through_netfabb(wxMenu* menu) +{ + if (!is_windows10()) + return nullptr; + wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _L("Fix through the Netfabb"), "", + [](wxCommandEvent&) { obj_list()->fix_through_netfabb(); }, "", menu, + []() {return plater()->can_fix_through_netfabb(); }, plater()); + menu->AppendSeparator(); + + return menu_item; +} + +void MenuFactory::append_menu_item_export_stl(wxMenu* menu) +{ + append_menu_item(menu, wxID_ANY, _L("Export as STL") + dots, "", + [](wxCommandEvent&) { plater()->export_stl(false, true); }, "", nullptr, + []() { + const Selection& selection = plater()->canvas3D()->get_selection(); + return selection.is_single_full_instance() || selection.is_single_full_object(); + }, m_parent); + menu->AppendSeparator(); +} + +void MenuFactory::append_menu_item_reload_from_disk(wxMenu* menu) +{ + append_menu_item(menu, wxID_ANY, _L("Reload from disk"), _L("Reload the selected volumes from disk"), + [](wxCommandEvent&) { plater()->reload_from_disk(); }, "", menu, + []() { return plater()->can_reload_from_disk(); }, m_parent); +} + +void MenuFactory::append_menu_item_change_extruder(wxMenu* menu) +{ + const std::vector names = { _L("Change extruder"), _L("Set extruder for selected items") }; + // Delete old menu item + for (const wxString& name : names) { + const int item_id = menu->FindItem(name); + if (item_id != wxNOT_FOUND) + menu->Destroy(item_id); + } + + const int extruders_cnt = extruders_count(); + if (extruders_cnt <= 1) + return; + + wxDataViewItemArray sels; + obj_list()->GetSelections(sels); + if (sels.IsEmpty()) + return; + + std::vector icons = get_extruder_color_icons(true); + wxMenu* extruder_selection_menu = new wxMenu(); + const wxString& name = sels.Count() == 1 ? names[0] : names[1]; + + int initial_extruder = -1; // negative value for multiple object/part selection + if (sels.Count() == 1) { + const ModelConfig& config = obj_list()->get_item_config(sels[0]); + initial_extruder = config.has("extruder") ? config.extruder() : 0; + } + + for (int i = 0; i <= extruders_cnt; i++) + { + bool is_active_extruder = i == initial_extruder; + int icon_idx = i == 0 ? 0 : i - 1; + + const wxString& item_name = (i == 0 ? _L("Default") : wxString::Format(_L("Extruder %d"), i)) + + (is_active_extruder ? " (" + _L("active") + ")" : ""); + + append_menu_item(extruder_selection_menu, wxID_ANY, item_name, "", + [i](wxCommandEvent&) { obj_list()->set_extruder_for_selected_items(i); }, *icons[icon_idx], menu, + [is_active_extruder]() { return !is_active_extruder; }, m_parent); + + } + + menu->AppendSubMenu(extruder_selection_menu, name); +} + +void MenuFactory::append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu) +{ + append_menu_item(menu, wxID_ANY, _L("Scale to print volume"), _L("Scale the selected object to fit the print volume"), + [](wxCommandEvent&) { plater()->scale_selection_to_fit_print_volume(); }, "", menu); +} + +void MenuFactory::append_menu_items_convert_unit(wxMenu* menu, int insert_pos/* = 1*/) +{ + std::vector obj_idxs, vol_idxs; + obj_list()->get_selection_indexes(obj_idxs, vol_idxs); + if (obj_idxs.empty() && vol_idxs.empty()) + return; + + auto volume_respects_conversion = [](ModelVolume* volume, ConversionType conver_type) + { + return (conver_type == ConversionType::CONV_FROM_INCH && volume->source.is_converted_from_inches) || + (conver_type == ConversionType::CONV_TO_INCH && !volume->source.is_converted_from_inches) || + (conver_type == ConversionType::CONV_FROM_METER && volume->source.is_converted_from_meters) || + (conver_type == ConversionType::CONV_TO_METER && !volume->source.is_converted_from_meters); + }; + + auto can_append = [obj_idxs, vol_idxs, volume_respects_conversion](ConversionType conver_type) + { + ModelObjectPtrs objects; + for (int obj_idx : obj_idxs) { + ModelObject* object = obj_list()->object(obj_idx); + if (vol_idxs.empty()) { + for (ModelVolume* volume : object->volumes) + if (volume_respects_conversion(volume, conver_type)) + return false; + } + else { + for (int vol_idx : vol_idxs) + if (volume_respects_conversion(object->volumes[vol_idx], conver_type)) + return false; + } + } + return true; + }; + + std::vector> items = { + {ConversionType::CONV_FROM_INCH , _L("Convert from imperial units") }, + {ConversionType::CONV_TO_INCH , _L("Revert conversion from imperial units") }, + {ConversionType::CONV_FROM_METER, _L("Convert from meters") }, + {ConversionType::CONV_TO_METER , _L("Revert conversion from meters") } }; + + for (auto item : items) { + int menu_id = menu->FindItem(item.second); + if (can_append(item.first)) { + // Add menu item if it doesn't exist + if (menu_id == wxNOT_FOUND) + append_menu_item(menu, wxID_ANY, item.second, item.second, + [item](wxCommandEvent&) { plater()->convert_unit(item.first); }, "", menu, + []() { return true; }, m_parent, insert_pos); + } + else if (menu_id != wxNOT_FOUND) { + // Delete menu item + menu->Destroy(menu_id); + } + } +} + +void MenuFactory::append_menu_item_merge_to_multipart_object(wxMenu* menu) +{ + menu->AppendSeparator(); + append_menu_item(menu, wxID_ANY, _L("Merge"), _L("Merge objects to the one multipart object"), + [](wxCommandEvent&) { obj_list()->merge(true); }, "", menu, + []() { return obj_list()->can_merge_to_multipart_object(); }, m_parent); +} +/* +void MenuFactory::append_menu_item_merge_to_single_object(wxMenu* menu) +{ + menu->AppendSeparator(); + append_menu_item(menu, wxID_ANY, _L("Merge"), _L("Merge objects to the one single object"), + [](wxCommandEvent&) { obj_list()->merge(false); }, "", menu, + []() { return obj_list()->can_merge_to_single_object(); }, m_parent); +} +*/ +void MenuFactory::append_menu_items_mirror(wxMenu* menu) +{ + wxMenu* mirror_menu = new wxMenu(); + if (!mirror_menu) + return; + + append_menu_item(mirror_menu, wxID_ANY, _L("Along X axis"), _L("Mirror the selected object along the X axis"), + [](wxCommandEvent&) { plater()->mirror(X); }, "mark_X", menu); + append_menu_item(mirror_menu, wxID_ANY, _L("Along Y axis"), _L("Mirror the selected object along the Y axis"), + [](wxCommandEvent&) { plater()->mirror(Y); }, "mark_Y", menu); + append_menu_item(mirror_menu, wxID_ANY, _L("Along Z axis"), _L("Mirror the selected object along the Z axis"), + [](wxCommandEvent&) { plater()->mirror(Z); }, "mark_Z", menu); + + append_submenu(menu, mirror_menu, wxID_ANY, _L("Mirror"), _L("Mirror the selected object"), "", + []() { return plater()->can_mirror(); }, m_parent); +} + +MenuFactory::MenuFactory() +{ + for (int i = 0; i < mtCount; i++) { + items_increase[i] = nullptr; + items_decrease[i] = nullptr; + items_set_number_of_copies[i] = nullptr; + } +} + +void MenuFactory::create_default_menu() +{ + wxMenu* sub_menu = append_submenu_add_generic(&m_default_menu, ModelVolumeType::INVALID); + append_submenu(&m_default_menu, sub_menu, wxID_ANY, _L("Add Shape"), "", "add_part", + []() {return true; }, m_parent); +} + +void MenuFactory::create_common_object_menu(wxMenu* menu) +{ +#ifdef __WXOSX__ + append_menu_items_osx(menu); +#endif // __WXOSX__ + append_menu_items_instance_manipulation(menu); + // Delete menu was moved to be after +/- instace to make it more difficult to be selected by mistake. + append_menu_item_delete(menu); + append_menu_item_instance_to_object(menu); + menu->AppendSeparator(); + + append_menu_item_printable(menu); + menu->AppendSeparator(); + + append_menu_item_reload_from_disk(menu); + append_menu_item_export_stl(menu); + // "Scale to print volume" makes a sense just for whole object + append_menu_item_scale_selection_to_fit_print_volume(menu); + + append_menu_item_fix_through_netfabb(menu); + append_menu_items_mirror(menu); +} + +void MenuFactory::create_object_menu() +{ + create_common_object_menu(&m_object_menu); + wxMenu* split_menu = new wxMenu(); + if (!split_menu) + return; + + append_menu_item(split_menu, wxID_ANY, _L("To objects"), _L("Split the selected object into individual objects"), + [](wxCommandEvent&) { plater()->split_object(); }, "split_object_SMALL", &m_object_menu, + []() { return plater()->can_split(true); }, m_parent); + append_menu_item(split_menu, wxID_ANY, _L("To parts"), _L("Split the selected object into individual parts"), + [](wxCommandEvent&) { plater()->split_volume(); }, "split_parts_SMALL", &m_object_menu, + []() { return plater()->can_split(false); }, m_parent); + + append_submenu(&m_object_menu, split_menu, wxID_ANY, _L("Split"), _L("Split the selected object"), "", + []() { return plater()->can_split(true) && wxGetApp().get_mode() > comSimple; }, m_parent); + m_object_menu.AppendSeparator(); + + // Layers Editing for object + append_menu_item_layers_editing(&m_object_menu); + m_object_menu.AppendSeparator(); + + // "Add (volumes)" popupmenu will be added later in append_menu_items_add_volume() +} + +void MenuFactory::create_sla_object_menu() +{ + create_common_object_menu(&m_sla_object_menu); + append_menu_item(&m_sla_object_menu, wxID_ANY, _L("Split"), _L("Split the selected object into individual objects"), + [](wxCommandEvent&) { plater()->split_object(); }, "split_object_SMALL", nullptr, + []() { return plater()->can_split(true); }, m_parent); + + m_sla_object_menu.AppendSeparator(); +} + +void MenuFactory::create_part_menu() +{ + wxMenu* menu = &m_part_menu; +#ifdef __WXOSX__ + append_menu_items_osx(menu); +#endif // __WXOSX__ + append_menu_item_delete(menu); + append_menu_item_reload_from_disk(menu); + append_menu_item_export_stl(menu); + append_menu_item_fix_through_netfabb(menu); + append_menu_items_mirror(menu); + + append_menu_item(menu, wxID_ANY, _L("Split"), _L("Split the selected object into individual parts"), + [](wxCommandEvent&) { plater()->split_volume(); }, "split_parts_SMALL", nullptr, + []() { return plater()->can_split(false); }, m_parent); + + menu->AppendSeparator(); + append_menu_item_change_type(menu); + +} + +void MenuFactory::create_instance_menu() +{ + wxMenu* menu = &m_instance_menu; + // create "Instance to Object" menu item + append_menu_item_instance_to_object(menu); + append_menu_item_printable(menu); +} + +void MenuFactory::init(wxWindow* parent) +{ + m_parent = parent; + + create_default_menu(); + create_object_menu(); + create_sla_object_menu(); + create_part_menu(); + create_instance_menu(); +} + +wxMenu* MenuFactory::default_menu() +{ + return &m_default_menu; +} + +wxMenu* MenuFactory::object_menu() +{ + append_menu_items_convert_unit(&m_object_menu, 11); + append_menu_item_settings(&m_object_menu); + append_menu_item_change_extruder(&m_object_menu); + update_menu_items_instance_manipulation(mtObjectFFF); + + return &m_object_menu; +} + +wxMenu* MenuFactory::sla_object_menu() +{ + append_menu_items_convert_unit(&m_sla_object_menu, 11); + append_menu_item_settings(&m_sla_object_menu); + update_menu_items_instance_manipulation(mtObjectSLA); + + return &m_sla_object_menu; +} + +wxMenu* MenuFactory::part_menu() +{ + append_menu_items_convert_unit(&m_part_menu, 2); + append_menu_item_settings(&m_part_menu); + append_menu_item_change_extruder(&m_part_menu); + + return &m_part_menu; +} + +wxMenu* MenuFactory::instance_menu() +{ + return &m_instance_menu; +} + +wxMenu* MenuFactory::layer_menu() +{ + MenuWithSeparators* menu = new MenuWithSeparators(); + append_menu_item_settings(menu); + + return menu; +} + +wxMenu* MenuFactory::multi_selection_menu() +{ + wxDataViewItemArray sels; + obj_list()->GetSelections(sels); + + for (const wxDataViewItem& item : sels) + if (!(list_model()->GetItemType(item) & (itVolume | itObject | itInstance))) + // show this menu only for Objects(Instances mixed with Objects)/Volumes selection + return nullptr; + + wxMenu* menu = new MenuWithSeparators(); + + append_menu_item_reload_from_disk(menu); + append_menu_items_convert_unit(menu); + if (obj_list()->can_merge_to_multipart_object()) + append_menu_item_merge_to_multipart_object(menu); + if (extruders_count() > 1) + append_menu_item_change_extruder(menu); + if (list_model()->GetItemType(sels[0]) != itVolume) + append_menu_item_printable(menu); + + return menu; +} + +void MenuFactory::append_menu_items_instance_manipulation(wxMenu* menu) +{ + MenuType type = menu == &m_object_menu ? mtObjectFFF : mtObjectSLA; + + items_increase[type] = append_menu_item(menu, wxID_ANY, _L("Add instance") + "\t+", _L("Add one more instance of the selected object"), + [](wxCommandEvent&) { plater()->increase_instances(); }, "add_copies", nullptr, + []() { return plater()->can_increase_instances(); }, m_parent); + items_decrease[type] = append_menu_item(menu, wxID_ANY, _L("Remove instance") + "\t-", _L("Remove one instance of the selected object"), + [](wxCommandEvent&) { plater()->decrease_instances(); }, "remove_copies", nullptr, + []() { return plater()->can_decrease_instances(); }, m_parent); + items_set_number_of_copies[type] = append_menu_item(menu, wxID_ANY, _L("Set number of instances") + dots, _L("Change the number of instances of the selected object"), + [](wxCommandEvent&) { plater()->set_number_of_copies(); }, "number_of_copies", nullptr, + []() { return plater()->can_increase_instances(); }, m_parent); + + append_menu_item(menu, wxID_ANY, _L("Fill bed with instances") + dots, _L("Fill the remaining area of bed with instances of the selected object"), + [](wxCommandEvent&) { plater()->fill_bed_with_instances(); }, "", nullptr, + []() { return plater()->can_increase_instances(); }, m_parent); + +} + +void MenuFactory::update_menu_items_instance_manipulation(MenuType type) +{ + wxMenu* menu = type == mtObjectFFF ? &m_object_menu : type == mtObjectSLA ? &m_sla_object_menu : nullptr; + if (menu) + return; + // Remove/Prepend "increase/decrease instances" menu items according to the view mode. + // Suppress to show those items for a Simple mode + if (wxGetApp().get_mode() == comSimple) { + if (menu->FindItem(_L("Add instance")) != wxNOT_FOUND) + { + // Detach an items from the menu, but don't delete them + // so that they can be added back later + // (after switching to the Advanced/Expert mode) + menu->Remove(items_increase[type]); + menu->Remove(items_decrease[type]); + menu->Remove(items_set_number_of_copies[type]); + } + } + else { + if (menu->FindItem(_L("Add instance")) == wxNOT_FOUND) + { + // Prepend items to the menu, if those aren't not there + menu->Prepend(items_set_number_of_copies[type]); + menu->Prepend(items_decrease[type]); + menu->Prepend(items_increase[type]); + } + } +} + +void MenuFactory::update_object_menu() +{ + append_menu_items_add_volume(&m_object_menu); +} + +void MenuFactory::msw_rescale() +{ + for (MenuWithSeparators* menu : { &m_object_menu, &m_sla_object_menu, &m_part_menu, &m_default_menu }) + msw_rescale_menu(dynamic_cast(menu)); +} + +} //namespace GUI +} //namespace Slic3r diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp new file mode 100644 index 0000000000..ffa17933ce --- /dev/null +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -0,0 +1,105 @@ +#ifndef slic3r_GUI_Factories_hpp_ +#define slic3r_GUI_Factories_hpp_ + +#include +#include +#include + +#include + +#include "libslic3r/PrintConfig.hpp" +#include "wxExtensions.hpp" + +class wxMenu; +class wxMenuItem; + +namespace Slic3r { + +enum class ModelVolumeType : int; + +namespace GUI { + +struct SettingsFactory +{ +// category -> vector ( option ) + typedef std::map> Bundle; + static std::map CATEGORY_ICON; + + static wxBitmap get_category_bitmap(const std::string& category_name); + static Bundle get_bundle(const DynamicPrintConfig* config, bool is_object_settings); + static std::vector get_options(bool is_part); +}; + +class MenuFactory +{ +public: + static std::vector> ADD_VOLUME_MENU_ITEMS; + static std::vector get_volume_bitmaps(); + + MenuFactory(); + ~MenuFactory() = default; + + void init(wxWindow* parent); + void update_object_menu(); + void msw_rescale(); + + wxMenu* default_menu(); + wxMenu* object_menu(); + wxMenu* sla_object_menu(); + wxMenu* part_menu(); + wxMenu* instance_menu(); + wxMenu* layer_menu(); + wxMenu* multi_selection_menu(); + +private: + enum MenuType { + mtObjectFFF = 0, + mtObjectSLA, + mtCount + }; + + wxWindow* m_parent {nullptr}; + + MenuWithSeparators m_object_menu; + MenuWithSeparators m_part_menu; + MenuWithSeparators m_sla_object_menu; + MenuWithSeparators m_default_menu; + MenuWithSeparators m_instance_menu; + + // Removed/Prepended Items according to the view mode + std::array items_increase; + std::array items_decrease; + std::array items_set_number_of_copies; + + void create_default_menu(); + void create_common_object_menu(wxMenu *menu); + void create_object_menu(); + void create_sla_object_menu(); + void create_part_menu(); + void create_instance_menu(); + + wxMenu* append_submenu_add_generic(wxMenu* menu, ModelVolumeType type); + void append_menu_items_add_volume(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); + wxMenuItem* append_menu_item_printable(wxMenu* menu); + void append_menu_items_osx(wxMenu* menu); + wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu); + void append_menu_item_export_stl(wxMenu* menu); + void append_menu_item_reload_from_disk(wxMenu* menu); + void append_menu_item_change_extruder(wxMenu* menu); + void append_menu_item_delete(wxMenu* menu); + void append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu); + void append_menu_items_convert_unit(wxMenu* menu, int insert_pos = 1); // Add "Conver/Revert..." menu items (from/to inches/meters) after "Reload From Disk" + void append_menu_item_merge_to_multipart_object(wxMenu *menu); +// void append_menu_item_merge_to_single_object(wxMenu *menu); + void append_menu_items_mirror(wxMenu *menu); + void append_menu_items_instance_manipulation(wxMenu *menu); + void update_menu_items_instance_manipulation(MenuType type); +}; + +}} + +#endif //slic3r_GUI_Factories_hpp_ diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index 8768f39ff7..725a396c34 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -260,16 +260,15 @@ void ObjectLayers::msw_rescale() editor->msw_rescale(); } - const std::vector btns = {2, 3}; // del_btn, add_btn - for (auto btn : btns) - { - wxSizerItem* b_item = item->GetSizer()->GetItem(btn); - if (b_item->IsWindow()) { - auto button = dynamic_cast(b_item->GetWindow()); - if (button != nullptr) - button->msw_rescale(); - } - } + if (item->GetSizer()->GetItemCount() > 2) // if there are Add/Del buttons + for (size_t btn : {2, 3}) { // del_btn, add_btn + wxSizerItem* b_item = item->GetSizer()->GetItem(btn); + if (b_item->IsWindow()) { + auto button = dynamic_cast(b_item->GetWindow()); + if (button != nullptr) + button->msw_rescale(); + } + } } } m_grid_sizer->Layout(); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 5247558c77..15c4578d82 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1,6 +1,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/PresetBundle.hpp" #include "GUI_ObjectList.hpp" +#include "GUI_Factories.hpp" #include "GUI_ObjectManipulation.hpp" #include "GUI_ObjectLayers.hpp" #include "GUI_App.hpp" @@ -16,6 +17,8 @@ #include "format.hpp" #include +#include + #include "slic3r/Utils/FixModelByWin10.hpp" #ifdef __WXMSW__ @@ -29,32 +32,6 @@ namespace GUI wxDEFINE_EVENT(EVT_OBJ_LIST_OBJECT_SELECT, SimpleEvent); -// pt_FFF -static 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_interface_pattern", "support_material_buildplate_only", - "support_material_spacing" } }, - { L("Wipe options") , { "wipe_into_infill", "wipe_into_objects" } } -}; - -// pt_SLA -static SettingsBundle FREQ_SETTINGS_BUNDLE_SLA = -{ - { L("Pad and Support") , { "supports_enable", "pad_enable" } } -}; - -// Note: id accords to type of the sub-object (adding volume), so sequence of the menu items is important -static std::vector> ADD_VOLUME_MENU_ITEMS = { -// menu_item Name menu_item bitmap name - {L("Add part"), "add_part" }, // ~ModelVolumeType::MODEL_PART - {L("Add modifier"), "add_modifier"}, // ~ModelVolumeType::PARAMETER_MODIFIER - {L("Add support enforcer"), "support_enforcer"}, // ~ModelVolumeType::SUPPORT_ENFORCER - {L("Add support blocker"), "support_blocker"} // ~ModelVolumeType::SUPPORT_BLOCKER -}; - static PrinterTechnology printer_technology() { return wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology(); @@ -84,34 +61,11 @@ static void take_snapshot(const wxString& snapshot_name) } ObjectList::ObjectList(wxWindow* parent) : - wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE), - m_parent(parent) + wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE) { - // Fill CATEGORY_ICON - { - // ptFFF - CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap("layers"); - CATEGORY_ICON[L("Infill")] = create_scaled_bitmap("infill"); - CATEGORY_ICON[L("Ironing")] = create_scaled_bitmap("ironing"); - CATEGORY_ICON[L("Support material")] = create_scaled_bitmap("support"); - CATEGORY_ICON[L("Speed")] = create_scaled_bitmap("time"); - CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap("funnel"); - CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap("funnel"); - CATEGORY_ICON[L("Wipe options")] = create_scaled_bitmap("funnel"); - CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap("skirt+brim"); -// CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap("time"); - CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap("wrench"); - // ptSLA - CATEGORY_ICON[L("Supports")] = create_scaled_bitmap("support"/*"sla_supports"*/); - CATEGORY_ICON[L("Pad")] = create_scaled_bitmap("pad"); - CATEGORY_ICON[L("Hollowing")] = create_scaled_bitmap("hollowing"); - } - // create control create_objects_ctrl(); - init_icons(); - // describe control behavior Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxDataViewEvent& event) { // detect the current mouse position here, to pass it to list_manipulation() method @@ -122,11 +76,6 @@ ObjectList::ObjectList(wxWindow* parent) : #endif #ifndef __APPLE__ - // On Windows and Linux, forces a kill focus emulation on the object manipulator fields because this event handler is called - // before the kill focus event handler on the object manipulator when changing selection in the list, invalidating the object - // manipulator cache with the following call to selection_changed() -// wxGetApp().obj_manipul()->emulate_kill_focus(); // It's not necessury anymore #ys_FIXME delete after testing - // On Windows and Linux: // It's not invoked KillFocus event for "temporary" panels (like "Manipulation panel", "Settings", "Layer ranges"), // if we change selection in object list. @@ -344,16 +293,6 @@ void ObjectList::create_objects_ctrl() } } -void ObjectList::create_popup_menus() -{ - // create popup menus for object and part - create_object_popupmenu(&m_menu_object); - create_part_popupmenu(&m_menu_part); - create_sla_object_popupmenu(&m_menu_sla_object); - create_instance_popupmenu(&m_menu_instance); - create_default_popupmenu(&m_menu_default); -} - void ObjectList::get_selected_item_indexes(int& obj_idx, int& vol_idx, const wxDataViewItem& input_item/* = wxDataViewItem(nullptr)*/) { const wxDataViewItem item = input_item == wxDataViewItem(nullptr) ? GetSelection() : input_item; @@ -378,10 +317,14 @@ void ObjectList::get_selection_indexes(std::vector& obj_idxs, std::vectorGetItemType(sels[0]) & itVolume) { + if ( m_objects_model->GetItemType(sels[0]) & itVolume || + (sels.Count()==1 && m_objects_model->GetItemType(m_objects_model->GetParent(sels[0])) & itVolume) ) { for (wxDataViewItem item : sels) { obj_idxs.emplace_back(m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item))); + if (sels.Count() == 1 && m_objects_model->GetItemType(m_objects_model->GetParent(item)) & itVolume) + item = m_objects_model->GetParent(item); + assert(m_objects_model->GetItemType(item) & itVolume); vol_idxs.emplace_back(m_objects_model->GetVolumeIdByItem(item)); } @@ -389,8 +332,6 @@ void ObjectList::get_selection_indexes(std::vector& obj_idxs, std::vectorGetItemType(item); - assert(type & (itObject | itInstance | itInstanceRoot)); - obj_idxs.emplace_back(type & itObject ? m_objects_model->GetIdByItem(item) : m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item))); } @@ -630,75 +571,6 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const (*m_objects)[obj_idx]->volumes[volume_id]->name = m_objects_model->GetName(item).ToUTF8().data(); } -void ObjectList::init_icons() -{ - m_bmp_solidmesh = ScalableBitmap(this, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART) ].second); - m_bmp_modifiermesh = ScalableBitmap(this, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::PARAMETER_MODIFIER)].second); - m_bmp_support_enforcer = ScalableBitmap(this, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER) ].second); - m_bmp_support_blocker = ScalableBitmap(this, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER) ].second); - - m_bmp_vector.reserve(4); // bitmaps for different types of parts - m_bmp_vector.push_back(&m_bmp_solidmesh.bmp()); - m_bmp_vector.push_back(&m_bmp_modifiermesh.bmp()); - m_bmp_vector.push_back(&m_bmp_support_enforcer.bmp()); - m_bmp_vector.push_back(&m_bmp_support_blocker.bmp()); - - - // Set volumes default bitmaps for the model - m_objects_model->SetVolumeBitmaps(m_bmp_vector); - - // init icon for manifold warning - m_bmp_manifold_warning = ScalableBitmap(this, "exclamation"); - // Set warning bitmap for the model - m_objects_model->SetWarningBitmap(&m_bmp_manifold_warning.bmp()); - - // init bitmap for "Add Settings" context menu - m_bmp_cog = ScalableBitmap(this, "cog"); -} - -void ObjectList::msw_rescale_icons() -{ - m_bmp_vector.clear(); - m_bmp_vector.reserve(4); // bitmaps for different types of parts - for (ScalableBitmap* bitmap : { &m_bmp_solidmesh, // Add part - &m_bmp_modifiermesh, // Add modifier - &m_bmp_support_enforcer, // Add support enforcer - &m_bmp_support_blocker }) // Add support blocker - { - bitmap->msw_rescale(); - m_bmp_vector.push_back(& bitmap->bmp()); - } - // Set volumes default bitmaps for the model - m_objects_model->SetVolumeBitmaps(m_bmp_vector); - - m_bmp_manifold_warning.msw_rescale(); - // Set warning bitmap for the model - m_objects_model->SetWarningBitmap(&m_bmp_manifold_warning.bmp()); - - m_bmp_cog.msw_rescale(); - - - // Update CATEGORY_ICON according to new scale - { - // ptFFF - CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap("layers"); - CATEGORY_ICON[L("Infill")] = create_scaled_bitmap("infill"); - CATEGORY_ICON[L("Ironing")] = create_scaled_bitmap("ironing"); - CATEGORY_ICON[L("Support material")] = create_scaled_bitmap("support"); - CATEGORY_ICON[L("Speed")] = create_scaled_bitmap("time"); - CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap("funnel"); - CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap("funnel"); - CATEGORY_ICON[L("Wipe options")] = create_scaled_bitmap("funnel"); - CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap("skirt+brim"); -// CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap("time"); - CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap("wrench"); - // ptSLA - CATEGORY_ICON[L("Supports")] = create_scaled_bitmap("support"/*"sla_supports"*/); - CATEGORY_ICON[L("Pad")] = create_scaled_bitmap("pad"); - } -} - - void ObjectList::selection_changed() { if (m_prevent_list_events) return; @@ -818,7 +690,7 @@ void ObjectList::paste_settings_into_list() assert(!config_cache.empty()); auto keys = config_cache.keys(); - auto part_options = get_options(true); + auto part_options = SettingsFactory::get_options(true); for (const std::string& opt_key: keys) { if (item_type & (itVolume | itLayer) && @@ -864,9 +736,7 @@ void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& vol } select_items(items); -//#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME selection_changed(); -//#endif //no __WXOSX__ //__WXMSW__ } void ObjectList::paste_objects_into_list(const std::vector& object_idxs) @@ -884,9 +754,7 @@ void ObjectList::paste_objects_into_list(const std::vector& object_idxs) wxGetApp().plater()->changed_objects(object_idxs); select_items(items); -//#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME selection_changed(); -//#endif //no __WXOSX__ //__WXMSW__ } #ifdef __WXOSX__ @@ -969,7 +837,7 @@ void ObjectList::list_manipulation(const wxPoint& mouse_pos, bool evt_context_me { const wxString title = col->GetTitle(); if (title == " ") - toggle_printable_state(item); + toggle_printable_state(); else if (title == _("Editing")) show_context_menu(evt_context_menu); else if (title == _("Name")) @@ -999,39 +867,35 @@ void ObjectList::list_manipulation(const wxPoint& mouse_pos, bool evt_context_me void ObjectList::show_context_menu(const bool evt_context_menu) { + wxMenu* menu {nullptr}; + Plater* plater = wxGetApp().plater(); + if (multiple_selection()) { if (selected_instances_of_same_object()) - wxGetApp().plater()->PopupMenu(&m_menu_instance); + menu = plater->instance_menu(); else - show_multi_selection_menu(); - - return; + menu = plater->multi_selection_menu(); } + else { + const auto item = GetSelection(); + if (item) + { + const ItemType type = m_objects_model->GetItemType(item); + if (!(type & (itObject | itVolume | itLayer | itInstance))) + return; - const auto item = GetSelection(); - wxMenu* menu {nullptr}; - if (item) - { - const ItemType type = m_objects_model->GetItemType(item); - if (!(type & (itObject | itVolume | itLayer | itInstance))) - return; - - menu = type & itInstance ? &m_menu_instance : - type & itLayer ? &m_menu_layer : - m_objects_model->GetParent(item) != wxDataViewItem(nullptr) ? &m_menu_part : - printer_technology() == ptFFF ? &m_menu_object : &m_menu_sla_object; - - if (type & (itObject | itVolume)) - append_menu_items_convert_unit(menu); - if (!(type & itInstance)) - append_menu_item_settings(menu); + menu = type & itInstance ? plater->instance_menu() : + type & itLayer ? plater->layer_menu() : + m_objects_model->GetParent(item) != wxDataViewItem(nullptr) ? plater->part_menu() : + printer_technology() == ptFFF ? plater->object_menu() : plater->sla_object_menu(); + } + else if (evt_context_menu) + menu = plater->default_menu(); } - else if (evt_context_menu) - menu = &m_menu_default; if (menu) - wxGetApp().plater()->PopupMenu(menu); + plater->PopupMenu(menu); } void ObjectList::extruder_editing() @@ -1271,15 +1135,6 @@ void ObjectList::OnDrop(wxDataViewEvent &event) return; } -// It looks like a fixed in current version of the wxWidgets -// #ifdef __WXGTK__ -// /* Under GTK, DnD moves an item between another two items. -// * And event.GetItem() return item, which is under "insertion line" -// * So, if we move item down we should to decrease the to_volume_id value -// **/ -// if (to_volume_id > from_volume_id) to_volume_id--; -// #endif // __WXGTK__ - take_snapshot(_((m_dragged_data.type() == itVolume) ? L("Volumes in Object reordered") : L("Object reordered"))); if (m_dragged_data.type() & itVolume) @@ -1317,207 +1172,34 @@ void ObjectList::OnDrop(wxDataViewEvent &event) wxGetApp().plater()->set_current_canvas_as_dirty(); } - -// Context Menu - -std::vector ObjectList::get_options(const bool is_part) +void ObjectList::add_category_to_settings_from_selection(const std::vector< std::pair >& category_options, wxDataViewItem item) { - if (printer_technology() == ptSLA) { - SLAPrintObjectConfig full_sla_config; - auto options = full_sla_config.keys(); - options.erase(find(options.begin(), options.end(), "layer_height")); - return options; - } - - PrintRegionConfig reg_config; - auto options = reg_config.keys(); - if (!is_part) { - PrintObjectConfig obj_config; - std::vector obj_options = obj_config.keys(); - options.insert(options.end(), obj_options.begin(), obj_options.end()); - } - return options; -} - -const std::vector& ObjectList::get_options_for_bundle(const wxString& bundle_name) -{ - const SettingsBundle& bundle = printer_technology() == ptSLA ? - FREQ_SETTINGS_BUNDLE_SLA : FREQ_SETTINGS_BUNDLE_FFF; - - for (auto& it : bundle) - { - if (bundle_name == _(it.first)) - return it.second; - } -#if 0 - // if "Quick menu" is selected - SettingsBundle& bundle_quick = printer_technology() == ptSLA ? - m_freq_settings_sla: m_freq_settings_fff; - - for (auto& it : bundle_quick) - { - if ( bundle_name == from_u8((boost::format(_utf8(L("Quick Add Settings (%s)"))) % _(it.first)).str()) ) - return it.second; - } -#endif - - static std::vector empty; - return empty; -} - -static bool improper_category(const std::string& category, const int extruders_cnt, const bool is_object_settings = true) -{ - return category.empty() || - (extruders_cnt == 1 && (category == "Extruders" || category == "Wipe options" )) || - (!is_object_settings && category == "Support material"); -} - -static bool is_object_item(ItemType item_type) -{ - return item_type & itObject || item_type & itInstance || - // multi-selection in ObjectList, but full_object in Selection - (item_type == itUndef && scene_selection().is_single_full_object()); -} - -void ObjectList::get_options_menu(settings_menu_hierarchy& settings_menu, const bool is_part) -{ - auto options = get_options(is_part); - - const int extruders_cnt = extruders_count(); - - DynamicPrintConfig config; - for (auto& option : options) - { - auto const opt = config.def()->get(option); - auto category = opt->category; - if (improper_category(category, extruders_cnt, !is_part)) - continue; - - const std::string& label = !opt->full_label.empty() ? opt->full_label : opt->label; - std::pair option_label(option, label); - std::vector< std::pair > new_category; - auto& cat_opt_label = settings_menu.find(category) == settings_menu.end() ? new_category : settings_menu.at(category); - cat_opt_label.push_back(option_label); - if (cat_opt_label.size() == 1) - settings_menu[category] = cat_opt_label; - } -} - -void ObjectList::get_settings_choice(const wxString& category_name) -{ - wxArrayString names; - wxArrayInt selections; - - /* If we try to add settings for object/part from 3Dscene, - * for the second try there is selected ItemSettings in ObjectList. - * So, check if selected item isn't SettingsItem. And get a SettingsItem's parent item, if yes - */ - const wxDataViewItem selected_item = GetSelection(); - wxDataViewItem item = m_objects_model->GetItemType(selected_item) & itSettings ? m_objects_model->GetParent(selected_item) : selected_item; + if (category_options.empty()) + return; const ItemType item_type = m_objects_model->GetItemType(item); - settings_menu_hierarchy settings_menu; - 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) - { - if (_(cat.first) == category_name) { - int sel = 0; - for (auto& pair : cat.second) { - names.Add(_(pair.second)); - if (find(opt_keys.begin(), opt_keys.end(), pair.first) != opt_keys.end()) - selections.Add(sel); - sel++; - } - settings_list = &cat.second; - break; - } - } - - if (!settings_list) - return; - - if (wxGetSelectedChoices(selections, _(L("Select showing settings")), category_name, names) == -1) - return; - - const int selection_cnt = selections.size(); -#if 0 - if (selection_cnt > 0) - { - // Add selected items to the "Quick menu" - SettingsBundle& freq_settings = printer_technology() == ptSLA ? - m_freq_settings_sla : m_freq_settings_fff; - bool changed_existing = false; - - std::vector tmp_freq_cat = {}; - - for (auto& cat : freq_settings) - { - if (_(cat.first) == category_name) - { - std::vector& freq_settings_category = cat.second; - freq_settings_category.clear(); - freq_settings_category.reserve(selection_cnt); - for (auto sel : selections) - freq_settings_category.push_back((*settings_list)[sel].first); - - changed_existing = true; - break; - } - } - - if (!changed_existing) - { - // Create new "Quick menu" item - for (auto& cat : settings_menu) - { - if (_(cat.first) == category_name) - { - freq_settings[cat.first] = std::vector {}; - - std::vector& freq_settings_category = freq_settings.find(cat.first)->second; - freq_settings_category.reserve(selection_cnt); - for (auto sel : selections) - freq_settings_category.push_back((*settings_list)[sel].first); - break; - } - } - } - } -#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")); + 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) - selected_options.push_back((*settings_list)[sel].first); - const DynamicPrintConfig& from_config = printer_technology() == ptFFF ? wxGetApp().preset_bundle->prints.get_edited_preset().config : wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; - for (auto& setting : (*settings_list)) - { - auto& opt_key = setting.first; - if (find(opt_keys.begin(), opt_keys.end(), opt_key) != opt_keys.end() && - find(selected_options.begin(), selected_options.end(), opt_key) == selected_options.end()) + for (auto& opt : category_options) { + auto& opt_key = opt.first; + if (find(opt_keys.begin(), opt_keys.end(), opt_key) != opt_keys.end() && !opt.second) m_config->erase(opt_key); - if (find(opt_keys.begin(), opt_keys.end(), opt_key) == opt_keys.end() && - find(selected_options.begin(), selected_options.end(), opt_key) != selected_options.end()) { + if (find(opt_keys.begin(), opt_keys.end(), opt_key) == opt_keys.end() && opt.second) { const ConfigOption* option = from_config.option(opt_key); if (!option) { // if current option doesn't exist in prints.get_edited_preset(), @@ -1528,48 +1210,25 @@ void ObjectList::get_settings_choice(const wxString& category_name) } } - // 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->get())); } -void ObjectList::get_freq_settings_choice(const wxString& bundle_name) +void ObjectList::add_category_to_settings_from_frequent(const std::vector& options, wxDataViewItem item) { - std::vector options = get_options_for_bundle(bundle_name); - const Selection& selection = scene_selection(); - const wxDataViewItem sel_item = // when all instances in object are selected - GetSelectedItemsCount() > 1 && selection.is_single_full_object() ? - m_objects_model->GetItemById(selection.get_object_idx()) : - GetSelection(); - - /* If we try to add settings for object/part from 3Dscene, - * for the second try there is selected ItemSettings in ObjectList. - * So, check if selected item isn't SettingsItem. And get a SettingsItem's parent item, if yes - */ - wxDataViewItem item = m_objects_model->GetItemType(sel_item) & itSettings ? m_objects_model->GetParent(sel_item) : sel_item; const 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 Height range")) : - item_type & itVolume ? _(L("Add Settings Bundle for Sub-object")) : - _(L("Add Settings Bundle for Object")); + const wxString snapshot_text = item_type & itLayer ? _L("Add Settings Bundle for Height range") : + 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; @@ -1604,520 +1263,12 @@ void ObjectList::show_settings(const wxDataViewItem settings_item) update_selections_on_canvas(); } -wxMenu* ObjectList::append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type) { - auto sub_menu = new wxMenu; - - if (wxGetApp().get_mode() == comExpert && type != ModelVolumeType::INVALID) { - append_menu_item(sub_menu, wxID_ANY, _(L("Load")) + " " + dots, "", - [this, type](wxCommandEvent&) { load_subobject(type); }, "", menu); - sub_menu->AppendSeparator(); - } - - for (auto& item : { L("Box"), L("Cylinder"), L("Sphere"), L("Slab") }) - { - if (type == ModelVolumeType::INVALID && strncmp(item, "Slab", 4) == 0) - continue; - append_menu_item(sub_menu, wxID_ANY, _(item), "", - [this, type, item](wxCommandEvent&) { load_generic_subobject(item, type); }, "", menu); - } - - return sub_menu; -} - -void ObjectList::append_menu_items_add_volume(wxMenu* menu) -{ - // Update "add" items(delete old & create new) settings popupmenu - for (auto& item : ADD_VOLUME_MENU_ITEMS){ - const auto settings_id = menu->FindItem(_(item.first)); - if (settings_id != wxNOT_FOUND) - menu->Destroy(settings_id); - } - - const ConfigOptionMode mode = wxGetApp().get_mode(); - - wxWindow* parent = wxGetApp().plater(); - - if (mode == comAdvanced) { - append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].first), "", - [this](wxCommandEvent&) { load_subobject(ModelVolumeType::MODEL_PART); }, - ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].second, nullptr, - [this]() { return is_instance_or_object_selected(); }, parent); - } - if (mode == comSimple) { - append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)].first), "", - [this](wxCommandEvent&) { load_generic_subobject(L("Box"), ModelVolumeType::SUPPORT_ENFORCER); }, - ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)].second, nullptr, - [this]() { return is_instance_or_object_selected(); }, parent); - append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER)].first), "", - [this](wxCommandEvent&) { load_generic_subobject(L("Box"), ModelVolumeType::SUPPORT_BLOCKER); }, - ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER)].second, nullptr, - [this]() { return is_instance_or_object_selected(); }, parent); - - return; - } - - for (size_t type = (mode == comExpert ? 0 : 1) ; type < ADD_VOLUME_MENU_ITEMS.size(); type++) - { - auto& item = ADD_VOLUME_MENU_ITEMS[type]; - - wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType(type)); - append_submenu(menu, sub_menu, wxID_ANY, _(item.first), "", item.second, - [this]() { return is_instance_or_object_selected(); }, parent); - } -} - -wxMenuItem* ObjectList::append_menu_item_split(wxMenu* menu) -{ - return append_menu_item(menu, wxID_ANY, _(L("Split to parts")), "", - [this](wxCommandEvent&) { split(); }, "split_parts_SMALL", menu, - [this]() { return is_splittable(); }, wxGetApp().plater()); -} - bool ObjectList::is_instance_or_object_selected() { const Selection& selection = scene_selection(); return selection.is_single_full_instance() || selection.is_single_full_object(); } -wxMenuItem* ObjectList::append_menu_item_layers_editing(wxMenu* menu, wxWindow* parent) -{ - return append_menu_item(menu, wxID_ANY, _(L("Height range Modifier")), "", - [this](wxCommandEvent&) { layers_editing(); }, "edit_layers_all", menu, - [this]() { return is_instance_or_object_selected(); }, parent); -} - -wxMenuItem* ObjectList::append_menu_item_settings(wxMenu* menu_) -{ - MenuWithSeparators* menu = dynamic_cast(menu_); - - const wxString menu_name = _(L("Add settings")); - // Delete old items from settings popupmenu - auto settings_id = menu->FindItem(menu_name); - if (settings_id != wxNOT_FOUND) - menu->Destroy(settings_id); - - for (auto& it : FREQ_SETTINGS_BUNDLE_FFF) - { - settings_id = menu->FindItem(_(it.first)); - if (settings_id != wxNOT_FOUND) - menu->Destroy(settings_id); - } - for (auto& it : FREQ_SETTINGS_BUNDLE_SLA) - { - settings_id = menu->FindItem(_(it.first)); - if (settings_id != wxNOT_FOUND) - menu->Destroy(settings_id); - } -#if 0 - for (auto& it : m_freq_settings_fff) - { - settings_id = menu->FindItem(from_u8((boost::format(_utf8(L("Quick Add Settings (%s)"))) % _(it.first)).str())); - if (settings_id != wxNOT_FOUND) - menu->Destroy(settings_id); - } - for (auto& it : m_freq_settings_sla) - { - settings_id = menu->FindItem(from_u8((boost::format(_utf8(L("Quick Add Settings (%s)"))) % _(it.first)).str())); - if (settings_id != wxNOT_FOUND) - menu->Destroy(settings_id); - } -#endif - menu->DestroySeparators(); // delete old separators - - // If there are selected more then one instance but not all of them - // don't add settings menu items - const Selection& selection = scene_selection(); - if ((selection.is_multiple_full_instance() && !selection.is_single_full_object()) || - selection.is_multiple_volume() || selection.is_mixed() ) // more than one volume(part) is selected on the scene - return nullptr; - - const auto sel_vol = get_selected_model_volume(); - if (sel_vol && sel_vol->type() >= ModelVolumeType::SUPPORT_ENFORCER) - return nullptr; - - const ConfigOptionMode mode = wxGetApp().get_mode(); - if (mode == comSimple) - return nullptr; - - // Create new items for settings popupmenu - - if (printer_technology() == ptFFF || - (menu->GetMenuItems().size() > 0 && !menu->GetMenuItems().back()->IsSeparator())) - menu->SetFirstSeparator(); - - // Add frequently settings - const ItemType item_type = m_objects_model->GetItemType(GetSelection()); - if (item_type == itUndef && !selection.is_single_full_object()) - return nullptr; - const bool is_object_settings = is_object_item(item_type); - create_freq_settings_popupmenu(menu, is_object_settings); - - if (mode == comAdvanced) - return nullptr; - - menu->SetSecondSeparator(); - - // Add full settings list - auto menu_item = new wxMenuItem(menu, wxID_ANY, menu_name); - menu_item->SetBitmap(m_bmp_cog.bmp()); - - menu_item->SetSubMenu(create_settings_popupmenu(menu)); - - return menu->Append(menu_item); -} - -wxMenuItem* ObjectList::append_menu_item_change_type(wxMenu* menu, wxWindow* parent/* = nullptr*/) -{ - return append_menu_item(menu, wxID_ANY, _(L("Change type")), "", - [this](wxCommandEvent&) { change_part_type(); }, "", menu, - [this]() { - wxDataViewItem item = GetSelection(); - return item.IsOk() || m_objects_model->GetItemType(item) == itVolume; - }, parent); -} - -wxMenuItem* ObjectList::append_menu_item_instance_to_object(wxMenu* menu, wxWindow* parent) -{ - wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _(L("Set as a Separated Object")), "", - [this](wxCommandEvent&) { split_instances(); }, "", menu); - - /* New behavior logic: - * 1. Split Object to several separated object, if ALL instances are selected - * 2. Separate selected instances from the initial object to the separated object, - * if some (not all) instances are selected - */ - parent->Bind(wxEVT_UPDATE_UI, [](wxUpdateUIEvent& evt) - { - const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); - evt.SetText(selection.is_single_full_object() ? - _(L("Set as a Separated Objects")) : _(L("Set as a Separated Object"))); - - evt.Enable(wxGetApp().plater()->can_set_instance_to_object()); - }, menu_item->GetId()); - - return menu_item; -} - -wxMenuItem* ObjectList::append_menu_item_printable(wxMenu* menu, wxWindow* /*parent*/) -{ - return append_menu_check_item(menu, wxID_ANY, _(L("Printable")), "", [this](wxCommandEvent&) { - const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); - wxDataViewItem item; - if (GetSelectedItemsCount() > 1 && selection.is_single_full_object()) - item = m_objects_model->GetItemById(selection.get_object_idx()); - else - item = GetSelection(); - - if (item) - toggle_printable_state(item); - }, menu); -} - -void ObjectList::append_menu_items_osx(wxMenu* menu) -{ - append_menu_item(menu, wxID_ANY, _(L("Rename")), "", - [this](wxCommandEvent&) { rename_item(); }, "", menu); - - menu->AppendSeparator(); -} - -wxMenuItem* ObjectList::append_menu_item_fix_through_netfabb(wxMenu* menu) -{ - if (!is_windows10()) - return nullptr; - Plater* plater = wxGetApp().plater(); - wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _(L("Fix through the Netfabb")), "", - [this](wxCommandEvent&) { fix_through_netfabb(); }, "", menu, - [plater]() {return plater->can_fix_through_netfabb(); }, plater); - menu->AppendSeparator(); - - return menu_item; -} - -void ObjectList::append_menu_item_export_stl(wxMenu* menu) const -{ - append_menu_item(menu, wxID_ANY, _(L("Export as STL")) + dots, "", - [](wxCommandEvent&) { wxGetApp().plater()->export_stl(false, true); }, "", menu); - menu->AppendSeparator(); -} - -void ObjectList::append_menu_item_reload_from_disk(wxMenu* menu) const -{ - append_menu_item(menu, wxID_ANY, _(L("Reload from disk")), _(L("Reload the selected volumes from disk")), - [](wxCommandEvent&) { wxGetApp().plater()->reload_from_disk(); }, "", menu, - []() { return wxGetApp().plater()->can_reload_from_disk(); }, wxGetApp().plater()); -} - -void ObjectList::append_menu_item_change_extruder(wxMenu* menu) -{ - const std::vector names = {_(L("Change extruder")), _(L("Set extruder for selected items")) }; - // Delete old menu item - for (const wxString& name : names) { - const int item_id = menu->FindItem(name); - if (item_id != wxNOT_FOUND) - menu->Destroy(item_id); - } - - const int extruders_cnt = extruders_count(); - if (extruders_cnt <= 1) - return; - - wxDataViewItemArray sels; - GetSelections(sels); - if (sels.IsEmpty()) - return; - - std::vector icons = get_extruder_color_icons(true); - wxMenu* extruder_selection_menu = new wxMenu(); - const wxString& name = sels.Count()==1 ? names[0] : names[1]; - - int initial_extruder = -1; // negative value for multiple object/part selection - if (sels.Count()==1) { - const ModelConfig &config = get_item_config(sels[0]); - initial_extruder = config.has("extruder") ? config.extruder() : 0; - } - - for (int i = 0; i <= extruders_cnt; i++) - { - bool is_active_extruder = i == initial_extruder; - int icon_idx = i == 0 ? 0 : i - 1; - - const wxString& item_name = (i == 0 ? _(L("Default")) : wxString::Format(_(L("Extruder %d")), i)) + - (is_active_extruder ? " (" + _(L("active")) + ")" : ""); - - append_menu_item(extruder_selection_menu, wxID_ANY, item_name, "", - [this, i](wxCommandEvent&) { set_extruder_for_selected_items(i); }, *icons[icon_idx], menu, - [is_active_extruder]() { return !is_active_extruder; }, GUI::wxGetApp().plater()); - - } - - menu->AppendSubMenu(extruder_selection_menu, name); -} - -void ObjectList::append_menu_item_delete(wxMenu* menu) -{ - append_menu_item(menu, wxID_ANY, _(L("Delete")), "", - [this](wxCommandEvent&) { remove(); }, "", menu); -} - -void ObjectList::append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu) -{ - append_menu_item(menu, wxID_ANY, _(L("Scale to print volume")), _(L("Scale the selected object to fit the print volume")), - [](wxCommandEvent&) { wxGetApp().plater()->scale_selection_to_fit_print_volume(); }, "", menu); -} - -void ObjectList::append_menu_items_convert_unit(wxMenu* menu, int insert_pos/* = 1*/) -{ - std::vector obj_idxs, vol_idxs; - get_selection_indexes(obj_idxs, vol_idxs); - if (obj_idxs.empty() && vol_idxs.empty()) - return; - - auto volume_respects_conversion = [](ModelVolume* volume, ConversionType conver_type) - { - return (conver_type == ConversionType::CONV_FROM_INCH && volume->source.is_converted_from_inches) || - (conver_type == ConversionType::CONV_TO_INCH && !volume->source.is_converted_from_inches) || - (conver_type == ConversionType::CONV_FROM_METER && volume->source.is_converted_from_meters) || - (conver_type == ConversionType::CONV_TO_METER && !volume->source.is_converted_from_meters); - }; - - auto can_append = [this, obj_idxs, vol_idxs, volume_respects_conversion](ConversionType conver_type) - { - ModelObjectPtrs objects; - for (int obj_idx : obj_idxs) { - ModelObject* object = (*m_objects)[obj_idx]; - if (vol_idxs.empty()) { - for (ModelVolume* volume : object->volumes) - if (volume_respects_conversion(volume, conver_type)) - return false; - } - else { - for (int vol_idx : vol_idxs) - if (volume_respects_conversion(object->volumes[vol_idx], conver_type)) - return false; - } - } - return true; - }; - - std::vector> items = { - {ConversionType::CONV_FROM_INCH , _L("Convert from imperial units") }, - {ConversionType::CONV_TO_INCH , _L("Revert conversion from imperial units") }, - {ConversionType::CONV_FROM_METER, _L("Convert from meters") }, - {ConversionType::CONV_TO_METER , _L("Revert conversion from meters") } }; - - for (auto item : items) { - int menu_id = menu->FindItem(item.second); - if (can_append(item.first)) { - // Add menu item if it doesn't exist - if (menu_id == wxNOT_FOUND) - append_menu_item(menu, wxID_ANY, item.second, item.second, - [item](wxCommandEvent&) { wxGetApp().plater()->convert_unit(item.first); }, "", menu, - []() {return true; }, nullptr, insert_pos); - } - else if (menu_id != wxNOT_FOUND) { - // Delete menu item - menu->Destroy(menu_id); - } - } -} - -void ObjectList::append_menu_item_merge_to_multipart_object(wxMenu* menu) -{ - menu->AppendSeparator(); - append_menu_item(menu, wxID_ANY, _L("Merge"), _L("Merge objects to the one multipart object"), - [this](wxCommandEvent&) { merge(true); }, "", menu, - [this]() { return this->can_merge_to_multipart_object(); }, wxGetApp().plater()); -} - -void ObjectList::append_menu_item_merge_to_single_object(wxMenu* menu) -{ - menu->AppendSeparator(); - append_menu_item(menu, wxID_ANY, _L("Merge"), _L("Merge objects to the one single object"), - [this](wxCommandEvent&) { merge(false); }, "", menu, - [this]() { return this->can_merge_to_single_object(); }, wxGetApp().plater()); -} - -void ObjectList::create_object_popupmenu(wxMenu *menu) -{ -#ifdef __WXOSX__ - append_menu_items_osx(menu); -#endif // __WXOSX__ - - append_menu_item_reload_from_disk(menu); - append_menu_item_export_stl(menu); - append_menu_item_fix_through_netfabb(menu); - append_menu_item_scale_selection_to_fit_print_volume(menu); - - // Split object to parts - append_menu_item_split(menu); -// menu->AppendSeparator(); - - // Merge multipart object to the single object -// append_menu_item_merge_to_single_object(menu); - menu->AppendSeparator(); - - // Layers Editing for object - append_menu_item_layers_editing(menu, wxGetApp().plater()); - menu->AppendSeparator(); - - // rest of a object_menu will be added later in: - // - append_menu_items_add_volume() -> for "Add (volumes)" - // - append_menu_item_settings() -> for "Add (settings)" -} - -void ObjectList::create_sla_object_popupmenu(wxMenu *menu) -{ -#ifdef __WXOSX__ - append_menu_items_osx(menu); -#endif // __WXOSX__ - - append_menu_item_reload_from_disk(menu); - append_menu_item_export_stl(menu); - append_menu_item_fix_through_netfabb(menu); - // rest of a object_sla_menu will be added later in: - // - append_menu_item_settings() -> for "Add (settings)" -} - -void ObjectList::create_part_popupmenu(wxMenu *menu) -{ -#ifdef __WXOSX__ - append_menu_items_osx(menu); -#endif // __WXOSX__ - - append_menu_item_reload_from_disk(menu); - append_menu_item_export_stl(menu); - append_menu_item_fix_through_netfabb(menu); - - append_menu_item_split(menu); - - // Append change part type - menu->AppendSeparator(); - append_menu_item_change_type(menu); - - // rest of a object_sla_menu will be added later in: - // - append_menu_item_settings() -> for "Add (settings)" -} - -void ObjectList::create_instance_popupmenu(wxMenu*menu) -{ - m_menu_item_split_instances = append_menu_item_instance_to_object(menu, wxGetApp().plater()); -} - -void ObjectList::create_default_popupmenu(wxMenu*menu) -{ - wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType::INVALID); - append_submenu(menu, sub_menu, wxID_ANY, _(L("Add Shape")), "", "add_part", - [](){return true;}, this); -} - -wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu) -{ - wxMenu *menu = new wxMenu; - - settings_menu_hierarchy settings_menu; - - /* If we try to add settings for object/part from 3Dscene, - * for the second try there is selected ItemSettings in ObjectList. - * So, check if selected item isn't SettingsItem. And get a SettingsItem's parent item, if yes - */ - const wxDataViewItem selected_item = GetSelection(); - wxDataViewItem item = m_objects_model->GetItemType(selected_item) & itSettings ? m_objects_model->GetParent(selected_item) : selected_item; - - get_options_menu(settings_menu, !is_object_item(m_objects_model->GetItemType(item))); - - for (auto cat : settings_menu) { - append_menu_item(menu, wxID_ANY, _(cat.first), "", - [this, menu](wxCommandEvent& event) { get_settings_choice(menu->GetLabel(event.GetId())); }, - CATEGORY_ICON.find(cat.first) == CATEGORY_ICON.end() ? wxNullBitmap : CATEGORY_ICON.at(cat.first), parent_menu, - []() { return true; }, wxGetApp().plater()); - } - - return menu; -} - -void ObjectList::create_freq_settings_popupmenu(wxMenu *menu, const bool is_object_settings/* = true*/) -{ - // Add default settings bundles - 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 (improper_category(it.first, extruders_cnt, is_object_settings)) - continue; - - append_menu_item(menu, wxID_ANY, _(it.first), "", - [this, menu](wxCommandEvent& event) { get_freq_settings_choice(menu->GetLabel(event.GetId())); }, - CATEGORY_ICON.find(it.first) == CATEGORY_ICON.end() ? wxNullBitmap : CATEGORY_ICON.at(it.first), menu, - []() { return true; }, wxGetApp().plater()); - } -#if 0 - // Add "Quick" settings bundles - const SettingsBundle& bundle_quick = printer_technology() == ptFFF ? - m_freq_settings_fff : m_freq_settings_sla; - - for (auto& it : bundle_quick) { - if (improper_category(it.first, extruders_cnt)) - continue; - - append_menu_item(menu, wxID_ANY, from_u8((boost::format(_utf8(L("Quick Add Settings (%s)"))) % _(it.first)).str()), "", - [menu, this](wxCommandEvent& event) { get_freq_settings_choice(menu->GetLabel(event.GetId())); }, - CATEGORY_ICON.find(it.first) == CATEGORY_ICON.end() ? wxNullBitmap : CATEGORY_ICON.at(it.first), menu, - [this]() { return true; }, wxGetApp().plater()); - } -#endif -} - -void ObjectList::update_opt_keys(t_config_option_keys& opt_keys, const bool is_object) -{ - 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); -} - void ObjectList::load_subobject(ModelVolumeType type) { wxDataViewItem item = GetSelection(); @@ -2132,12 +1283,11 @@ void ObjectList::load_subobject(ModelVolumeType type) if (m_objects_model->GetItemType(item)&itInstance) item = m_objects_model->GetItemById(obj_idx); - take_snapshot(_(L("Load Part"))); + 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 @@ -2150,9 +1300,7 @@ void ObjectList::load_subobject(ModelVolumeType type) if (sel_item) select_item(sel_item); -//#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME selection_changed(); -//#endif //no __WXOSX__ //__WXMSW__ } void ObjectList::load_part( ModelObject* model_object, @@ -2163,23 +1311,30 @@ void ObjectList::load_part( ModelObject* model_object, wxArrayString input_files; wxGetApp().import_model(parent, input_files); + + wxProgressDialog dlg(_L("Loading") + dots, "", 100, wxGetApp().plater(), wxPD_AUTO_HIDE); + wxBusyCursor busy; + for (size_t i = 0; i < input_files.size(); ++i) { std::string input_file = input_files.Item(i).ToUTF8().data(); + dlg.Update(static_cast(100.0f * static_cast(i) / static_cast(input_files.size())), + _L("Loading file") + ": " + from_path(boost::filesystem::path(input_file).filename())); + dlg.Fit(); + Model model; try { model = Model::read_from_file(input_file); } catch (std::exception &e) { - auto msg = _(L("Error!")) + " " + input_file + " : " + e.what() + "."; + auto msg = _L("Error!") + " " + input_file + " : " + e.what() + "."; show_error(parent, msg); exit(1); } for (auto object : model.objects) { Vec3d delta = Vec3d::Zero(); - if (model_object->origin_translation != Vec3d::Zero()) - { + if (model_object->origin_translation != Vec3d::Zero()) { object->center_around_origin(); delta = model_object->origin_translation - object->origin_translation; } @@ -2288,9 +1443,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode const auto object_item = m_objects_model->GetTopParent(GetSelection()); select_item([this, object_item, name, type, new_volume]() { return m_objects_model->AddVolumeChild(object_item, name, type, new_volume->get_mesh_errors_count() > 0); }); -//#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME selection_changed(); -//#endif //no __WXOSX__ //__WXMSW__ } void ObjectList::load_shape_object(const std::string& type_name) @@ -2372,14 +1525,20 @@ void ObjectList::del_subobject_item(wxDataViewItem& item) if (type == itUndef) return; + wxDataViewItem parent = m_objects_model->GetParent(item); + if (type & itSettings) - del_settings_from_config(m_objects_model->GetParent(item)); + del_settings_from_config(parent); 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 (type & itInfo && obj_idx != -1) { + Unselect(item); + Select(parent); + } else if (idx == -1) return; else if (!del_subobject_from_object(obj_idx, idx, type)) @@ -2387,9 +1546,10 @@ void ObjectList::del_subobject_item(wxDataViewItem& item) // If last volume item with warning was deleted, unmark object item if (type & itVolume && (*m_objects)[obj_idx]->get_mesh_errors_count() == 0) - m_objects_model->DeleteWarningIcon(m_objects_model->GetParent(item)); + m_objects_model->DeleteWarningIcon(parent); m_objects_model->Delete(item); + update_info_items(obj_idx); } void ObjectList::del_settings_from_config(const wxDataViewItem& parent_item) @@ -2494,6 +1654,8 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con wxString extruder = object->config.has("extruder") ? wxString::Format("%d", object->config.extruder()) : _L("default"); m_objects_model->SetExtruder(extruder, obj_item); } + // add settings to the object, if it has them + add_settings_item(obj_item, &object->config.get()); } } } @@ -2854,11 +2016,27 @@ bool ObjectList::get_volume_by_item(const wxDataViewItem& item, ModelVolume*& vo return true; } -bool ObjectList::is_splittable() +bool ObjectList::is_splittable(bool to_objects) { const wxDataViewItem item = GetSelection(); if (!item) return false; + if (to_objects) + { + ItemType type = m_objects_model->GetItemType(item); + if (type == itVolume) + return false; + if (type == itObject || m_objects_model->GetItemType(m_objects_model->GetParent(item)) == itObject) { + auto obj_idx = get_selected_obj_idx(); + if (obj_idx < 0) + return false; + if ((*m_objects)[obj_idx]->volumes.size() > 1) + return true; + return (*m_objects)[obj_idx]->volumes[0]->is_splittable(); + } + return false; + } + ModelVolume* volume; if (!get_volume_by_item(item, volume) || !volume) return false; @@ -2947,20 +2125,32 @@ void ObjectList::part_selection_changed() { if (item) { - if (m_objects_model->GetParent(item) == wxDataViewItem(nullptr)) { - obj_idx = m_objects_model->GetIdByItem(item); + const ItemType type = m_objects_model->GetItemType(item); + const wxDataViewItem parent = m_objects_model->GetParent(item); + const ItemType parent_type = m_objects_model->GetItemType(parent); + obj_idx = m_objects_model->GetObjectIdByItem(item); + + if (parent == wxDataViewItem(nullptr) + || type == itInfo) { og_name = _(L("Object manipulation")); m_config = &(*m_objects)[obj_idx]->config; update_and_show_manipulations = true; + + if (type == itInfo) { + InfoItemType info_type = m_objects_model->GetInfoItemType(item); + if (info_type != InfoItemType::VariableLayerHeight) { + GLGizmosManager::EType gizmo_type = + info_type == InfoItemType::CustomSupports ? GLGizmosManager::EType::FdmSupports + : GLGizmosManager::EType::Seam; + GLGizmosManager& gizmos_mgr = wxGetApp().plater()->canvas3D()->get_gizmos_manager(); + if (gizmos_mgr.get_current_type() != gizmo_type) + gizmos_mgr.open_gizmo(gizmo_type); + } else + wxGetApp().plater()->toggle_layers_editing(true); + } } else { - 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; @@ -3035,37 +2225,6 @@ void ObjectList::part_selection_changed() panel.Thaw(); } -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, is_object_settings)) - 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) { @@ -3075,7 +2234,13 @@ wxDataViewItem ObjectList::add_settings_item(wxDataViewItem parent_item, const D 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 (!is_object_settings) { + ModelVolumeType volume_type = m_objects_model->GetVolumeType(parent_item); + if (volume_type == ModelVolumeType::SUPPORT_BLOCKER || volume_type == ModelVolumeType::SUPPORT_ENFORCER) + return ret; + } + + SettingsFactory::Bundle cat_options = SettingsFactory::get_bundle(config, is_object_settings); if (cat_options.empty()) return ret; @@ -3097,6 +2262,52 @@ wxDataViewItem ObjectList::add_settings_item(wxDataViewItem parent_item, const D return ret; } + +void ObjectList::update_info_items(size_t obj_idx) +{ + const ModelObject* model_object = (*m_objects)[obj_idx]; + wxDataViewItem item_obj = m_objects_model->GetItemById(obj_idx); + assert(item_obj.IsOk()); + + for (InfoItemType type : {InfoItemType::CustomSupports, + InfoItemType::CustomSeam, + InfoItemType::VariableLayerHeight}) { + wxDataViewItem item = m_objects_model->GetInfoItemByType(item_obj, type); + bool shows = item.IsOk(); + bool should_show = false; + + switch (type) { + case InfoItemType::CustomSupports : + case InfoItemType::CustomSeam : + should_show = printer_technology() == ptFFF + && std::any_of(model_object->volumes.begin(), model_object->volumes.end(), + [type](const ModelVolume* mv) { + return ! (type == InfoItemType::CustomSupports + ? mv->supported_facets.empty() + : mv->seam_facets.empty()); + }); + break; + + case InfoItemType::VariableLayerHeight : + should_show = printer_technology() == ptFFF + && ! model_object->layer_height_profile.empty(); + break; + } + + if (! shows && should_show) { + m_objects_model->AddInfoChild(item_obj, type); + Expand(item_obj); + } + else if (shows && ! should_show) { + Unselect(item); + m_objects_model->Delete(item); + Select(item_obj); + } + } +} + + + void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) { auto model_object = (*m_objects)[obj_idx]; @@ -3105,6 +2316,8 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) model_object->config.has("extruder") ? model_object->config.extruder() : 0, get_mesh_errors_count(obj_idx) > 0); + update_info_items(obj_idx); + // add volumes to the object if (model_object->volumes.size() > 1) { for (const ModelVolume* volume : model_object->volumes) { @@ -3678,13 +2891,12 @@ void ObjectList::update_selections() 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 ) + // We doesn't update selection if itSettings | itLayerRoot | itLayer Item for the current object/part is selected if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) & (itSettings | itLayerRoot | itLayer)) { const auto item = GetSelection(); if (selection.is_single_full_object()) { - if (m_objects_model->GetObjectIdByItem(item) == selection.get_object_idx()) + if (m_objects_model->GetItemType(m_objects_model->GetParent(item)) & itObject) return; sels.Add(m_objects_model->GetItemById(selection.get_object_idx())); } @@ -3884,7 +3096,7 @@ void ObjectList::update_selections_on_canvas() if (sel_cnt == 1) { wxDataViewItem item = GetSelection(); - if (m_objects_model->GetItemType(item) & (itSettings | itInstanceRoot | itLayerRoot | itLayer)) + if (m_objects_model->GetItemType(item) & (itSettings | itInstanceRoot | itLayerRoot | itLayer | itInfo)) add_to_selection(m_objects_model->GetParent(item), selection, instance_idx, mode); else add_to_selection(item, selection, instance_idx, mode); @@ -4152,9 +3364,15 @@ void ObjectList::fix_multiselection_conflicts() ModelVolume* ObjectList::get_selected_model_volume() { - auto item = GetSelection(); - if (!item || m_objects_model->GetItemType(item) != itVolume) + wxDataViewItem item = GetSelection(); + if (!item) return nullptr; + if (m_objects_model->GetItemType(item) != itVolume) { + if (m_objects_model->GetItemType(m_objects_model->GetParent(item)) == itVolume) + item = m_objects_model->GetParent(item); + else + return nullptr; + } const auto vol_idx = m_objects_model->GetVolumeIdByItem(item); const auto obj_idx = get_selected_obj_idx(); @@ -4197,8 +3415,10 @@ void ObjectList::change_part_type() take_snapshot(_(L("Change Part Type"))); - const auto item = GetSelection(); volume->set_type(new_type); + wxDataViewItem item = GetSelection(); + if (m_objects_model->GetItemType(item) != itVolume && m_objects_model->GetItemType(m_objects_model->GetParent(item)) == itVolume) + item = m_objects_model->GetParent(item); m_objects_model->SetVolumeType(item, new_type); changed_object(get_selected_obj_idx()); @@ -4289,6 +3509,9 @@ void ObjectList::update_object_list_by_printer_technology() m_objects_model->GetChildren(wxDataViewItem(nullptr), object_items); for (auto& object_item : object_items) { + // update custom supports info + update_info_items(m_objects_model->GetObjectIdByItem(object_item)); + // Update Settings Item for object update_settings_item_and_selection(object_item, sel); @@ -4342,11 +3565,6 @@ void ObjectList::update_object_list_by_printer_technology() m_prevent_canvas_selection_update = false; } -void ObjectList::update_object_menu() -{ - append_menu_items_add_volume(&m_menu_object); -} - void ObjectList::instances_to_separated_object(const int obj_idx, const std::set& inst_idxs) { if ((*m_objects)[obj_idx]->instances.size() == inst_idxs.size()) @@ -4519,41 +3737,17 @@ void ObjectList::msw_rescale() GetColumn(colExtruder)->SetWidth( 8 * em); GetColumn(colEditing )->SetWidth( 3 * em); - // rescale all icons, used by ObjectList - msw_rescale_icons(); - // rescale/update existing items with bitmaps m_objects_model->Rescale(); - // rescale menus - for (MenuWithSeparators* menu : { &m_menu_object, - &m_menu_part, - &m_menu_sla_object, - &m_menu_instance, - &m_menu_layer, - &m_menu_default}) - msw_rescale_menu(menu); - Layout(); } void ObjectList::sys_color_changed() { - // msw_rescale_icons() updates icons, so use it - msw_rescale_icons(); - // update existing items with bitmaps m_objects_model->Rescale(); - // msw_rescale_menu updates just icons, so use it - for (MenuWithSeparators* menu : { &m_menu_object, - &m_menu_part, - &m_menu_sla_object, - &m_menu_instance, - &m_menu_layer, - &m_menu_default}) - msw_rescale_menu(menu); - Layout(); } @@ -4598,33 +3792,6 @@ void ObjectList::OnEditingDone(wxDataViewEvent &event) plater->set_current_canvas_as_dirty(); } -void ObjectList::show_multi_selection_menu() -{ - wxDataViewItemArray sels; - GetSelections(sels); - - for (const wxDataViewItem& item : sels) - if (!(m_objects_model->GetItemType(item) & (itVolume | itObject | itInstance))) - // show this menu only for Objects(Instances mixed with Objects)/Volumes selection - return; - - wxMenu* menu = new wxMenu(); - - if (extruders_count() > 1) - append_menu_item_change_extruder(menu); - - append_menu_item(menu, wxID_ANY, _(L("Reload from disk")), _(L("Reload the selected volumes from disk")), - [](wxCommandEvent&) { wxGetApp().plater()->reload_from_disk(); }, "", menu, []() { - return wxGetApp().plater()->can_reload_from_disk(); - }, wxGetApp().plater()); - - append_menu_items_convert_unit(menu); - if (can_merge_to_multipart_object()) - append_menu_item_merge_to_multipart_object(menu); - - wxGetApp().plater()->PopupMenu(menu); -} - void ObjectList::extruder_selection() { wxArrayString choices; @@ -4740,37 +3907,59 @@ void ObjectList::update_printable_state(int obj_idx, int instance_idx) m_objects_model->SetPrintableState(printable, obj_idx, instance_idx); } -void ObjectList::toggle_printable_state(wxDataViewItem item) +void ObjectList::toggle_printable_state() { - const ItemType type = m_objects_model->GetItemType(item); - if (!(type&(itObject|itInstance/*|itVolume*/))) + wxDataViewItemArray sels; + GetSelections(sels); + + wxDataViewItem frst_item = sels[0]; + + ItemType type = m_objects_model->GetItemType(frst_item); + if (!(type & (itObject | itInstance))) return; - if (type & itObject) + + int obj_idx = m_objects_model->GetObjectIdByItem(frst_item); + int inst_idx = type == itObject ? 0 : m_objects_model->GetInstanceIdByItem(frst_item); + bool printable = !object(obj_idx)->instances[inst_idx]->printable; + + const wxString snapshot_text = sels.Count() > 1 ? (printable ? _L("Set Printable group") : _L("Set Unprintable group")) : + object(obj_idx)->instances.size() == 1 ? from_u8((boost::format("%1% %2%") + % (printable ? _L("Set Printable") : _L("Set Unprintable")) + % object(obj_idx)->name).str()) : + (printable ? _L("Set Printable Instance") : _L("Set Unprintable Instance")); + take_snapshot(snapshot_text); + + std::vector obj_idxs; + for (auto item : sels) { - const int obj_idx = m_objects_model->GetObjectIdByItem(item); - ModelObject* object = (*m_objects)[obj_idx]; + type = m_objects_model->GetItemType(item); + if (!(type & (itObject | itInstance))) + continue; - // get object's printable and change it - const bool printable = !m_objects_model->IsPrintable(item); + obj_idx = m_objects_model->GetObjectIdByItem(item); + ModelObject* obj = object(obj_idx); - const wxString snapshot_text = from_u8((boost::format("%1% %2%") - % (printable ? _(L("Set Printable")) : _(L("Set Unprintable"))) - % object->name).str()); - take_snapshot(snapshot_text); + obj_idxs.emplace_back(static_cast(obj_idx)); - // 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); + // set printable value for selected instance/instances in object + if (type == itInstance) { + inst_idx = m_objects_model->GetInstanceIdByItem(item); + obj->instances[m_objects_model->GetInstanceIdByItem(item)]->printable = printable; + } + else + for (auto inst : obj->instances) + inst->printable = printable; // update printable state in ObjectList - m_objects_model->SetObjectPrintableState(printable ? piPrintable : piUnprintable , item); + m_objects_model->SetObjectPrintableState(printable ? piPrintable : piUnprintable, item); } - else - wxGetApp().plater()->canvas3D()->get_selection().toggle_instance_printable_state(); + + sort(obj_idxs.begin(), obj_idxs.end()); + obj_idxs.erase(unique(obj_idxs.begin(), obj_idxs.end()), obj_idxs.end()); + + // update printable state on canvas + wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_objects(obj_idxs); // update scene wxGetApp().plater()->update(); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 0846def53c..5812e26f75 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -18,7 +18,6 @@ class wxBoxSizer; class wxBitmapComboBox; class wxMenuItem; -class ObjectDataViewModel; class MenuWithSeparators; namespace Slic3r { @@ -31,18 +30,11 @@ class TriangleMesh; 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> 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; +typedef std::vector t_config_option_keys; +typedef std::vector ModelVolumePtrs; +typedef double coordf_t; +typedef std::pair t_layer_height_range; +typedef std::map t_layer_config_ranges; namespace GUI { @@ -106,7 +98,7 @@ public: private: SELECTION_MODE m_selection_mode {smUndef}; - int m_selected_layers_range_idx; + int m_selected_layers_range_idx {-1}; Clipboard m_clipboard; @@ -147,23 +139,6 @@ private: } m_dragged_data; wxBoxSizer *m_sizer {nullptr}; - wxWindow *m_parent {nullptr}; - - ScalableBitmap m_bmp_modifiermesh; - ScalableBitmap m_bmp_solidmesh; - ScalableBitmap m_bmp_support_enforcer; - ScalableBitmap m_bmp_support_blocker; - ScalableBitmap m_bmp_manifold_warning; - ScalableBitmap m_bmp_cog; - - MenuWithSeparators m_menu_object; - MenuWithSeparators m_menu_part; - MenuWithSeparators m_menu_sla_object; - MenuWithSeparators m_menu_instance; - MenuWithSeparators m_menu_layer; - MenuWithSeparators m_menu_default; - wxMenuItem* m_menu_item_settings { nullptr }; - wxMenuItem* m_menu_item_split_instances { nullptr }; ObjectDataViewModel *m_objects_model{ nullptr }; ModelConfig *m_config {nullptr}; @@ -185,7 +160,6 @@ private: // update_settings_items - updating canvas selection is undesirable, // because it would turn off the gizmos (mainly a problem for the SLA gizmo) - 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. @@ -193,8 +167,8 @@ private: #endif /* __MSW__ */ #if 0 - SettingsBundle m_freq_settings_fff; - SettingsBundle m_freq_settings_sla; + SettingsFactory::Bundle m_freq_settings_fff; + SettingsFactory::Bundle m_freq_settings_sla; #endif size_t m_items_count { size_t(-1) }; @@ -212,8 +186,6 @@ public: void set_min_height(); void update_min_height(); - std::map CATEGORY_ICON; - ObjectDataViewModel* GetModel() const { return m_objects_model; } ModelConfig* config() const { return m_config; } std::vector* objects() const { return m_objects; } @@ -221,7 +193,6 @@ public: ModelObject* object(const int obj_idx) const ; void create_objects_ctrl(); - void create_popup_menus(); void update_objects_list_extruder_column(size_t extruders_count); void update_extruder_colors(); // show/hide "Extruder" column for Objects List @@ -232,9 +203,6 @@ public: void update_name_in_model(const wxDataViewItem& item) const; void update_extruder_values_for_items(const size_t max_extruder); - void init_icons(); - void msw_rescale_icons(); - // Get obj_idx and vol_idx values for the selected (by default) or an adjusted item void get_selected_item_indexes(int& obj_idx, int& vol_idx, const wxDataViewItem& item = wxDataViewItem(0)); void get_selection_indexes(std::vector& obj_idxs, std::vector& vol_idxs); @@ -264,39 +232,11 @@ public: void increase_instances(); void decrease_instances(); - void get_settings_choice(const wxString& category_name); - void get_freq_settings_choice(const wxString& bundle_name); + void add_category_to_settings_from_selection(const std::vector< std::pair >& category_options, wxDataViewItem item); + void add_category_to_settings_from_frequent(const std::vector& category_options, wxDataViewItem item); void show_settings(const wxDataViewItem settings_item); bool is_instance_or_object_selected(); - 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, wxWindow* parent); - wxMenuItem* append_menu_item_settings(wxMenu* menu); - wxMenuItem* append_menu_item_change_type(wxMenu* menu, wxWindow* parent = nullptr); - 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; - void append_menu_item_reload_from_disk(wxMenu* menu) const; - void append_menu_item_change_extruder(wxMenu* menu); - void append_menu_item_delete(wxMenu* menu); - void append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu); - void append_menu_items_convert_unit(wxMenu* menu, int insert_pos = 1); // Add "Conver/Revert..." menu items (from/to inches/meters) after "Reload From Disk" - void append_menu_item_merge_to_multipart_object(wxMenu *menu); - void append_menu_item_merge_to_single_object(wxMenu *menu); - void create_object_popupmenu(wxMenu *menu); - void create_sla_object_popupmenu(wxMenu*menu); - void create_part_popupmenu(wxMenu*menu); - void create_instance_popupmenu(wxMenu*menu); - void create_default_popupmenu(wxMenu *menu); - wxMenu* create_settings_popupmenu(wxMenu *parent_menu); - void create_freq_settings_popupmenu(wxMenu *parent_menu, const bool is_object_settings = true); - - 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); @@ -318,7 +258,7 @@ public: DynamicPrintConfig get_default_layer_config(const int obj_idx); bool get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume); - bool is_splittable(); + bool is_splittable(bool to_objects); bool selected_instances_of_same_object(); bool can_split_instances(); bool can_merge_to_multipart_object() const; @@ -328,7 +268,6 @@ public: wxBoxSizer* get_sizer() {return m_sizer;} int get_selected_obj_idx() const; ModelConfig& 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(); @@ -404,11 +343,10 @@ public: void change_part_type(); void last_volume_is_deleted(const int obj_idx); - 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 update_info_items(size_t obj_idx); void instances_to_separated_object(const int obj_idx, const std::set& inst_idx); void instances_to_separated_objects(const int obj_idx); @@ -431,9 +369,9 @@ public: 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); + void toggle_printable_state(); - void show_multi_selection_menu(); + void set_extruder_for_selected_items(const int extruder) const ; private: #ifdef __WXOSX__ @@ -454,11 +392,6 @@ private: #endif /* __WXMSW__ */ void OnEditingDone(wxDataViewEvent &event); void extruder_selection(); - void set_extruder_for_selected_items(const int extruder) const ; - - std::vector get_options(const bool is_part); - const std::vector& get_options_for_bundle(const wxString& bundle_name); - void get_options_menu(settings_menu_hierarchy& settings_menu, const bool is_part); }; diff --git a/src/slic3r/GUI/GUI_ObjectSettings.cpp b/src/slic3r/GUI/GUI_ObjectSettings.cpp index 2501ea4995..e6689b43cd 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.cpp @@ -1,5 +1,6 @@ #include "GUI_ObjectSettings.hpp" #include "GUI_ObjectList.hpp" +#include "GUI_Factories.hpp" #include "OptionsGroup.hpp" #include "GUI_App.hpp" @@ -83,7 +84,7 @@ bool ObjectSettings::update_settings_list() 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->get(), is_object_settings); + SettingsFactory::Bundle cat_options = SettingsFactory::get_bundle(&config->get(), is_object_settings); if (!cat_options.empty()) { diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 76677660da..e67ddb0452 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -1,4 +1,6 @@ +//#include "stdlib.h" #include "libslic3r/libslic3r.h" +#include "libslic3r/Layer.hpp" #include "GUI_Preview.hpp" #include "GUI_App.hpp" #include "GUI.hpp" @@ -24,6 +26,7 @@ // this include must follow the wxWidgets ones or it won't compile on Windows -> see http://trac.wxwidgets.org/ticket/2421 #include "libslic3r/Print.hpp" #include "libslic3r/SLAPrint.hpp" +#include "NotificationManager.hpp" namespace Slic3r { namespace GUI { @@ -211,6 +214,7 @@ bool Preview::init(wxWindow* parent, Model* model) m_choice_view_type->Append(_L("Width")); m_choice_view_type->Append(_L("Speed")); m_choice_view_type->Append(_L("Fan speed")); + m_choice_view_type->Append(_L("Temperature")); m_choice_view_type->Append(_L("Volumetric flow rate")); m_choice_view_type->Append(_L("Tool")); m_choice_view_type->Append(_L("Color Print")); @@ -635,8 +639,70 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee m_layers_slider->SetDrawMode(sla_print_technology, sequential_print); if (sla_print_technology) m_layers_slider->SetLayersTimes(plater->sla_print().print_statistics().layers_times); - else - m_layers_slider->SetLayersTimes(m_gcode_result->time_statistics.modes.front().layers_times); + else { + auto print_mode_stat = m_gcode_result->time_statistics.modes.front(); + m_layers_slider->SetLayersTimes(print_mode_stat.layers_times, print_mode_stat.time); + } + + // Suggest the auto color change, if model looks like sign + if (m_layers_slider->IsNewPrint()) + { + const Print& print = wxGetApp().plater()->fff_print(); + double delta_area = scale_(scale_(25)); // equal to 25 mm2 + + //bool is_possible_auto_color_change = false; + for (auto object : print.objects()) { + double object_x = double(object->size().x()); + double object_y = double(object->size().y()); + + // if it's sign, than object have not to be a too height + double height = object->height(); + coord_t longer_side = std::max(object_x, object_y); + if (height / longer_side > 0.3) + continue; + + const ExPolygons& bottom = object->get_layer(0)->lslices; + double bottom_area = area(bottom); + + // at least 30% of object's height have to be a solid + size_t i; + for (i = 1; i < size_t(0.3 * object->layers().size()); i++) { + double cur_area = area(object->get_layer(i)->lslices); + if (cur_area != bottom_area && fabs(cur_area - bottom_area) > scale_(scale_(1))) + break; + } + if (i < size_t(0.3 * object->layers().size())) + continue; + + // bottom layer have to be a biggest, so control relation between bottom layer and object size + double prev_area = area(object->get_layer(i)->lslices); + for ( i++; i < object->layers().size(); i++) { + double cur_area = area(object->get_layer(i)->lslices); + if (cur_area > prev_area && prev_area - cur_area > scale_(scale_(1))) + break; + prev_area = cur_area; + } + if (i < object->layers().size()) + continue; + + double top_area = area(object->get_layer(int(object->layers().size()) - 1)->lslices); + if( bottom_area - top_area > delta_area) { + NotificationManager* notif_mngr = wxGetApp().plater()->get_notification_manager(); + notif_mngr->push_notification( + NotificationType::SignDetected, NotificationManager::NotificationLevel::RegularNotification, + _u8L("NOTE:") + "\n" + _u8L("Sliced object looks like the sign") + "\n", + _u8L("Apply auto color change to print"), + [this](wxEvtHandler*) { + m_layers_slider->auto_color_change(); + return true; + }); + + notif_mngr->apply_in_preview(); + + break; + } + } + } m_layers_slider_sizer->Show((size_t)0); Layout(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index f3f87cc33e..d870687129 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -7,6 +7,7 @@ #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/ImGuiWrapper.hpp" #include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/GUI_ObjectList.hpp" #include @@ -316,8 +317,12 @@ void GLGizmoFdmSupports::update_model_object() const updated |= mv->supported_facets.set(*m_triangle_selectors[idx].get()); } - if (updated) + if (updated) { + const ModelObjectPtrs& mos = wxGetApp().model().objects; + wxGetApp().obj_list()->update_info_items(std::find(mos.begin(), mos.end(), mo) - mos.begin()); + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 3d0d9c79ad..9db50e6f11 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -122,9 +122,10 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) cons glsafe(::glTranslated(0.0, 0.0, m_c->selection_info()->get_sla_shift())); glsafe(::glMultMatrixd(instance_matrix.data())); - float render_color[4]; + std::array render_color; const sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; size_t cache_size = drain_holes.size(); + for (size_t i = 0; i < cache_size; ++i) { const sla::DrainHole& drain_hole = drain_holes[i]; @@ -136,26 +137,27 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) cons // First decide about the color of the point. if (picking) { 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]; + + render_color = color; } else { - render_color[3] = 1.f; if (size_t(m_hover_id) == i) { - render_color[0] = 0.f; - render_color[1] = 1.0f; - render_color[2] = 1.0f; + render_color = {0.f, 1.f, 1.f, 1.f}; + } else if (m_c->hollowed_mesh() && + i < m_c->hollowed_mesh()->get_drainholes().size() && + m_c->hollowed_mesh()->get_drainholes()[i].failed) { + render_color = {1.f, 0.f, 0.f, .5f}; } else { // neigher hover nor picking + render_color[0] = point_selected ? 1.0f : 0.7f; render_color[1] = point_selected ? 0.3f : 0.7f; render_color[2] = point_selected ? 0.3f : 0.7f; render_color[3] = 0.5f; } } - glsafe(::glColor4fv(render_color)); + + glsafe(::glColor4fv(render_color.data())); 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)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index c5060a88ed..6b6905e4d2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -7,9 +7,10 @@ #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/Plater.hpp" #include "libslic3r/PresetBundle.hpp" -#include "libslic3r/SLA/Rotfinder.hpp" +#include "slic3r/GUI/Jobs/RotoptimizeJob.hpp" namespace Slic3r { namespace GUI { @@ -204,6 +205,30 @@ void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limi { if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) return; + + RotoptimzeWindow popup{m_imgui, m_rotoptimizewin_state, {x, y, bottom_limit}}; +} + +void GLGizmoRotate3D::load_rotoptimize_state() +{ + std::string accuracy_str = + wxGetApp().app_config->get("sla_auto_rotate", "accuracy"); + + std::string method_str = + wxGetApp().app_config->get("sla_auto_rotate", "method_id"); + + if (!accuracy_str.empty()) { + float accuracy = std::stof(accuracy_str); + accuracy = std::max(0.f, std::min(accuracy, 1.f)); + + m_rotoptimizewin_state.accuracy = accuracy; + } + + if (!method_str.empty()) { + int method_id = std::stoi(method_str); + if (method_id < int(RotoptimizeJob::get_methods_count())) + m_rotoptimizewin_state.method_id = method_id; + } } void GLGizmoRotate::render_circle() const @@ -436,6 +461,8 @@ GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_fil { m_gizmos[i].set_group_id(i); } + + load_rotoptimize_state(); } bool GLGizmoRotate3D::on_init() @@ -492,5 +519,51 @@ void GLGizmoRotate3D::on_render() const m_gizmos[Z].render(); } +const char * GLGizmoRotate3D::RotoptimzeWindow::options[RotoptimizeJob::get_methods_count()]; +bool GLGizmoRotate3D::RotoptimzeWindow::options_valid = false; + +GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui, + State & state, + const Alignment &alignment) + : m_imgui{imgui} +{ + imgui->begin(_L("Optimize orientation"), ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoCollapse); + + // adjust window position to avoid overlap the view toolbar + float win_h = ImGui::GetWindowHeight(); + float x = alignment.x, y = alignment.y; + y = std::min(y, alignment.bottom_limit - win_h); + ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); + + ImGui::PushItemWidth(200.f); + + size_t methods_cnt = RotoptimizeJob::get_methods_count(); + if (!options_valid) { + for (size_t i = 0; i < methods_cnt; ++i) + options[i] = RotoptimizeJob::get_method_names()[i].c_str(); + + options_valid = true; + } + + int citem = state.method_id; + if (ImGui::Combo(_L("Choose goal").c_str(), &citem, options, methods_cnt) ) { + state.method_id = citem; + wxGetApp().app_config->set("sla_auto_rotate", "method_id", std::to_string(state.method_id)); + } + + ImGui::Separator(); + + if ( imgui->button(_L("Optimize")) ) { + wxGetApp().plater()->optimize_rotation(); + } +} + +GLGizmoRotate3D::RotoptimzeWindow::~RotoptimzeWindow() +{ + m_imgui->end(); +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index 126c97b1dd..c18d0eefd9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -2,6 +2,7 @@ #define slic3r_GLGizmoRotate_hpp_ #include "GLGizmoBase.hpp" +#include "../Jobs/RotoptimizeJob.hpp" namespace Slic3r { @@ -136,6 +137,39 @@ protected: } void on_render_input_window(float x, float y, float bottom_limit) override; + +private: + + class RotoptimzeWindow { + ImGuiWrapper *m_imgui = nullptr; + + static const char * options []; + static bool options_valid; + + public: + + struct State { + float accuracy = 1.f; + int method_id = 0; + }; + + struct Alignment { float x, y, bottom_limit; }; + + RotoptimzeWindow(ImGuiWrapper * imgui, + State & state, + const Alignment &bottom_limit); + + ~RotoptimzeWindow(); + + RotoptimzeWindow(const RotoptimzeWindow&) = delete; + RotoptimzeWindow(RotoptimzeWindow &&) = delete; + RotoptimzeWindow& operator=(const RotoptimzeWindow &) = delete; + RotoptimzeWindow& operator=(RotoptimzeWindow &&) = delete; + }; + + RotoptimzeWindow::State m_rotoptimizewin_state = {}; + + void load_rotoptimize_state(); }; } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index 6c3a8ddd3a..9724767550 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -7,7 +7,7 @@ #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/ImGuiWrapper.hpp" #include "slic3r/GUI/Plater.hpp" - +#include "slic3r/GUI/GUI_ObjectList.hpp" #include @@ -222,8 +222,12 @@ void GLGizmoSeam::update_model_object() const updated |= mv->seam_facets.set(*m_triangle_selectors[idx].get()); } - if (updated) + if (updated) { + const ModelObjectPtrs& mos = wxGetApp().model().objects; + wxGetApp().obj_list()->update_info_items(std::find(mos.begin(), mos.end(), mo) - mos.begin()); + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index b5dbab2844..b200623f49 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -16,6 +16,7 @@ #include "slic3r/GUI/GUI_ObjectSettings.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/NotificationManager.hpp" #include "libslic3r/PresetBundle.hpp" #include "libslic3r/SLAPrint.hpp" @@ -1161,6 +1162,7 @@ void GLGizmoSlaSupports::disable_editing_mode() m_c->instances_hider()->show_supports(true); m_parent.set_as_dirty(); } + wxGetApp().plater()->get_notification_manager()->close_notification_of_type(NotificationType::QuitSLAManualMode); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index a34c7562e7..acb85d5399 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -200,15 +200,25 @@ void HollowedMesh::on_update() if (print_object->is_step_done(slaposDrillHoles) && print_object->has_mesh(slaposDrillHoles)) { size_t timestamp = print_object->step_state_with_timestamp(slaposDrillHoles).timestamp; if (timestamp > m_old_hollowing_timestamp) { - const TriangleMesh& backend_mesh = print_object->get_mesh_to_print(); + const TriangleMesh& backend_mesh = print_object->get_mesh_to_slice(); if (! backend_mesh.empty()) { m_hollowed_mesh_transformed.reset(new TriangleMesh(backend_mesh)); Transform3d trafo_inv = canvas->sla_print()->sla_trafo(*mo).inverse(); m_hollowed_mesh_transformed->transform(trafo_inv); + m_drainholes = print_object->model_object()->sla_drain_holes; m_old_hollowing_timestamp = timestamp; + + const TriangleMesh &interior = print_object->hollowed_interior_mesh(); + if (!interior.empty()) { + m_hollowed_interior_transformed = std::make_unique(interior); + m_hollowed_interior_transformed->repaired = false; + m_hollowed_interior_transformed->repair(true); + m_hollowed_interior_transformed->transform(trafo_inv); + } } - else + else { m_hollowed_mesh_transformed.reset(nullptr); + } } } else @@ -230,6 +240,10 @@ const TriangleMesh* HollowedMesh::get_hollowed_mesh() const return m_hollowed_mesh_transformed.get(); } +const TriangleMesh* HollowedMesh::get_hollowed_interior() const +{ + return m_hollowed_interior_transformed.get(); +} @@ -306,6 +320,10 @@ void ObjectClipper::on_update() m_clippers.back()->set_mesh(*mesh); } m_old_meshes = meshes; + + if (has_hollowed) + m_clippers.front()->set_negative_mesh(*get_pool()->hollowed_mesh()->get_hollowed_interior()); + m_active_inst_bb_radius = mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius(); //if (has_hollowed && m_clp_ratio != 0.) diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp index 61c2732976..28be1b97f2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -5,6 +5,7 @@ #include #include "slic3r/GUI/MeshUtils.hpp" +#include "libslic3r/SLA/Hollowing.hpp" namespace Slic3r { @@ -198,7 +199,10 @@ public: CommonGizmosDataID get_dependencies() const override { return CommonGizmosDataID::SelectionInfo; } #endif // NDEBUG + const sla::DrainHoles &get_drainholes() const { return m_drainholes; } + const TriangleMesh* get_hollowed_mesh() const; + const TriangleMesh* get_hollowed_interior() const; protected: void on_update() override; @@ -206,9 +210,11 @@ protected: private: std::unique_ptr m_hollowed_mesh_transformed; + std::unique_ptr m_hollowed_interior_transformed; size_t m_old_hollowing_timestamp = 0; int m_print_object_idx = -1; int m_print_objects_count = 0; + sla::DrainHoles m_drainholes; }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index abbc8599bd..2f1396d634 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -163,6 +163,17 @@ void GLGizmosManager::reset_all_states() m_hover = Undefined; } +bool GLGizmosManager::open_gizmo(EType type) +{ + int idx = int(type); + if (m_gizmos[idx]->is_selectable() && m_gizmos[idx]->is_activable()) { + activate_gizmo(m_current == idx ? Undefined : (EType)idx); + update_data(); + return true; + } + return false; +} + void GLGizmosManager::set_hover_id(int id) { if (!m_enabled || m_current == Undefined) @@ -266,24 +277,21 @@ bool GLGizmosManager::is_running() const bool GLGizmosManager::handle_shortcut(int key) { - if (!m_enabled) + if (!m_enabled || m_parent.get_selection().is_empty()) return false; - if (m_parent.get_selection().is_empty()) + auto it = std::find_if(m_gizmos.begin(), m_gizmos.end(), + [key](const std::unique_ptr& gizmo) { + int gizmo_key = gizmo->get_shortcut_key(); + return gizmo->is_selectable() + && ((gizmo_key == key - 64) || (gizmo_key == key - 96)); + }); + + if (it == m_gizmos.end()) return false; - bool handled = false; - - for (size_t idx : get_selectable_idxs()) { - int it_key = m_gizmos[idx]->get_shortcut_key(); - - if (m_gizmos[idx]->is_activable() && ((it_key == key - 64) || (it_key == key - 96))) { - activate_gizmo(m_current == idx ? Undefined : (EType)idx); - handled = true; - } - } - - return handled; + EType gizmo_type = EType(it - m_gizmos.begin()); + return open_gizmo(gizmo_type); } bool GLGizmosManager::is_dragging() const @@ -814,10 +822,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt) if (!processed && !evt.HasModifiers()) { if (handle_shortcut(keyCode)) - { - update_data(); processed = true; - } } if (processed) @@ -1147,12 +1152,20 @@ bool GLGizmosManager::is_in_editing_mode(bool error_notification) const return false; if (error_notification) - wxGetApp().plater()->get_notification_manager()->push_slicing_error_notification( - _u8L("You are currently editing SLA support points. Please, apply or discard " - "your changes first.")); + wxGetApp().plater()->get_notification_manager()->push_notification( + NotificationType::QuitSLAManualMode, + NotificationManager::NotificationLevel::ErrorNotification, + _u8L("You are currently editing SLA support points. Please, " + "apply or discard your changes first.")); return true; } + +int GLGizmosManager::get_shortcut_key(GLGizmosManager::EType type) const +{ + return m_gizmos[type]->get_shortcut_key(); +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index 917a5830c2..c8951966e6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -171,6 +171,7 @@ public: void refresh_on_off_state(); void reset_all_states(); bool is_serializing() const { return m_serializing; } + bool open_gizmo(EType type); void set_hover_id(int id); void enable_grabber(EType type, unsigned int id, bool enable); @@ -228,6 +229,7 @@ public: void update_after_undo_redo(const UndoRedo::Snapshot& snapshot); int get_selectable_icons_cnt() const { return get_selectable_idxs().size(); } + int get_shortcut_key(GLGizmosManager::EType) const; private: void render_background(float left, float top, float right, float bottom, float border) const; diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 1ed4b492fd..db7af046bf 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -51,7 +51,9 @@ static const std::map font_icons_large = { {ImGui::EjectButton , "notification_eject_sd" }, {ImGui::EjectHoverButton , "notification_eject_sd_hover" }, {ImGui::WarningMarker , "notification_warning" }, - {ImGui::ErrorMarker , "notification_error" } + {ImGui::ErrorMarker , "notification_error" }, + {ImGui::CancelButton , "notification_cancel" }, + {ImGui::CancelHoverButton , "notification_cancel_hover" }, }; const ImVec4 ImGuiWrapper::COL_GREY_DARK = { 0.333f, 0.333f, 0.333f, 1.0f }; diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index 3f1207b479..391c1fe284 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -78,7 +78,7 @@ void ArrangeJob::prepare_all() { for (ModelObject *obj: m_plater->model().objects) for (ModelInstance *mi : obj->instances) { ArrangePolygons & cont = mi->printable ? m_selected : m_unprintable; - cont.emplace_back(get_arrange_poly(PtrWrapper{mi}, m_plater)); + cont.emplace_back(get_arrange_poly(mi, m_plater)); } if (auto wti = get_wipe_tower_arrangepoly(*m_plater)) @@ -111,7 +111,7 @@ void ArrangeJob::prepare_selected() { for (size_t i = 0; i < inst_sel.size(); ++i) { ArrangePolygon &&ap = - get_arrange_poly(PtrWrapper{mo->instances[i]}, m_plater); + get_arrange_poly(mo->instances[i], m_plater); ArrangePolygons &cont = mo->instances[i]->printable ? (inst_sel[i] ? m_selected : @@ -161,12 +161,7 @@ void ArrangeJob::process() { static const auto arrangestr = _(L("Arranging")); - const GLCanvas3D::ArrangeSettings &settings = - static_cast(m_plater->canvas3D())->get_arrange_settings(); - - arrangement::ArrangeParams params; - params.allow_rotations = settings.enable_rotation; - params.min_obj_distance = scaled(settings.distance); + arrangement::ArrangeParams params = get_arrange_params(m_plater); auto count = unsigned(m_selected.size() + m_unprintable.size()); Points bedpts = get_bed_shape(*m_plater->config()); @@ -235,4 +230,23 @@ double bed_stride(const Plater *plater) { return scaled((1. + LOGICAL_BED_GAP) * bedwidth); } +template<> +arrangement::ArrangePolygon get_arrange_poly(ModelInstance *inst, + const Plater * plater) +{ + return get_arrange_poly(PtrWrapper{inst}, plater); +} + +arrangement::ArrangeParams get_arrange_params(Plater *p) +{ + const GLCanvas3D::ArrangeSettings &settings = + static_cast(p->canvas3D())->get_arrange_settings(); + + arrangement::ArrangeParams params; + params.allow_rotations = settings.enable_rotation; + params.min_obj_distance = scaled(settings.distance); + + return params; +} + }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.hpp b/src/slic3r/GUI/Jobs/ArrangeJob.hpp index 38aafb52c1..7fa4b927e6 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.hpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.hpp @@ -4,7 +4,11 @@ #include "PlaterJob.hpp" #include "libslic3r/Arrange.hpp" -namespace Slic3r { namespace GUI { +namespace Slic3r { + +class ModelInstance; + +namespace GUI { class ArrangeJob : public PlaterJob { @@ -89,6 +93,11 @@ arrangement::ArrangePolygon get_arrange_poly(T obj, const Plater *plater) return ap; } +template<> +arrangement::ArrangePolygon get_arrange_poly(ModelInstance *inst, + const Plater * plater); + +arrangement::ArrangeParams get_arrange_params(Plater *p); }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/Job.cpp b/src/slic3r/GUI/Jobs/Job.cpp index 1f7f58875b..6346227aab 100644 --- a/src/slic3r/GUI/Jobs/Job.cpp +++ b/src/slic3r/GUI/Jobs/Job.cpp @@ -24,7 +24,7 @@ void GUI::Job::run(std::exception_ptr &eptr) void GUI::Job::update_status(int st, const wxString &msg) { - auto evt = new wxThreadEvent(); + auto evt = new wxThreadEvent(wxEVT_THREAD, m_thread_evt_id); evt->SetInt(st); evt->SetString(msg); wxQueueEvent(this, evt); @@ -33,7 +33,11 @@ void GUI::Job::update_status(int st, const wxString &msg) GUI::Job::Job(std::shared_ptr pri) : m_progress(std::move(pri)) { - Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) { + m_thread_evt_id = wxNewId(); + + Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) { + if (m_finalizing) return; + auto msg = evt.GetString(); if (!msg.empty() && !m_worker_error) m_progress->set_status_text(msg.ToUTF8().data()); @@ -53,13 +57,27 @@ GUI::Job::Job(std::shared_ptr pri) m_progress->set_progress(m_range); on_exception(m_worker_error); } - else + else { + // This is an RAII solution to remember that finalization is + // running. The run method calls update_status(status_range(), "") + // at the end, which queues up a call to this handler in all cases. + // If process also calls update_status with maxed out status arg + // it will call this handler twice. It is not a problem unless + // yield is called inside the finilize() method, which would + // jump out of finalize and call this handler again. + struct Finalizing { + bool &flag; + Finalizing (bool &f): flag(f) { flag = true; } + ~Finalizing() { flag = false; } + } fin(m_finalizing); + finalize(); + } // dont do finalization again for the same process m_finalized = true; } - }); + }, m_thread_evt_id); } void GUI::Job::start() @@ -76,7 +94,8 @@ void GUI::Job::start() m_progress->set_cancel_callback( [this]() { m_canceled.store(true); }); - m_finalized = false; + m_finalized = false; + m_finalizing = false; // Changing cursor to busy wxBeginBusyCursor(); diff --git a/src/slic3r/GUI/Jobs/Job.hpp b/src/slic3r/GUI/Jobs/Job.hpp index cb73fe74d4..082e5f4e8b 100644 --- a/src/slic3r/GUI/Jobs/Job.hpp +++ b/src/slic3r/GUI/Jobs/Job.hpp @@ -29,9 +29,10 @@ namespace Slic3r { namespace GUI { class Job : public wxEvtHandler { int m_range = 100; + int m_thread_evt_id = wxID_ANY; boost::thread m_thread; std::atomic m_running{false}, m_canceled{false}; - bool m_finalized = false; + bool m_finalized = false, m_finalizing = false; std::shared_ptr m_progress; std::exception_ptr m_worker_error = nullptr; diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp index 978ccf8fcf..a670affefb 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp @@ -7,47 +7,84 @@ #include "libslic3r/SLAPrint.hpp" #include "slic3r/GUI/Plater.hpp" +#include "libslic3r/PresetBundle.hpp" + +#include "slic3r/GUI/GUI_App.hpp" +#include "libslic3r/AppConfig.hpp" namespace Slic3r { namespace GUI { +void RotoptimizeJob::prepare() +{ + std::string accuracy_str = + wxGetApp().app_config->get("sla_auto_rotate", "accuracy"); + + std::string method_str = + wxGetApp().app_config->get("sla_auto_rotate", "method_id"); + + if (!accuracy_str.empty()) + m_accuracy = std::stof(accuracy_str); + + if (!method_str.empty()) + m_method_id = std::stoi(method_str); + + m_accuracy = std::max(0.f, std::min(m_accuracy, 1.f)); + m_method_id = std::max(size_t(0), std::min(get_methods_count() - 1, m_method_id)); + + m_default_print_cfg = wxGetApp().preset_bundle->full_config(); + + const auto &sel = m_plater->get_selection().get_content(); + + m_selected_object_ids.clear(); + m_selected_object_ids.reserve(sel.size()); + for (auto &[obj_idx, ignore] : sel) + m_selected_object_ids.emplace_back(obj_idx); +} + void RotoptimizeJob::process() { - int obj_idx = m_plater->get_selected_object_idx(); - if (obj_idx < 0 || int(m_plater->sla_print().objects().size()) <= obj_idx) - return; - - ModelObject *o = m_plater->model().objects[size_t(obj_idx)]; - const SLAPrintObject *po = m_plater->sla_print().objects()[size_t(obj_idx)]; + int prev_status = 0; + auto params = + sla::RotOptimizeParams{} + .accuracy(m_accuracy) + .print_config(&m_default_print_cfg) + .statucb([this, &prev_status](int s) + { + if (s > 0 && s < 100) + update_status(prev_status + s / m_selected_object_ids.size(), + _(L("Searching for optimal orientation"))); - if (!o || !po) return; - - TriangleMesh mesh = o->raw_mesh(); - mesh.require_shared_vertices(); - -// for (auto inst : o->instances) { -// Transform3d tr = Transform3d::Identity(); -// tr.rotate(Eigen::AngleAxisd(inst->get_rotation(Z), Vec3d::UnitZ())); -// tr.rotate(Eigen::AngleAxisd(inst->get_rotation(Y), Vec3d::UnitY())); -// tr.rotate(Eigen::AngleAxisd(inst->get_rotation(X), Vec3d::UnitX())); - -// double score = sla::get_model_supportedness(*po, tr); - -// std::cout << "Model supportedness before: " << score << std::endl; -// } - - Vec2d r = sla::find_best_rotation(*po, 0.75f, - [this](unsigned s) { - if (s < 100) - update_status(int(s), _(L("Searching for optimal orientation"))); - }, - [this] () { return was_canceled(); }); + return !was_canceled(); + }); - double mindist = 6.0; // FIXME + for (ObjRot &objrot : m_selected_object_ids) { + ModelObject *o = m_plater->model().objects[size_t(objrot.idx)]; + if (!o) continue; + + if (Methods[m_method_id].findfn) + objrot.rot = Methods[m_method_id].findfn(*o, params); + + prev_status += 100 / m_selected_object_ids.size(); + + if (was_canceled()) break; + } + + update_status(100, was_canceled() ? _(L("Orientation search canceled.")) : + _(L("Orientation found."))); +} + +void RotoptimizeJob::finalize() +{ + if (was_canceled()) return; + + for (const ObjRot &objrot : m_selected_object_ids) { + ModelObject *o = m_plater->model().objects[size_t(objrot.idx)]; + if (!o) continue; - if (!was_canceled()) { for(ModelInstance * oi : o->instances) { - oi->set_rotation({r[X], r[Y], 0.}); + if (objrot.rot) + oi->set_rotation({objrot.rot->x(), objrot.rot->y(), 0.}); auto trmatrix = oi->get_transformation().get_matrix(); Polygon trchull = o->convex_hull_2d(trmatrix); @@ -63,19 +100,13 @@ void RotoptimizeJob::process() oi->set_rotation(rt); } - m_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(); + +// m_plater->find_new_position(o->instances); } - update_status(100, was_canceled() ? _(L("Orientation search canceled.")) : - _(L("Orientation found."))); -} - -void RotoptimizeJob::finalize() -{ if (!was_canceled()) m_plater->update(); diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp index 06688b52d9..3144f3c3e6 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp @@ -3,17 +3,70 @@ #include "PlaterJob.hpp" -namespace Slic3r { namespace GUI { +#include "libslic3r/SLA/Rotfinder.hpp" +#include "libslic3r/PrintConfig.hpp" + +namespace Slic3r { + +namespace GUI { class RotoptimizeJob : public PlaterJob { + using FindFn = std::function; + + struct FindMethod { std::string name; FindFn findfn; }; + + static inline const FindMethod Methods[] = { + { L("Best surface quality"), sla::find_best_misalignment_rotation }, + { L("Least supports"), sla::find_least_supports_rotation }, + // Just a min area bounding box that is done for all methods anyway. + { L("Z axis only"), nullptr } + }; + + size_t m_method_id = 0; + float m_accuracy = 0.75; + + DynamicPrintConfig m_default_print_cfg; + + struct ObjRot + { + size_t idx; + std::optional rot; + ObjRot(size_t id): idx{id}, rot{} {} + }; + + std::vector m_selected_object_ids; + +protected: + + void prepare() override; + public: + RotoptimizeJob(std::shared_ptr pri, Plater *plater) : PlaterJob{std::move(pri), plater} {} void process() override; void finalize() override; + + static constexpr size_t get_methods_count() { return std::size(Methods); } + static const auto & get_method_names() + { + static bool m_method_names_valid = false; + static std::array m_method_names; + + if (!m_method_names_valid) { + + for (size_t i = 0; i < std::size(Methods); ++i) + m_method_names[i] = _utf8(Methods[i].name); + + m_method_names_valid = true; + } + + return m_method_names; + } }; }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp index f9fbef8a8f..3da937f0ac 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.cpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp @@ -132,7 +132,8 @@ SLAImportJob::~SLAImportJob() = default; void SLAImportJob::process() { auto progr = [this](int s) { - if (s < 100) update_status(int(s), _(L("Importing SLA archive"))); + if (s < 100) + update_status(int(s), _(L("Importing SLA archive"))); return !was_canceled(); }; @@ -178,7 +179,7 @@ void SLAImportJob::prepare() if (dlg.ShowModal() == wxID_OK) { auto path = dlg.get_path(); auto nm = wxFileName(path); - p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : path.ToUTF8(); + p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : nm.GetFullPath(); p->sel = dlg.get_selection(); p->win = dlg.get_marchsq_windowsize(); } else { diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index a7363837ea..1c451672c8 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -196,6 +196,9 @@ void KBShortcutsDialog::fill_shortcuts() { "D", L("Horizontal slider - Move active thumb Right") }, { "X", L("On/Off one layer mode of the vertical slider") }, { "L", L("Show/Hide Legend and Estimated printing time") }, +#if ENABLE_GCODE_WINDOW + { "C", L("Show/Hide G-code window") }, +#endif // ENABLE_GCODE_WINDOW }; m_full_shortcuts.push_back({ { _L("Preview"), "" }, preview_shortcuts }); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 91357ff6c4..9c426dcd96 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -521,11 +521,21 @@ void MainFrame::init_tabpanel() m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME); #ifndef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList m_tabpanel->SetFont(Slic3r::GUI::wxGetApp().normal_font()); +#endif +#if wxCHECK_VERSION(3,1,3) + if (wxSystemSettings::GetAppearance().IsDark()) + m_tabpanel->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); #endif m_tabpanel->Hide(); m_settings_dialog.set_tabpanel(m_tabpanel); - m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxEvent&) { + m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxBookCtrlEvent& e) { +#if ENABLE_VALIDATE_CUSTOM_GCODE + Tab* old_tab = dynamic_cast(m_tabpanel->GetPage(e.GetOldSelection())); + if (old_tab) + old_tab->validate_custom_gcodes(); +#endif // ENABLE_VALIDATE_CUSTOM_GCODE + wxWindow* panel = m_tabpanel->GetCurrentPage(); Tab* tab = dynamic_cast(panel); @@ -544,25 +554,11 @@ void MainFrame::init_tabpanel() select_tab(size_t(0)); // select Plater }); -#if ENABLE_VALIDATE_CUSTOM_GCODE - m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGING, [this](wxBookCtrlEvent& evt) { - wxWindow* panel = m_tabpanel->GetCurrentPage(); - if (panel != nullptr) { - Tab* tab = dynamic_cast(panel); - if (tab && (tab->type() == Preset::TYPE_FILAMENT || tab->type() == Preset::TYPE_PRINTER)) - if (!tab->validate_custom_gcodes()) - evt.Veto(); - } - }); -#endif // ENABLE_VALIDATE_CUSTOM_GCODE - m_plater = new Plater(this, this); m_plater->Hide(); wxGetApp().plater_ = m_plater; - wxGetApp().obj_list()->create_popup_menus(); - if (wxGetApp().is_editor()) create_preset_tabs(); @@ -673,7 +669,7 @@ bool MainFrame::is_active_and_shown_tab(Tab* tab) bool MainFrame::can_start_new_project() const { - return (m_plater != nullptr) && !m_plater->model().objects.empty(); + return (m_plater != nullptr) && (!m_plater->get_project_filename(".3mf").IsEmpty() || !m_plater->model().objects.empty()); } bool MainFrame::can_save() const @@ -846,6 +842,9 @@ void MainFrame::on_sys_color_changed() // update label colors in respect to the system mode wxGetApp().init_label_colours(); +#ifdef __WXMSW__ + m_tabpanel->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +#endif // update Plater wxGetApp().plater()->sys_color_changed(); @@ -1159,7 +1158,7 @@ void MainFrame::init_menubar_as_editor() editMenu->AppendSeparator(); append_menu_item(editMenu, wxID_ANY, _L("Searc&h") + "\tCtrl+F", - _L("Search in settings"), [this](wxCommandEvent&) { m_plater->search(/*m_tabpanel->GetCurrentPage() == */m_plater->IsShown()); }, + _L("Search in settings"), [this](wxCommandEvent&) { m_plater->search(m_plater->IsShown()); }, "search", nullptr, []() {return true; }, this); } @@ -1218,6 +1217,14 @@ void MainFrame::init_menubar_as_editor() append_menu_check_item(viewMenu, wxID_ANY, _L("&Collapse sidebar") + sep + "Shift+" + sep_space + "Tab", _L("Collapse sidebar"), [this](wxCommandEvent&) { m_plater->collapse_sidebar(!m_plater->is_sidebar_collapsed()); }, this, []() { return true; }, [this]() { return m_plater->is_sidebar_collapsed(); }, this); +#ifndef __APPLE__ + // OSX adds its own menu item to toggle fullscreen. + append_menu_check_item(viewMenu, wxID_ANY, _L("&Full screen") + "\t" + "F11", _L("Full screen"), + [this](wxCommandEvent&) { this->ShowFullScreen(!this->IsFullScreen(), + // wxFULLSCREEN_ALL: wxFULLSCREEN_NOMENUBAR | wxFULLSCREEN_NOTOOLBAR | wxFULLSCREEN_NOSTATUSBAR | wxFULLSCREEN_NOBORDER | wxFULLSCREEN_NOCAPTION + wxFULLSCREEN_NOSTATUSBAR | wxFULLSCREEN_NOBORDER | wxFULLSCREEN_NOCAPTION); }, + this, []() { return true; }, [this]() { return this->IsFullScreen(); }, this); +#endif // __APPLE__ } // Help menu diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index ee0abe76f9..f9ccfd0d6e 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -2,6 +2,7 @@ #include "libslic3r/Tesselate.hpp" #include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/ClipperUtils.hpp" #include "slic3r/GUI/Camera.hpp" @@ -31,6 +32,15 @@ void MeshClipper::set_mesh(const TriangleMesh& mesh) } } +void MeshClipper::set_negative_mesh(const TriangleMesh& mesh) +{ + if (m_negative_mesh != &mesh) { + m_negative_mesh = &mesh; + m_triangles_valid = false; + m_triangles2d.resize(0); + } +} + void MeshClipper::set_transformation(const Geometry::Transformation& trafo) @@ -74,6 +84,15 @@ void MeshClipper::recalculate_triangles() std::vector list_of_expolys; m_tms->set_up_direction(up.cast()); m_tms->slice(std::vector{height_mesh}, SlicingMode::Regular, 0.f, &list_of_expolys, [](){}); + + if (m_negative_mesh && !m_negative_mesh->empty()) { + TriangleMeshSlicer negative_tms{m_negative_mesh}; + negative_tms.set_up_direction(up.cast()); + + std::vector neg_polys; + negative_tms.slice(std::vector{height_mesh}, SlicingMode::Regular, 0.f, &neg_polys, [](){}); + list_of_expolys.front() = diff_ex(list_of_expolys.front(), neg_polys.front()); + } m_triangles2d = triangulate_expolygons_2f(list_of_expolys[0], m_trafo.get_matrix().matrix().determinant() < 0.); // Rotate the cut into world coords: diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 60dcb30c81..09caf199bb 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -78,6 +78,8 @@ public: // must make sure that it stays valid. void set_mesh(const TriangleMesh& mesh); + void set_negative_mesh(const TriangleMesh &mesh); + // Inform the MeshClipper about the transformation that transforms the mesh // into world coordinates. void set_transformation(const Geometry::Transformation& trafo); @@ -91,6 +93,7 @@ private: Geometry::Transformation m_trafo; const TriangleMesh* m_mesh = nullptr; + const TriangleMesh* m_negative_mesh = nullptr; ClippingPlane m_plane; std::vector m_triangles2d; GLIndexedVertexArray m_vertex_array; diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index a6fd9cfd3c..7f61ad7f39 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -1,12 +1,11 @@ #include "NotificationManager.hpp" -#include "GUI_App.hpp" -#include "GUI.hpp" -#include "Plater.hpp" -#include "GLCanvas3D.hpp" -#include "ImGuiWrapper.hpp" +#include "GUI.hpp" +#include "ImGuiWrapper.hpp" +#include "PrintHostDialogs.hpp" #include "wxExtensions.hpp" +#include "../Utils/PrintHost.hpp" #include #include @@ -22,9 +21,6 @@ static constexpr float SPACE_RIGHT_PANEL = 10.0f; static constexpr float FADING_OUT_DURATION = 2.0f; // Time in Miliseconds after next render when fading out is requested static constexpr int FADING_OUT_TIMEOUT = 100; -// If timeout is changed to higher than 1 second, substract_time call should be revorked -//static constexpr int MAX_TIMEOUT_MILISECONDS = 1000; -//static constexpr int MAX_TIMEOUT_SECONDS = 1; namespace Slic3r { namespace GUI { @@ -131,35 +127,35 @@ void NotificationManager::NotificationIDProvider::release_id(int) {} NotificationManager::PopNotification::PopNotification(const NotificationData &n, NotificationIDProvider &id_provider, wxEvtHandler* evt_handler) : m_data (n) , m_id_provider (id_provider) - , m_remaining_time (n.duration) - , m_last_remaining_time (n.duration) - , m_counting_down (n.duration != 0) , m_text1 (n.text1) , m_hypertext (n.hypertext) , m_text2 (n.text2) , m_evt_handler (evt_handler) , m_notification_start (GLCanvas3D::timestamp_now()) -{ - //init(); -} +{} void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width) { - if (!m_initialized) + + if (m_state == EState::Unknown) init(); - if (m_hidden) { + if (m_state == EState::Hidden) { m_top_y = initial_y - GAP_WIDTH; return; } - if (m_fading_out) - m_last_render_fading = GLCanvas3D::timestamp_now(); + if (m_state == EState::ClosePending || m_state == EState::Finished) + { + m_state = EState::Finished; + return; + } - Size cnv_size = canvas.get_canvas_size(); + Size cnv_size = canvas.get_canvas_size(); ImGuiWrapper& imgui = *wxGetApp().imgui(); - ImVec2 mouse_pos = ImGui::GetMousePos(); - float right_gap = SPACE_RIGHT_PANEL + (move_from_overlay ? overlay_width + m_line_height * 5 : 0); + ImVec2 mouse_pos = ImGui::GetMousePos(); + float right_gap = SPACE_RIGHT_PANEL + (move_from_overlay ? overlay_width + m_line_height * 5 : 0); + bool fading_pop = false; if (m_line_height != ImGui::CalcTextSize("A").y) init(); @@ -173,55 +169,36 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init imgui.set_next_window_pos(win_pos.x, win_pos.y, ImGuiCond_Always, 1.0f, 0.0f); imgui.set_next_window_size(m_window_width, m_window_height, ImGuiCond_Always); - // find if hovered - m_hovered = false; + + // find if hovered FIXME: do it only in update state? + if (m_state == EState::Hovered) { + m_state = EState::Unknown; + init(); + } + if (mouse_pos.x < win_pos.x && mouse_pos.x > win_pos.x - m_window_width && mouse_pos.y > win_pos.y && mouse_pos.y < win_pos.y + m_window_height) { ImGui::SetNextWindowFocus(); - m_hovered = true; + set_hovered(); } - + // color change based on fading out - bool fading_pop = false; - if (m_fading_out) { - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), m_fading_out, m_current_fade_opacity); + if (m_state == EState::FadingOut) { + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), true, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), true, m_current_fade_opacity); fading_pop = true; } - - // background color - if (m_is_gray) { - ImVec4 backcolor(0.7f, 0.7f, 0.7f, 0.5f); - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); - } - else if (m_data.level == NotificationLevel::ErrorNotification) { - ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); - backcolor.x += 0.3f; - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); - } - else if (m_data.level == NotificationLevel::WarningNotification) { - ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); - backcolor.x += 0.3f; - backcolor.y += 0.15f; - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); - } - - // name of window - probably indentifies window and is shown so last_end add whitespaces according to id + + bool bgrnd_color_pop = push_background_color(); + + + // name of window indentifies window - has to be unique string if (m_id == 0) m_id = m_id_provider.allocate_id(); std::string name = "!!Ntfctn" + std::to_string(m_id); + if (imgui.begin(name, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar)) { ImVec2 win_size = ImGui::GetWindowSize(); - //FIXME: dont forget to us this for texts - //GUI::format(_utf8(L())); - - /* - //countdown numbers - ImGui::SetCursorPosX(15); - ImGui::SetCursorPosY(15); - imgui.text(std::to_string(m_remaining_time).c_str()); - */ - render_left_sign(imgui); render_text(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y); render_close_button(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y); @@ -231,13 +208,34 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init } imgui.end(); - if (m_is_gray || m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) + if (bgrnd_color_pop) ImGui::PopStyleColor(); if (fading_pop) ImGui::PopStyleColor(2); } - +bool NotificationManager::PopNotification::push_background_color() +{ + if (m_is_gray) { + ImVec4 backcolor(0.7f, 0.7f, 0.7f, 0.5f); + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); + return true; + } + if (m_data.level == NotificationLevel::ErrorNotification) { + ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); + backcolor.x += 0.3f; + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); + return true; + } + if (m_data.level == NotificationLevel::WarningNotification) { + ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); + backcolor.x += 0.3f; + backcolor.y += 0.15f; + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); + return true; + } + return false; +} void NotificationManager::PopNotification::count_spaces() { //determine line width @@ -253,30 +251,29 @@ void NotificationManager::PopNotification::count_spaces() m_window_width_offset = m_left_indentation + m_line_height * 3.f; m_window_width = m_line_height * 25; } -void NotificationManager::PopNotification::init() + +void NotificationManager::PopNotification::count_lines() { - std::string text = m_text1 + " " + m_hypertext; - size_t last_end = 0; - m_lines_count = 0; + std::string text = m_text1 + " " + m_hypertext; + size_t last_end = 0; + m_lines_count = 0; - count_spaces(); - - // count lines m_endlines.clear(); while (last_end < text.length() - 1) { - size_t next_hard_end = text.find_first_of('\n', last_end); - if (next_hard_end != std::string::npos && ImGui::CalcTextSize(text.substr(last_end, next_hard_end - last_end).c_str()).x < m_window_width - m_window_width_offset) { + size_t next_hard_end = text.find_first_of('\n', last_end); + if (next_hard_end != std::string::npos && ImGui::CalcTextSize(text.substr(last_end, next_hard_end - last_end).c_str()).x < m_window_width - m_window_width_offset) { //next line is ended by '/n' m_endlines.push_back(next_hard_end); last_end = next_hard_end + 1; - } else { + } + else { // find next suitable endline if (ImGui::CalcTextSize(text.substr(last_end).c_str()).x >= m_window_width - m_window_width_offset) { // more than one line till end - size_t next_space = text.find_first_of(' ', last_end); + size_t next_space = text.find_first_of(' ', last_end); if (next_space > 0) { - size_t next_space_candidate = text.find_first_of(' ', next_space + 1); + size_t next_space_candidate = text.find_first_of(' ', next_space + 1); while (next_space_candidate > 0 && ImGui::CalcTextSize(text.substr(last_end, next_space_candidate - last_end).c_str()).x < m_window_width - m_window_width_offset) { next_space = next_space_candidate; next_space_candidate = text.find_first_of(' ', next_space + 1); @@ -290,7 +287,8 @@ void NotificationManager::PopNotification::init() } m_endlines.push_back(last_end + letter_count); last_end += letter_count; - } else { + } + else { m_endlines.push_back(next_space); last_end = next_space + 1; } @@ -304,9 +302,22 @@ void NotificationManager::PopNotification::init() } m_lines_count++; } +} + +void NotificationManager::PopNotification::init() +{ + // Do not init closing notification + if (is_finished()) + return; + + count_spaces(); + count_lines(); + if (m_lines_count == 3) m_multiline = true; - m_initialized = true; + m_notification_start = GLCanvas3D::timestamp_now(); + if (m_state == EState::Unknown) + m_state = EState::Shown; } void NotificationManager::PopNotification::set_next_window_size(ImGuiWrapper& imgui) { @@ -329,8 +340,8 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons if (m_multiline) { int last_end = 0; - float starting_y = m_line_height/2;//10; - float shift_y = m_line_height;// -m_line_height / 20; + float starting_y = m_line_height/2; + float shift_y = m_line_height; for (size_t i = 0; i < m_lines_count; i++) { std::string line = m_text1.substr(last_end , m_endlines[i] - last_end); if(i < m_lines_count - 1) @@ -375,12 +386,14 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2); imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); // line2 - std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); - cursor_y = win_size.y / 2 + win_size.y / 6 - m_line_height / 2; - ImGui::SetCursorPosX(x_offset); - ImGui::SetCursorPosY(cursor_y); - imgui.text(line.c_str()); - cursor_x = x_offset + ImGui::CalcTextSize(line.c_str()).x; + if (m_text1.length() > m_endlines[0]) { + std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); + cursor_y = win_size.y / 2 + win_size.y / 6 - m_line_height / 2; + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(cursor_y); + imgui.text(line.c_str()); + cursor_x = x_offset + ImGui::CalcTextSize(line.c_str()).x; + } } else { ImGui::SetCursorPosX(x_offset); ImGui::SetCursorPosY(cursor_y); @@ -423,8 +436,8 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, m_multiline = true; set_next_window_size(imgui); } - else { - m_close_pending = on_text_click(); + else if (on_text_click()) { + close(); } } ImGui::PopStyleColor(); @@ -432,12 +445,12 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, ImGui::PopStyleColor(); //hover color - ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f);//ImGui::GetStyleColorVec4(ImGuiCol_Button); + ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f); if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly)) orange_color.y += 0.2f; //text - Notifications_Internal::push_style_color(ImGuiCol_Text, orange_color, m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_Text, orange_color, m_state == EState::FadingOut, m_current_fade_opacity); ImGui::SetCursorPosX(text_x); ImGui::SetCursorPosY(text_y); imgui.text(text.c_str()); @@ -448,7 +461,7 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, lineEnd.y -= 2; ImVec2 lineStart = lineEnd; lineStart.x = ImGui::GetItemRectMin().x; - ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.w * 255.f * (m_fading_out ? m_current_fade_opacity : 1.f)))); + ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.w * 255.f * (m_state == EState::FadingOut ? m_current_fade_opacity : 1.f)))); } @@ -458,12 +471,11 @@ void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& img ImVec2 win_pos(win_pos_x, win_pos_y); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); - Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); - //button - if part if treggered std::string button_text; button_text = ImGui::CloseNotifButton; @@ -479,7 +491,7 @@ void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& img ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) { - m_close_pending = true; + close(); } //invisible large button @@ -487,7 +499,7 @@ void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& img ImGui::SetCursorPosY(0); if (imgui.button(" ", m_line_height * 2.125, win_size.y - ( m_minimize_b_visible ? 2 * m_line_height : 0))) { - m_close_pending = true; + close(); } ImGui::PopStyleColor(); ImGui::PopStyleColor(); @@ -510,9 +522,9 @@ void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper& { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); - Notifications_Internal::push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_state == EState::FadingOut, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); //button - if part if treggered @@ -553,7 +565,7 @@ void NotificationManager::PopNotification::update(const NotificationData& n) m_text2 = n.text2; init(); } -bool NotificationManager::PopNotification::compare_text(const std::string& text) +bool NotificationManager::PopNotification::compare_text(const std::string& text) const { std::wstring wt1 = boost::nowide::widen(m_text1); std::wstring wt2 = boost::nowide::widen(text); @@ -564,71 +576,63 @@ bool NotificationManager::PopNotification::compare_text(const std::string& text) return false; } -void NotificationManager::PopNotification::update_state() +bool NotificationManager::PopNotification::update_state(bool paused, const int64_t delta) { - if (!m_initialized) - init(); m_next_render = std::numeric_limits::max(); - if (m_hidden) { - m_state = EState::Hidden; - return; + if (m_state == EState::Unknown) { + init(); + return true; + } + + if (m_state == EState::Hidden) { + return false; } int64_t now = GLCanvas3D::timestamp_now(); - if (m_hovered) { - // reset fading - m_fading_out = false; + // reset timers - hovered state is set in render + if (m_state == EState::Hovered) { m_current_fade_opacity = 1.0f; - m_remaining_time = m_data.duration; - m_notification_start = now; - } - - - - if (m_counting_down) { + m_state = EState::Unknown; + init(); + // Timers when not fading + } else if (m_state != EState::NotFading && m_state != EState::FadingOut && m_data.duration != 0 && !paused) { int64_t up_time = now - m_notification_start; + if (up_time >= m_data.duration * 1000) { + m_state = EState::FadingOut; + m_fading_start = now; + } else { + m_next_render = m_data.duration * 1000 - up_time; + } + } + // Timers when fading + if (m_state == EState::FadingOut && !paused) { + int64_t curr_time = now - m_fading_start; + int64_t next_render = FADING_OUT_TIMEOUT - delta; + m_current_fade_opacity = std::clamp(1.0f - 0.001f * static_cast(curr_time) / FADING_OUT_DURATION, 0.0f, 1.0f); + if (m_current_fade_opacity <= 0.0f) { + m_state = EState::Finished; + return true; + } else if (next_render <= 20) { + m_next_render = FADING_OUT_TIMEOUT; + return true; + } else { + m_next_render = next_render; + return false; + } + } - if (m_fading_out && m_current_fade_opacity <= 0.0f) - m_finished = true; - else if (!m_fading_out && /*m_remaining_time <=0*/up_time >= m_data.duration * 1000) { - m_fading_out = true; - m_fading_start = now; - m_last_render_fading = now; - } else if (!m_fading_out) { - m_next_render = m_data.duration * 1000 - up_time;//std::min(/*m_data.duration * 1000 - up_time*/m_remaining_time * 1000, MAX_TIMEOUT_MILISECONDS); - } - + if (m_state == EState::Finished) { + return true; } - - if (m_finished) { + + if (m_state == EState::ClosePending) { m_state = EState::Finished; - m_next_render = 0; - return; - } - if (m_close_pending) { - m_finished = true; - m_state = EState::ClosePending; - m_next_render = 0; - return; - } - if (m_fading_out) { - if (!m_paused) { - m_state = EState::FadingOutStatic; - int64_t curr_time = now - m_fading_start; - int64_t no_render_time = now - m_last_render_fading; - m_current_fade_opacity = std::clamp(1.0f - 0.001f * static_cast(curr_time) / FADING_OUT_DURATION, 0.0f, 1.0f); - auto next_render = FADING_OUT_TIMEOUT - no_render_time; - if (next_render <= 0) { - //m_last_render_fading = GLCanvas3D::timestamp_now(); - m_state = EState::FadingOutRender; - m_next_render = 0; - } else - m_next_render = next_render; - } + return true; } + return false; } NotificationManager::SlicingCompleteLargeNotification::SlicingCompleteLargeNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool large) : @@ -672,9 +676,11 @@ void NotificationManager::SlicingCompleteLargeNotification::set_print_info(const void NotificationManager::SlicingCompleteLargeNotification::set_large(bool l) { m_is_large = l; - m_counting_down = !l; + //FIXME this information should not be lost (change m_data?) +// m_counting_down = !l; m_hypertext = l ? _u8L("Export G-Code.") : std::string(); - m_hidden = !l; + m_state = l ? EState::Shown : EState::Hidden; + init(); } //---------------ExportFinishedNotification----------- void NotificationManager::ExportFinishedNotification::count_spaces() @@ -733,8 +739,8 @@ void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiW ImVec2 win_pos(win_pos_x, win_pos_y); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); - Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); std::string button_text; @@ -768,7 +774,7 @@ void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiW assert(m_evt_handler != nullptr); if (m_evt_handler != nullptr) wxPostEvent(m_evt_handler, EjectDriveNotificationClickedEvent(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED)); - m_close_pending = true; + close(); } //invisible large button @@ -779,7 +785,7 @@ void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiW assert(m_evt_handler != nullptr); if (m_evt_handler != nullptr) wxPostEvent(m_evt_handler, EjectDriveNotificationClickedEvent(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED)); - m_close_pending = true; + close(); } ImGui::PopStyleColor(); ImGui::PopStyleColor(); @@ -796,33 +802,261 @@ bool NotificationManager::ExportFinishedNotification::on_text_click() void NotificationManager::ProgressBarNotification::init() { PopNotification::init(); - m_lines_count++; - m_endlines.push_back(m_endlines.back()); + //m_lines_count++; + if(m_lines_count >= 2) { + m_lines_count = 3; + m_multiline = true; + while (m_endlines.size() < 3) + m_endlines.push_back(m_endlines.back()); + } else { + m_lines_count = 2; + m_endlines.push_back(m_endlines.back()); + } + if(m_state == EState::Shown) + m_state = EState::NotFading; } + + +void NotificationManager::ProgressBarNotification::count_lines() +{ + std::string text = m_text1 + " " + m_hypertext; + size_t last_end = 0; + m_lines_count = 0; + + m_endlines.clear(); + while (last_end < text.length() - 1) + { + size_t next_hard_end = text.find_first_of('\n', last_end); + if (next_hard_end != std::string::npos && ImGui::CalcTextSize(text.substr(last_end, next_hard_end - last_end).c_str()).x < m_window_width - m_window_width_offset) { + //next line is ended by '/n' + m_endlines.push_back(next_hard_end); + last_end = next_hard_end + 1; + } + else { + // find next suitable endline + if (ImGui::CalcTextSize(text.substr(last_end).c_str()).x >= m_window_width - m_window_width_offset) { + // more than one line till end + size_t next_space = text.find_first_of(' ', last_end); + if (next_space > 0) { + size_t next_space_candidate = text.find_first_of(' ', next_space + 1); + while (next_space_candidate > 0 && ImGui::CalcTextSize(text.substr(last_end, next_space_candidate - last_end).c_str()).x < m_window_width - m_window_width_offset) { + next_space = next_space_candidate; + next_space_candidate = text.find_first_of(' ', next_space + 1); + } + // when one word longer than line. Or the last space is too early. + if (ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x > m_window_width - m_window_width_offset || + ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x < (m_window_width - m_window_width_offset) / 4 * 3 + ) { + float width_of_a = ImGui::CalcTextSize("a").x; + int letter_count = (int)((m_window_width - m_window_width_offset) / width_of_a); + while (last_end + letter_count < text.size() && ImGui::CalcTextSize(text.substr(last_end, letter_count).c_str()).x < m_window_width - m_window_width_offset) { + letter_count++; + } + m_endlines.push_back(last_end + letter_count); + last_end += letter_count; + } + else { + m_endlines.push_back(next_space); + last_end = next_space + 1; + } + } + } + else { + m_endlines.push_back(text.length()); + last_end = text.length(); + } + + } + m_lines_count++; + } +} + + + void NotificationManager::ProgressBarNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { - PopNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); - render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + // line1 - we do not print any more text than what fits on line 1. Line 2 is bar. + if (m_multiline) { + // two lines text, one line bar + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(m_line_height / 4); + imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(m_line_height + m_line_height / 4); + std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0), m_endlines[1] - m_endlines[0] - (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); + imgui.text(line.c_str()); + if (m_has_cancel_button) + render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + } else { + //one line text, one line bar + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(/*win_size_y / 2 - win_size_y / 6 -*/ m_line_height / 4); + imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + if (m_has_cancel_button) + render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + } + + } void NotificationManager::ProgressBarNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { - ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f); - float invisible_length = 0;//((float)(m_data.duration - m_remaining_time) / (float)m_data.duration * win_size_x); - //invisible_length -= win_size_x / ((float)m_data.duration * 60.f) * (60 - m_countdown_frame); - ImVec2 lineEnd = ImVec2(win_pos_x - invisible_length - m_window_width_offset, win_pos_y + win_size_y/2 + m_line_height / 2); - ImVec2 lineStart = ImVec2(win_pos_x - win_size_x + m_left_indentation, win_pos_y + win_size_y/2 + m_line_height / 2); - ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (1.0f * 255.f)), m_line_height * 0.7f); - /* - //countdown line - ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button); - float invisible_length = ((float)(m_data.duration - m_remaining_time) / (float)m_data.duration * win_size_x); - invisible_length -= win_size_x / ((float)m_data.duration * 60.f) * (60 - m_countdown_frame); - ImVec2 lineEnd = ImVec2(win_pos_x - invisible_length, win_pos_y + win_size_y - 5); - ImVec2 lineStart = ImVec2(win_pos_x - win_size_x, win_pos_y + win_size_y - 5); - ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.picture_width * 255.f * (m_fading_out ? m_current_fade_opacity : 1.f))), 2.f); - if (!m_paused) - m_countdown_frame++; - */ + ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f); + ImVec4 gray_color = ImVec4(.34f, .34f, .34f, 1.0f); + ImVec2 lineEnd = ImVec2(win_pos_x - m_window_width_offset, win_pos_y + win_size_y / 2 + (m_multiline ? m_line_height / 2 : 0)); + ImVec2 lineStart = ImVec2(win_pos_x - win_size_x + m_left_indentation, win_pos_y + win_size_y / 2 + (m_multiline ? m_line_height / 2 : 0)); + ImVec2 midPoint = ImVec2(lineStart.x + (lineEnd.x - lineStart.x) * m_percentage, lineStart.y); + ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(gray_color.x * 255), (int)(gray_color.y * 255), (int)(gray_color.z * 255), (1.0f * 255.f)), m_line_height * 0.2f); + ImGui::GetWindowDrawList()->AddLine(lineStart, midPoint, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (1.0f * 255.f)), m_line_height * 0.2f); +} +//------PrintHostUploadNotification---------------- +void NotificationManager::PrintHostUploadNotification::init() +{ + ProgressBarNotification::init(); + if (m_state == EState::NotFading && m_uj_state == UploadJobState::PB_COMPLETED) + m_state = EState::Shown; +} +void NotificationManager::PrintHostUploadNotification::count_spaces() +{ + //determine line width + m_line_height = ImGui::CalcTextSize("A").y; + + m_left_indentation = m_line_height; + if (m_uj_state == UploadJobState::PB_ERROR) { + std::string text; + text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker); + float picture_width = ImGui::CalcTextSize(text.c_str()).x; + m_left_indentation = picture_width + m_line_height / 2; + } + m_window_width_offset = m_line_height * 6; //(m_has_cancel_button ? 6 : 4); + m_window_width = m_line_height * 25; +} +bool NotificationManager::PrintHostUploadNotification::push_background_color() +{ + + if (m_uj_state == UploadJobState::PB_ERROR) { + ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); + backcolor.x += 0.3f; + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); + return true; + } + return false; +} +void NotificationManager::PrintHostUploadNotification::set_percentage(float percent) +{ + m_percentage = percent; + if (percent >= 1.0f) { + m_uj_state = UploadJobState::PB_COMPLETED; + m_has_cancel_button = false; + init(); + } else if (percent < 0.0f) { + error(); + } else { + m_uj_state = UploadJobState::PB_PROGRESS; + m_has_cancel_button = true; + } +} +void NotificationManager::PrintHostUploadNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + std::string text; + switch (m_uj_state) { + case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_PROGRESS: + { + ProgressBarNotification::render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + float uploaded = m_file_size * m_percentage; + std::stringstream stream; + stream << std::fixed << std::setprecision(2) << (int)(m_percentage * 100) << "% - " << uploaded << " of " << m_file_size << "MB uploaded"; + text = stream.str(); + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? 0 : m_line_height / 4)); + break; + } + case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_ERROR: + text = _u8L("ERROR"); + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? m_line_height / 4 : m_line_height / 2)); + break; + case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_CANCELLED: + text = _u8L("CANCELED"); + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? m_line_height / 4 : m_line_height / 2)); + break; + case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_COMPLETED: + text = _u8L("COMPLETED"); + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? m_line_height / 4 : m_line_height / 2)); + break; + } + + imgui.text(text.c_str()); + +} +void NotificationManager::PrintHostUploadNotification::render_left_sign(ImGuiWrapper& imgui) +{ + if (m_uj_state == UploadJobState::PB_ERROR) { + std::string text; + text = ImGui::ErrorMarker; + ImGui::SetCursorPosX(m_line_height / 3); + ImGui::SetCursorPosY(m_window_height / 2 - m_line_height); + imgui.text(text.c_str()); + } +} +void NotificationManager::PrintHostUploadNotification::render_cancel_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + ImVec2 win_size(win_size_x, win_size_y); + ImVec2 win_pos(win_pos_x, win_pos_y); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); + Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); + + std::string button_text; + button_text = ImGui::CancelButton; + + if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - m_line_height * 5.f, win_pos.y), + ImVec2(win_pos.x - m_line_height * 2.5f, win_pos.y + win_size.y), + true)) + { + button_text = ImGui::CancelHoverButton; + // tooltip + long time_now = wxGetLocalTime(); + if (m_hover_time > 0 && m_hover_time < time_now) { + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); + ImGui::BeginTooltip(); + imgui.text(_u8L("Cancel upload") + " " + GUI::shortkey_ctrl_prefix() + "T"); + ImGui::EndTooltip(); + ImGui::PopStyleColor(); + } + if (m_hover_time == 0) + m_hover_time = time_now; + } + else + m_hover_time = 0; + + ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); + ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); + ImGui::SetCursorPosX(win_size.x - m_line_height * 5.0f); + ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); + if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + { + wxGetApp().printhost_job_queue().cancel(m_job_id - 1); + } + + //invisible large button + ImGui::SetCursorPosX(win_size.x - m_line_height * 4.625f); + ImGui::SetCursorPosY(0); + if (imgui.button(" ", m_line_height * 2.f, win_size.y)) + { + wxGetApp().printhost_job_queue().cancel(m_job_id - 1); + } + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + } //------NotificationManager-------- NotificationManager::NotificationManager(wxEvtHandler* evt_handler) : @@ -881,12 +1115,7 @@ void NotificationManager::push_plater_error_notification(const std::string& text { push_notification_data({ NotificationType::PlaterError, NotificationLevel::ErrorNotification, 0, _u8L("ERROR:") + "\n" + text }, 0); } -void NotificationManager::push_plater_warning_notification(const std::string& text) -{ - push_notification_data({ NotificationType::PlaterWarning, NotificationLevel::WarningNotification, 0, _u8L("WARNING:") + "\n" + text }, 0); - // dissaper if in preview - set_in_preview(m_in_preview); -} + void NotificationManager::close_plater_error_notification(const std::string& text) { for (std::unique_ptr ¬ification : m_pop_notifications) { @@ -895,11 +1124,32 @@ void NotificationManager::close_plater_error_notification(const std::string& tex } } } + +void NotificationManager::push_plater_warning_notification(const std::string& text) +{ + // Find if was not hidden + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::PlaterWarning && notification->compare_text(_u8L("WARNING:") + "\n" + text)) { + if (notification->get_state() == PopNotification::EState::Hidden) { + //dynamic_cast(notification.get())->show(); + return; + } + } + } + + NotificationData data{ NotificationType::PlaterWarning, NotificationLevel::WarningNotification, 0, _u8L("WARNING:") + "\n" + text }; + + auto notification = std::make_unique(data, m_id_provider, m_evt_handler); + push_notification_data(std::move(notification), 0); + // dissaper if in preview + apply_in_preview(); +} + void NotificationManager::close_plater_warning_notification(const std::string& text) { for (std::unique_ptr ¬ification : m_pop_notifications) { if (notification->get_type() == NotificationType::PlaterWarning && notification->compare_text(_u8L("WARNING:") + "\n" + text)) { - notification->close(); + dynamic_cast(notification.get())->real_close(); } } } @@ -937,6 +1187,14 @@ void NotificationManager::close_slicing_errors_and_warnings() } } } +void NotificationManager::close_slicing_error_notification(const std::string& text) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingError && notification->compare_text(_u8L("ERROR:") + "\n" + text)) { + notification->close(); + } + } +} void NotificationManager::push_slicing_complete_notification(int timestamp, bool large) { std::string hypertext; @@ -997,23 +1255,57 @@ void NotificationManager::push_exporting_finished_notification(const std::string NotificationData data{ NotificationType::ExportFinished, NotificationLevel::RegularNotification, on_removable ? 0 : 20, _u8L("Exporting finished.") + "\n" + path }; push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, on_removable, path, dir_path), 0); } -void NotificationManager::push_progress_bar_notification(const std::string& text, float percentage) + +void NotificationManager::push_upload_job_notification(int id, float filesize, const std::string& filename, const std::string& host, float percentage) { - NotificationData data{ NotificationType::ProgressBar, NotificationLevel::ProgressBarNotification, 0, text }; - push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, 0), 0); -} -void NotificationManager::set_progress_bar_percentage(const std::string& text, float percentage) -{ - bool found = false; + // find if upload with same id was not already in notification + // done by compare_jon_id not compare_text thus has to be performed here for (std::unique_ptr& notification : m_pop_notifications) { - if (notification->get_type() == NotificationType::ProgressBar && notification->compare_text(text)) { - dynamic_cast(notification.get())->set_percentage(percentage); - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); - found = true; + if (notification->get_type() == NotificationType::PrintHostUpload && dynamic_cast(notification.get())->compare_job_id(id)) { + return; } } - if (!found) { - push_progress_bar_notification(text, percentage); + std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host); + NotificationData data{ NotificationType::PrintHostUpload, NotificationLevel::ProgressBarNotification, 10, text }; + push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, 0, id, filesize), 0); +} +void NotificationManager::set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::PrintHostUpload) { + PrintHostUploadNotification* phun = dynamic_cast(notification.get()); + if (phun->compare_job_id(id)) { + phun->set_percentage(percentage); + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); + break; + } + } + } +} +void NotificationManager::upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::PrintHostUpload) { + PrintHostUploadNotification* phun = dynamic_cast(notification.get()); + if (phun->compare_job_id(id)) { + phun->cancel(); + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); + break; + } + } + } +} +void NotificationManager::upload_job_notification_show_error(int id, const std::string& filename, const std::string& host) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::PrintHostUpload) { + PrintHostUploadNotification* phun = dynamic_cast(notification.get()); + if(phun->compare_job_id(id)) { + phun->error(); + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); + break; + } + } } } bool NotificationManager::push_notification_data(const NotificationData& notification_data, int timestamp) @@ -1035,20 +1327,19 @@ bool NotificationManager::push_notification_data(std::unique_ptractivate_existing(notification.get())) { m_pop_notifications.back()->update(notification->get_data()); - canvas.request_extra_frame_delayed(33); + canvas.schedule_extra_frame(0); return false; } else { m_pop_notifications.emplace_back(std::move(notification)); - canvas.request_extra_frame_delayed(33); + canvas.schedule_extra_frame(0); return true; } } -void NotificationManager::render_notifications(float overlay_width) +void NotificationManager::render_notifications(GLCanvas3D& canvas, float overlay_width) { sort_notifications(); - - GLCanvas3D& canvas = *wxGetApp().plater()->get_current_canvas3D(); + float last_y = 0.0f; for (const auto& notification : m_pop_notifications) { @@ -1059,7 +1350,49 @@ void NotificationManager::render_notifications(float overlay_width) } } - update_notifications(); + m_last_render = GLCanvas3D::timestamp_now(); +} + +bool NotificationManager::update_notifications(GLCanvas3D& canvas) +{ + // no update if not top window + wxWindow* p = dynamic_cast(wxGetApp().plater()); + while (p->GetParent() != nullptr) + p = p->GetParent(); + wxTopLevelWindow* top_level_wnd = dynamic_cast(p); + if (!top_level_wnd->IsActive()) + return false; + + // next_render() returns numeric_limits::max if no need for frame + const int64_t max = std::numeric_limits::max(); + int64_t next_render = max; + const int64_t time_since_render = GLCanvas3D::timestamp_now() - m_last_render; + bool request_render = false; + // During render, each notification detects if its currently hovered and changes its state to EState::Hovered + // If any notification is hovered, all restarts its countdown + bool hover = false; + for (const std::unique_ptr& notification : m_pop_notifications) { + if (notification->is_hovered()) { + hover = true; + break; + } + } + // update state of all notif and erase finished + for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end();) { + std::unique_ptr& notification = *it; + request_render |= notification->update_state(hover, time_since_render); + next_render = std::min(next_render, notification->next_render()); + if (notification->get_state() == PopNotification::EState::Finished) + it = m_pop_notifications.erase(it); + else + ++it; + } + + // request next frame in future + if (next_render < max) + canvas.schedule_extra_frame(int(next_render)); + + return request_render; } void NotificationManager::sort_notifications() @@ -1080,9 +1413,11 @@ bool NotificationManager::activate_existing(const NotificationManager::PopNotifi const std::string &new_text = notification->get_data().text1; for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end(); ++it) { if ((*it)->get_type() == new_type && !(*it)->is_finished()) { - if (new_type == NotificationType::CustomNotification || new_type == NotificationType::PlaterWarning) { - if (!(*it)->compare_text(new_text)) + if (std::find(m_multiple_types.begin(), m_multiple_types.end(), new_type) != m_multiple_types.end()) { + // If found same type and same text, return true - update will be performed on the old notif + if ((*it)->compare_text(new_text) == false) { continue; + } } else if (new_type == NotificationType::SlicingWarning) { auto w1 = dynamic_cast(notification); auto w2 = dynamic_cast(it->get()); @@ -1094,7 +1429,6 @@ bool NotificationManager::activate_existing(const NotificationManager::PopNotifi continue; } } - if (it != m_pop_notifications.end() - 1) std::rotate(it, it + 1, m_pop_notifications.end()); return true; @@ -1108,107 +1442,12 @@ void NotificationManager::set_in_preview(bool preview) m_in_preview = preview; for (std::unique_ptr ¬ification : m_pop_notifications) { if (notification->get_type() == NotificationType::PlaterWarning) - notification->hide(preview); + notification->hide(preview); + if (notification->get_type() == NotificationType::SignDetected) + notification->hide(!preview); } } -void NotificationManager::update_notifications() -{ - // no update if not top window - wxWindow* p = dynamic_cast(wxGetApp().plater()); - while (p->GetParent() != nullptr) - p = p->GetParent(); - wxTopLevelWindow* top_level_wnd = dynamic_cast(p); - if (!top_level_wnd->IsActive()) - return; - - //static size_t last_size = m_pop_notifications.size(); - - //request frames - int64_t next_render = std::numeric_limits::max(); - for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end();) { - std::unique_ptr& notification = *it; - notification->set_paused(m_hovered); - notification->update_state(); - next_render = std::min(next_render, notification->next_render()); - if (notification->get_state() == PopNotification::EState::Finished) - it = m_pop_notifications.erase(it); - else { - - ++it; - } - } - /* - m_requires_update = false; - for (const std::unique_ptr& notification : m_pop_notifications) { - if (notification->requires_update()) { - m_requires_update = true; - break; - } - } - */ - // update hovering state - m_hovered = false; - for (const std::unique_ptr& notification : m_pop_notifications) { - if (notification->is_hovered()) { - m_hovered = true; - break; - } - } - - /* - // Reuire render if some notification was just deleted. - size_t curr_size = m_pop_notifications.size(); - m_requires_render = m_hovered || (last_size != curr_size); - last_size = curr_size; - - // Ask notification if it needs render - if (!m_requires_render) { - for (const std::unique_ptr& notification : m_pop_notifications) { - if (notification->requires_render()) { - m_requires_render = true; - break; - } - } - } - // Make sure there will be update after last notification erased - if (m_requires_render) - m_requires_update = true; - */ - - - if (next_render == 0) - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame_delayed(33); //few milliseconds to get from GLCanvas::render - else if (next_render < std::numeric_limits::max()) - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame_delayed(int(next_render)); - - /* - // actualizate timers - wxWindow* p = dynamic_cast(wxGetApp().plater()); - while (p->GetParent() != nullptr) - p = p->GetParent(); - wxTopLevelWindow* top_level_wnd = dynamic_cast(p); - if (!top_level_wnd->IsActive()) - return; - - { - // Control the fade-out. - // time in seconds - long now = wxGetLocalTime(); - // Pausing fade-out when the mouse is over some notification. - if (!m_hovered && m_last_time < now) { - if (now - m_last_time >= MAX_TIMEOUT_SECONDS) { - for (auto& notification : m_pop_notifications) { - //if (notification->get_state() != PopNotification::EState::Static) - notification->substract_remaining_time(MAX_TIMEOUT_SECONDS); - } - m_last_time = now; - } - } - } - */ -} - bool NotificationManager::has_slicing_error_notification() { return std::any_of(m_pop_notifications.begin(), m_pop_notifications.end(), [](auto &n) { diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 31dace42e5..17657948e6 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -1,6 +1,9 @@ #ifndef slic3r_GUI_NotificationManager_hpp_ #define slic3r_GUI_NotificationManager_hpp_ +#include "GUI_App.hpp" +#include "Plater.hpp" +#include "GLCanvas3D.hpp" #include "Event.hpp" #include "I18N.hpp" @@ -66,12 +69,20 @@ enum class NotificationType PlaterWarning, // Progress bar instead of text. ProgressBar, + // Progress bar with info from Print Host Upload Queue dialog. + PrintHostUpload, // Notification, when Color Change G-code is empty and user try to add color change on DoubleSlider. EmptyColorChangeCode, // Notification that custom supports/seams were deleted after mesh repair. CustomSupportsAndSeamRemovedAfterRepair, // Notification that auto adding of color changes is impossible - EmptyAutoColorChange + EmptyAutoColorChange, + // Notification about detected sign + SignDetected, + // Notification emitted by Print::validate + PrintValidateWarning, + // Notification telling user to quit SLA supports manual editing + QuitSLAManualMode }; class NotificationManager @@ -114,6 +125,7 @@ public: // void set_slicing_warning_gray(const std::string& text, bool g); // immediately stops showing slicing errors void close_slicing_errors_and_warnings(); + void close_slicing_error_notification(const std::string& text); // Release those slicing warnings, which refer to an ObjectID, which is not in the list. // living_oids is expected to be sorted. void remove_slicing_warnings_of_released_objects(const std::vector& living_oids); @@ -136,21 +148,26 @@ public: // Exporting finished, show this information with path, button to open containing folder and if ejectable - eject button void push_exporting_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable); // notification with progress bar - void push_progress_bar_notification(const std::string& text, float percentage = 0); - void set_progress_bar_percentage(const std::string& text, float percentage); + void push_upload_job_notification(int id, float filesize, const std::string& filename, const std::string& host, float percentage = 0); + void set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage); + void upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host); + void upload_job_notification_show_error(int id, const std::string& filename, const std::string& host); // Close old notification ExportFinished. void new_export_began(bool on_removable); // finds ExportFinished notification and closes it if it was to removable device void device_ejected(); // renders notifications in queue and deletes expired ones - void render_notifications(float overlay_width); + void render_notifications(GLCanvas3D& canvas, float overlay_width); // finds and closes all notifications of given type void close_notification_of_type(const NotificationType type); - // Which view is active? Plater or G-code preview? Hide warnings in G-code preview. + // Hides warnings in G-code preview. Should be called from plater only when 3d view/ preview is changed void set_in_preview(bool preview); + // Calls set_in_preview to apply appearing or disappearing of some notificatons; + void apply_in_preview() { set_in_preview(m_in_preview); } // Move to left to avoid colision with variable layer height gizmo. void set_move_from_overlay(bool move) { m_move_from_overlay = move; } - + // perform update_state on each notification and ask for more frames if needed, return true for render needed + bool update_notifications(GLCanvas3D& canvas); private: // duration 0 means not disapearing struct NotificationData { @@ -188,23 +205,25 @@ private: enum class EState { - Unknown, + Unknown, // NOT initialized Hidden, - FadingOutRender, // Requesting Render - FadingOutStatic, + Shown, // Requesting Render at some time if duration != 0 + NotFading, // Never jumps to state Fading out even if duration says so + FadingOut, // Requesting Render at some time ClosePending, // Requesting Render Finished, // Requesting Render + Hovered, // Followed by Shown + Paused }; PopNotification(const NotificationData &n, NotificationIDProvider &id_provider, wxEvtHandler* evt_handler); virtual ~PopNotification() { if (m_id) m_id_provider.release_id(m_id); } - void render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width); + virtual void render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width); // close will dissapear notification on next render - void close() { m_close_pending = true; } + virtual void close() { m_state = EState::ClosePending; wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0);} // data from newer notification of same type void update(const NotificationData& n); - bool is_finished() const { return m_finished || m_close_pending; } - bool is_hovered() const { return m_hovered; } + bool is_finished() const { return m_state == EState::ClosePending || m_state == EState::Finished; } // returns top after movement float get_top() const { return m_top_y; } //returns top in actual frame @@ -212,25 +231,18 @@ private: const NotificationType get_type() const { return m_data.type; } const NotificationData get_data() const { return m_data; } const bool is_gray() const { return m_is_gray; } - // Call equals one second down - void substract_remaining_time(int seconds) { m_remaining_time -= seconds; } void set_gray(bool g) { m_is_gray = g; } - void set_paused(bool p) { m_paused = p; } - bool compare_text(const std::string& text); - void hide(bool h) { m_hidden = h; } - // sets m_next_render with time of next mandatory rendering - void update_state(); - int64_t next_render() const { return m_next_render; } - /* - bool requires_render() const { return m_state == EState::FadingOutRender || m_state == EState::ClosePending || m_state == EState::Finished; } - bool requires_update() const { return m_state != EState::Hidden; } - */ - EState get_state() const { return m_state; } + virtual bool compare_text(const std::string& text) const; + void hide(bool h) { if (is_finished()) return; m_state = h ? EState::Hidden : EState::Unknown; } + // sets m_next_render with time of next mandatory rendering. Delta is time since last render. + bool update_state(bool paused, const int64_t delta); + int64_t next_render() const { return is_finished() ? 0 : m_next_render; } + EState get_state() const { return m_state; } + bool is_hovered() const { return m_state == EState::Hovered; } + void set_hovered() { if (m_state != EState::Finished && m_state != EState::ClosePending && m_state != EState::Hidden && m_state != EState::Unknown) m_state = EState::Hovered; } protected: // Call after every size change - void init(); - // Part of init() - virtual void count_spaces(); + virtual void init(); // Calculetes correct size but not se it in imgui! virtual void set_next_window_size(ImGuiWrapper& imgui); virtual void render_text(ImGuiWrapper& imgui, @@ -244,51 +256,49 @@ private: const std::string text, bool more = false); // Left sign could be error or warning sign - void render_left_sign(ImGuiWrapper& imgui); + virtual void render_left_sign(ImGuiWrapper& imgui); virtual void render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y); // Hypertext action, returns true if notification should close. // Action is stored in NotificationData::callback as std::function virtual bool on_text_click(); + + // Part of init(), counts horizontal spacing like left indentation + virtual void count_spaces(); + // Part of init(), counts end lines + virtual void count_lines(); + // returns true if PopStyleColor should be called later to pop this push + virtual bool push_background_color(); const NotificationData m_data; - // For reusing ImGUI windows. NotificationIDProvider &m_id_provider; + int m_id{ 0 }; + // State for rendering EState m_state { EState::Unknown }; - int m_id { 0 }; - bool m_initialized { false }; + // Time values for rendering fade-out + + int64_t m_fading_start{ 0LL }; + + // first appereance of notification or last hover; + int64_t m_notification_start; + // time to next must-do render + int64_t m_next_render{ std::numeric_limits::max() }; + float m_current_fade_opacity{ 1.0f }; + + // Notification data + // Main text std::string m_text1; // Clickable text std::string m_hypertext; // Aditional text after hypertext - currently not used std::string m_text2; - // Countdown variables - long m_remaining_time; - bool m_counting_down; - long m_last_remaining_time; - bool m_paused { false }; - int m_countdown_frame { 0 }; - bool m_fading_out { false }; - int64_t m_fading_start { 0LL }; - // time of last done render when fading - int64_t m_last_render_fading { 0LL }; - // first appereance of notification or last hover; - int64_t m_notification_start; - // time to next must-do render - int64_t m_next_render { std::numeric_limits::max() }; - float m_current_fade_opacity { 1.0f }; - // If hidden the notif is alive but not visible to user - bool m_hidden { false }; - // m_finished = true - does not render, marked to delete - bool m_finished { false }; - // Will go to m_finished next render - bool m_close_pending { false }; - bool m_hovered { false }; - // variables to count positions correctly + + // inner variables to position notification window, texts and buttons correctly + // all space without text float m_window_width_offset; // Space on left side without text @@ -298,9 +308,7 @@ private: float m_window_width { 450.0f }; //Distance from bottom of notifications to top of this notification float m_top_y { 0.0f }; - - // Height of text - // Used as basic scaling unit! + // Height of text - Used as basic scaling unit! float m_line_height; std::vector m_endlines; // Gray are f.e. eorrors when its uknown if they are still valid @@ -318,10 +326,16 @@ private: { public: SlicingCompleteLargeNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool largeds); - void set_large(bool l); - bool get_large() { return m_is_large; } - - void set_print_info(const std::string &info); + void set_large(bool l); + bool get_large() { return m_is_large; } + void set_print_info(const std::string &info); + virtual void render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width) override + { + // This notification is always hidden if !large (means side bar is collapsed) + if (!get_large() && !is_finished()) + m_state = EState::Hidden; + PopNotification::render(canvas, initial_y, move_from_overlay, overlay_width); + } protected: virtual void render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, @@ -340,21 +354,87 @@ private: int warning_step; }; + class PlaterWarningNotification : public PopNotification + { + public: + PlaterWarningNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler) : PopNotification(n, id_provider, evt_handler) {} + virtual void close() override { if(is_finished()) return; m_state = EState::Hidden; wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); } + void real_close() { m_state = EState::ClosePending; wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); } + void show() { m_state = EState::Unknown; } + }; + + class ProgressBarNotification : public PopNotification { public: + ProgressBarNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage) : PopNotification(n, id_provider, evt_handler) { set_percentage(percentage); } - void set_percentage(float percent) { m_percentage = percent; if (percent >= 1.0f) m_progress_complete = true; else m_progress_complete = false; } + virtual void set_percentage(float percent) { m_percentage = percent; } protected: - virtual void init(); + virtual void init() override; + virtual void count_lines() override; + virtual void render_text(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, - const float win_pos_x, const float win_pos_y); - void render_bar(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, - const float win_pos_x, const float win_pos_y); - bool m_progress_complete{ false }; - float m_percentage; + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) override; + virtual void render_bar(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) ; + virtual void render_cancel_button(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) + {} + virtual void render_minimize_button(ImGuiWrapper& imgui, + const float win_pos_x, const float win_pos_y) override {} + float m_percentage; + + bool m_has_cancel_button {false}; + // local time of last hover for showing tooltip + + }; + + + + class PrintHostUploadNotification : public ProgressBarNotification + { + public: + enum class UploadJobState + { + PB_PROGRESS, + PB_ERROR, + PB_CANCELLED, + PB_COMPLETED + }; + PrintHostUploadNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage, int job_id, float filesize) + :ProgressBarNotification(n, id_provider, evt_handler, percentage) + , m_job_id(job_id) + , m_file_size(filesize) + { + m_has_cancel_button = true; + } + static std::string get_upload_job_text(int id, const std::string& filename, const std::string& host) { return /*"[" + std::to_string(id) + "] " + */filename + " -> " + host; } + virtual void set_percentage(float percent) override; + void cancel() { m_uj_state = UploadJobState::PB_CANCELLED; m_has_cancel_button = false; } + void error() { m_uj_state = UploadJobState::PB_ERROR; m_has_cancel_button = false; init(); } + bool compare_job_id(const int other_id) const { return m_job_id == other_id; } + virtual bool compare_text(const std::string& text) const override { return false; } + protected: + virtual void init() override; + virtual void count_spaces() override; + virtual bool push_background_color() override; + virtual void render_bar(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) override; + virtual void render_cancel_button(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) override; + virtual void render_left_sign(ImGuiWrapper& imgui) override; + // Identifies job in cancel callback + int m_job_id; + // Size of uploaded size to be displayed in MB + float m_file_size; + long m_hover_time{ 0 }; + UploadJobState m_uj_state{ UploadJobState::PB_PROGRESS }; }; class ExportFinishedNotification : public PopNotification @@ -401,32 +481,25 @@ private: void sort_notifications(); // If there is some error notification active, then the "Export G-code" notification after the slicing is finished is suppressed. bool has_slicing_error_notification(); - // perform update_state on each notification and ask for more frames if needed - void update_notifications(); - + // Target for wxWidgets events sent by clicking on the hyperlink available at some notifications. wxEvtHandler* m_evt_handler; // Cache of IDs to identify and reuse ImGUI windows. NotificationIDProvider m_id_provider; std::deque> m_pop_notifications; - // Last render time in seconds for fade out control. - long m_last_time { 0 }; - // When mouse hovers over some notification, the fade-out of all notifications is suppressed. - bool m_hovered { false }; //timestamps used for slicing finished - notification could be gone so it needs to be stored here std::unordered_set m_used_timestamps; // True if G-code preview is active. False if the Plater is active. bool m_in_preview { false }; // True if the layer editing is enabled in Plater, so that the notifications are shifted left of it. bool m_move_from_overlay { false }; - + // Timestamp of last rendering + int64_t m_last_render { 0LL }; + // Notification types that can be shown multiple types at once (compared by text) + const std::vector m_multiple_types = { NotificationType::CustomNotification, NotificationType::PlaterWarning, NotificationType::ProgressBar, NotificationType::PrintHostUpload }; //prepared (basic) notifications const std::vector basic_notifications = { -// {NotificationType::SlicingNotPossible, NotificationLevel::RegularNotification, 10, _u8L("Slicing is not possible.")}, -// {NotificationType::ExportToRemovableFinished, NotificationLevel::ImportantNotification, 0, _u8L("Exporting finished."), _u8L("Eject drive.") }, {NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotification, 10, _u8L("3D Mouse disconnected.") }, -// {NotificationType::Mouse3dConnected, NotificationLevel::RegularNotification, 5, _u8L("3D Mouse connected.") }, -// {NotificationType::NewPresetsAviable, NotificationLevel::ImportantNotification, 20, _u8L("New Presets are available."), _u8L("See here.") }, {NotificationType::PresetUpdateAvailable, NotificationLevel::ImportantNotification, 20, _u8L("Configuration update is available."), _u8L("See more."), [](wxEvtHandler* evnthndlr) { if (evnthndlr != nullptr) diff --git a/src/slic3r/GUI/OG_CustomCtrl.cpp b/src/slic3r/GUI/OG_CustomCtrl.cpp index 6433bf2d13..aba3404e73 100644 --- a/src/slic3r/GUI/OG_CustomCtrl.cpp +++ b/src/slic3r/GUI/OG_CustomCtrl.cpp @@ -91,7 +91,7 @@ void OG_CustomCtrl::init_ctrl_lines() height = m_bmp_blinking_sz.GetHeight() + m_v_gap; ctrl_lines.emplace_back(CtrlLine(height, this, line, true)); } - else if (opt_group->label_width != 0 && (!line.label.IsEmpty() || option_set.front().opt.gui_type == "legend") ) + else if (opt_group->label_width != 0 && (!line.label.IsEmpty() || option_set.front().opt.gui_type == ConfigOptionDef::GUIType::legend) ) { wxSize label_sz = GetTextExtent(line.label); height = label_sz.y * (label_sz.GetWidth() > int(opt_group->label_width * m_em_unit) ? 2 : 1) + m_v_gap; @@ -186,11 +186,11 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/) #endif //__WXMSW__ h_pos += label_w + 1 + m_h_gap; } - h_pos += (opt.opt.gui_type == "legend" ? 1 : 3) * blinking_button_width; + h_pos += (opt.opt.gui_type == ConfigOptionDef::GUIType::legend ? 1 : 3) * blinking_button_width; if (field == field_in) break; - if (opt.opt.gui_type == "legend") + if (opt.opt.gui_type == ConfigOptionDef::GUIType::legend) h_pos += 2 * blinking_button_width; h_pos += field->getWindow()->GetSize().x; @@ -580,7 +580,7 @@ wxCoord OG_CustomCtrl::CtrlLine::draw_mode_bmp(wxDC& dc, wxCoord v_pos) wxBitmap bmp = create_scaled_bitmap(bmp_name, ctrl, wxOSX ? 10 : 12); wxCoord y_draw = v_pos + lround((height - get_bitmap_size(bmp).GetHeight()) / 2); - if (og_line.get_options().front().opt.gui_type != "legend") + if (og_line.get_options().front().opt.gui_type != ConfigOptionDef::GUIType::legend) dc.DrawBitmap(bmp, 0, y_draw); return get_bitmap_size(bmp).GetWidth() + ctrl->m_h_gap; diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 7de37fb48c..49c75f9f2f 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -2,7 +2,7 @@ #include "wxExtensions.hpp" #include "BitmapCache.hpp" #include "GUI_App.hpp" -#include "GUI_ObjectList.hpp" +#include "GUI_Factories.hpp" #include "I18N.hpp" #include "libslic3r/Model.hpp" @@ -44,8 +44,22 @@ void ObjectDataViewModelNode::init_container() #endif //__WXGTK__ } -#define LAYER_ROOT_ICON "edit_layers_all" -#define LAYER_ICON "edit_layers_some" +static constexpr char LayerRootIcon[] = "edit_layers_all"; +static constexpr char LayerIcon[] = "edit_layers_some"; +static constexpr char WarningIcon[] = "exclamation"; +static constexpr char InfoIcon[] = "info"; + +ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const InfoItemType info_type) : + m_parent(parent), + m_type(itInfo), + m_extruder(wxEmptyString) +{ + m_name = info_type == InfoItemType::CustomSupports ? _L("Paint-on supports") + : info_type == InfoItemType::CustomSeam ? _L("Paint-on seam") + : _L("Variable layer height"); + m_info_item_type = info_type; +} + ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const ItemType type) : m_parent(parent), @@ -65,9 +79,11 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent } else if (type == itLayerRoot) { - m_bmp = create_scaled_bitmap(LAYER_ROOT_ICON); // FIXME: pass window ptr + m_bmp = create_scaled_bitmap(LayerRootIcon); // FIXME: pass window ptr m_name = _(L("Layers")); } + else if (type == itInfo) + assert(false); if (type & (itInstanceRoot | itLayerRoot)) init_container(); @@ -94,7 +110,7 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent } 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(LAYER_ICON); // FIXME: pass window ptr + m_bmp = create_scaled_bitmap(LayerIcon); // FIXME: pass window ptr set_action_and_extruder_icons(); init_container(); @@ -121,7 +137,7 @@ void ObjectDataViewModelNode::set_action_and_extruder_icons() void ObjectDataViewModelNode::set_extruder_icon() { - if (m_type & itInstance) + if (m_type & (itInstance | itInstanceRoot | itLayerRoot)) return; // don't set colored bitmap for Instance int extruder_idx = atoi(m_extruder.c_str()); @@ -140,17 +156,14 @@ void ObjectDataViewModelNode::update_settings_digest_bitmaps() { m_bmp = m_empty_bmp; - std::map& categories_icon = Slic3r::GUI::wxGetApp().obj_list()->CATEGORY_ICON; - std::string scaled_bitmap_name = m_name.ToUTF8().data(); scaled_bitmap_name += "-em" + std::to_string(wxGetApp().em_unit()) + (wxGetApp().dark_mode() ? "-dm" : ""); wxBitmap *bmp = m_bitmap_cache->find(scaled_bitmap_name); if (bmp == nullptr) { std::vector bmps; - for (auto& cat : m_opt_categories) - bmps.emplace_back( categories_icon.find(cat) == categories_icon.end() ? - wxNullBitmap : categories_icon.at(cat)); + for (auto& category : m_opt_categories) + bmps.emplace_back(SettingsFactory::get_category_bitmap(category)); bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps); } @@ -249,6 +262,10 @@ static int get_root_idx(ObjectDataViewModelNode *parent_node, const ItemType roo ObjectDataViewModel::ObjectDataViewModel() { m_bitmap_cache = new Slic3r::GUI::BitmapCache; + + m_volume_bmps = MenuFactory::get_volume_bitmaps(); + m_warning_bmp = create_scaled_bitmap(WarningIcon); + m_info_bmp = create_scaled_bitmap(InfoIcon); } ObjectDataViewModel::~ObjectDataViewModel() @@ -267,7 +284,7 @@ wxDataViewItem ObjectDataViewModel::Add(const wxString &name, auto root = new ObjectDataViewModelNode(name, extruder_str); // Add error icon if detected auto-repaire if (has_errors) - root->m_bmp = *m_warning_bmp; + root->m_bmp = m_warning_bmp; m_objects.push_back(root); // notify control @@ -317,7 +334,7 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent // if part with errors is added, but object wasn't marked, then mark it if (!obj_errors && has_errors) - root->SetBitmap(*m_warning_bmp); + root->SetBitmap(m_warning_bmp); // notify control const wxDataViewItem child((void*)node); @@ -329,12 +346,37 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent return child; } +wxDataViewItem ObjectDataViewModel::AddInfoChild(const wxDataViewItem &parent_item, InfoItemType info_type) +{ + ObjectDataViewModelNode *root = static_cast(parent_item.GetID()); + if (!root) return wxDataViewItem(0); + + const auto node = new ObjectDataViewModelNode(root, info_type); + + // The new item should be added according to its order in InfoItemType. + // Find last info item with lower index and append after it. + const auto& children = root->GetChildren(); + int idx = -1; + for (int i=0; iGetType() == itInfo && int(children[i]->GetInfoItemType()) < int(info_type) ) + idx = i; + } + + root->Insert(node, idx+1); + node->SetBitmap(m_info_bmp); + // notify control + const wxDataViewItem child((void*)node); + ItemAdded(parent_item, child); + return child; +} + wxDataViewItem ObjectDataViewModel::AddSettingsChild(const wxDataViewItem &parent_item) { ObjectDataViewModelNode *root = static_cast(parent_item.GetID()); if (!root) return wxDataViewItem(0); const auto node = new ObjectDataViewModelNode(root, itSettings); + root->Insert(node, 0); // notify control const wxDataViewItem child((void*)node); @@ -1378,6 +1420,14 @@ ItemType ObjectDataViewModel::GetItemType(const wxDataViewItem &item) const return node->m_type < 0 ? itUndef : node->m_type; } +InfoItemType ObjectDataViewModel::GetInfoItemType(const wxDataViewItem &item) const +{ + if (!item.IsOk()) + return InfoItemType::Undef; + ObjectDataViewModelNode *node = static_cast(item.GetID()); + return node->m_info_item_type; +} + wxDataViewItem ObjectDataViewModel::GetItemByType(const wxDataViewItem &parent_item, ItemType type) const { if (!parent_item.IsOk()) @@ -1410,6 +1460,21 @@ wxDataViewItem ObjectDataViewModel::GetLayerRootItem(const wxDataViewItem &item) return GetItemByType(item, itLayerRoot); } +wxDataViewItem ObjectDataViewModel::GetInfoItemByType(const wxDataViewItem &parent_item, InfoItemType type) const +{ + if (! parent_item.IsOk()) + return wxDataViewItem(0); + + ObjectDataViewModelNode *node = static_cast(parent_item.GetID()); + for (size_t i = 0; i < node->GetChildCount(); i++) { + const ObjectDataViewModelNode* child_node = node->GetNthChild(i); + if (child_node->m_type == itInfo && child_node->m_info_item_type == type) + return wxDataViewItem((void*)child_node); + } + + return wxDataViewItem(0); // not found +} + bool ObjectDataViewModel::IsSettingsItem(const wxDataViewItem &item) const { if (!item.IsOk()) @@ -1434,10 +1499,20 @@ void ObjectDataViewModel::SetVolumeType(const wxDataViewItem &item, const Slic3r return; ObjectDataViewModelNode *node = static_cast(item.GetID()); - node->SetBitmap(*m_volume_bmps[int(type)]); + node->SetVolumeType(type); + node->SetBitmap(m_volume_bmps[int(type)]); ItemChanged(item); } +ModelVolumeType ObjectDataViewModel::GetVolumeType(const wxDataViewItem& item) +{ + if (!item.IsOk() || GetItemType(item) != itVolume) + return ModelVolumeType::INVALID; + + ObjectDataViewModelNode *node = static_cast(item.GetID()); + return node->GetVolumeType(); +} + wxDataViewItem ObjectDataViewModel::SetPrintableState( PrintIndicator printable, int obj_idx, @@ -1480,6 +1555,9 @@ wxDataViewItem ObjectDataViewModel::SetObjectPrintableState( void ObjectDataViewModel::Rescale() { + m_volume_bmps = MenuFactory::get_volume_bitmaps(); + m_warning_bmp = create_scaled_bitmap(WarningIcon); + wxDataViewItemArray all_items; GetAllChildren(wxDataViewItem(0), all_items); @@ -1494,15 +1572,15 @@ void ObjectDataViewModel::Rescale() switch (node->m_type) { case itObject: - if (node->m_bmp.IsOk()) node->m_bmp = *m_warning_bmp; + if (node->m_bmp.IsOk()) node->m_bmp = m_warning_bmp; break; case itVolume: node->m_bmp = GetVolumeIcon(node->m_volume_type, node->m_bmp.GetWidth() != node->m_bmp.GetHeight()); break; case itLayerRoot: - node->m_bmp = create_scaled_bitmap(LAYER_ROOT_ICON); + node->m_bmp = create_scaled_bitmap(LayerRootIcon); case itLayer: - node->m_bmp = create_scaled_bitmap(LAYER_ICON); + node->m_bmp = create_scaled_bitmap(LayerIcon); break; default: break; } @@ -1514,7 +1592,7 @@ void ObjectDataViewModel::Rescale() wxBitmap ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, const bool is_marked/* = false*/) { if (!is_marked) - return *m_volume_bmps[static_cast(vol_type)]; + return m_volume_bmps[static_cast(vol_type)]; std::string scaled_bitmap_name = "warning" + std::to_string(static_cast(vol_type)); scaled_bitmap_name += "-em" + std::to_string(Slic3r::GUI::wxGetApp().em_unit()); @@ -1523,8 +1601,8 @@ wxBitmap ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_ty if (bmp == nullptr) { std::vector bmps; - bmps.emplace_back(*m_warning_bmp); - bmps.emplace_back(*m_volume_bmps[static_cast(vol_type)]); + bmps.emplace_back(m_warning_bmp); + bmps.emplace_back(m_volume_bmps[static_cast(vol_type)]); bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps); } @@ -1543,7 +1621,7 @@ void ObjectDataViewModel::DeleteWarningIcon(const wxDataViewItem& item, const bo return; if (node->GetType() & itVolume) { - node->SetBitmap(*m_volume_bmps[static_cast(node->volume_type())]); + node->SetBitmap(m_volume_bmps[static_cast(node->volume_type())]); return; } diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index 17ad2047fc..1dd41bb107 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -27,6 +27,7 @@ enum ItemType { itSettings = 16, itLayerRoot = 32, itLayer = 64, + itInfo = 128 }; enum ColumnNumber @@ -44,6 +45,14 @@ enum PrintIndicator piUnprintable , // unprintable }; +enum class InfoItemType +{ + Undef, + CustomSupports, + CustomSeam, + VariableLayerHeight +}; + class ObjectDataViewModelNode; WX_DEFINE_ARRAY_PTR(ObjectDataViewModelNode*, MyObjectTreeModelNodePtrArray); @@ -69,6 +78,7 @@ class ObjectDataViewModelNode std::string m_action_icon_name = ""; ModelVolumeType m_volume_type; + InfoItemType m_info_item_type {InfoItemType::Undef}; public: ObjectDataViewModelNode(const wxString& name, @@ -104,6 +114,7 @@ public: const wxString& extruder = wxEmptyString ); ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const ItemType type); + ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const InfoItemType type); ~ObjectDataViewModelNode() { @@ -171,13 +182,15 @@ public: } bool SetValue(const wxVariant &variant, unsigned int col); - + void SetVolumeType(ModelVolumeType type) { m_volume_type = type; } 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; } + InfoItemType GetInfoItemType() const { return m_info_item_type; } void SetIdx(const int& idx); int GetIdx() const { return m_idx; } + ModelVolumeType GetVolumeType() { return m_volume_type; } t_layer_height_range GetLayerRange() const { return m_layer_range; } PrintIndicator IsPrintable() const { return m_printable; } @@ -241,8 +254,9 @@ wxDECLARE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent); class ObjectDataViewModel :public wxDataViewModel { std::vector m_objects; - std::vector m_volume_bmps; - wxBitmap* m_warning_bmp { nullptr }; + std::vector m_volume_bmps; + wxBitmap m_warning_bmp; + wxBitmap m_info_bmp; wxDataViewCtrl* m_ctrl { nullptr }; @@ -260,6 +274,7 @@ public: const int extruder = 0, const bool create_frst_child = true); wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); + wxDataViewItem AddInfoChild(const wxDataViewItem &parent_item, InfoItemType info_type); wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num); wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, const std::vector& print_indicator); wxDataViewItem AddLayersRoot(const wxDataViewItem &parent_item); @@ -334,12 +349,15 @@ public: // In our case it is an item with all columns bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; } - ItemType GetItemType(const wxDataViewItem &item) const ; + ItemType GetItemType(const wxDataViewItem &item) const; + InfoItemType GetInfoItemType(const wxDataViewItem &item) const; 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; + wxDataViewItem GetInfoItemByType(const wxDataViewItem &parent_item, InfoItemType type) const; + bool IsSettingsItem(const wxDataViewItem &item) const; void UpdateSettingsDigest( const wxDataViewItem &item, const std::vector& categories); @@ -348,9 +366,8 @@ public: 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); + ModelVolumeType GetVolumeType(const wxDataViewItem &item); wxDataViewItem SetPrintableState( PrintIndicator printable, int obj_idx, int subobj_idx = -1, ItemType subobj_type = itInstance); diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index 0fa97bfc11..91f1f1f0b6 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -5,6 +5,8 @@ #include "I18N.hpp" #include "3DScene.hpp" +#include "libslic3r/Platform.hpp" + #include #include @@ -88,22 +90,24 @@ float OpenGLManager::GLInfo::get_max_anisotropy() const void OpenGLManager::GLInfo::detect() const { - m_version = gl_get_string_safe(GL_VERSION, "N/A"); - m_glsl_version = gl_get_string_safe(GL_SHADING_LANGUAGE_VERSION, "N/A"); - m_vendor = gl_get_string_safe(GL_VENDOR, "N/A"); - m_renderer = gl_get_string_safe(GL_RENDERER, "N/A"); + *const_cast(&m_version) = gl_get_string_safe(GL_VERSION, "N/A"); + *const_cast(&m_glsl_version) = gl_get_string_safe(GL_SHADING_LANGUAGE_VERSION, "N/A"); + *const_cast(&m_vendor) = gl_get_string_safe(GL_VENDOR, "N/A"); + *const_cast(&m_renderer) = gl_get_string_safe(GL_RENDERER, "N/A"); - glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_max_tex_size)); + int* max_tex_size = const_cast(&m_max_tex_size); + glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, max_tex_size)); - m_max_tex_size /= 2; + *max_tex_size /= 2; if (Slic3r::total_physical_memory() / (1024 * 1024 * 1024) < 6) - m_max_tex_size /= 2; + *max_tex_size /= 2; - if (GLEW_EXT_texture_filter_anisotropic) - glsafe(::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &m_max_anisotropy)); - - m_detected = true; + if (GLEW_EXT_texture_filter_anisotropic) { + float* max_anisotropy = const_cast(&m_max_anisotropy); + glsafe(::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy)); + } + *const_cast(&m_detected) = true; } static bool version_greater_or_equal_to(const std::string& version, unsigned int major, unsigned int minor) @@ -172,19 +176,16 @@ std::string OpenGLManager::GLInfo::to_string(bool format_as_html, bool extension out << b_start << "Renderer: " << b_end << m_renderer << line_end; out << b_start << "GLSL version: " << b_end << m_glsl_version << line_end; - if (extensions) - { + if (extensions) { std::vector extensions_list; std::string extensions_str = gl_get_string_safe(GL_EXTENSIONS, ""); boost::split(extensions_list, extensions_str, boost::is_any_of(" "), boost::token_compress_off); - if (!extensions_list.empty()) - { + if (!extensions_list.empty()) { out << h2_start << "Installed extensions:" << h2_end << line_end; std::sort(extensions_list.begin(), extensions_list.end()); - for (const std::string& ext : extensions_list) - { + for (const std::string& ext : extensions_list) { out << ext << line_end; } } @@ -250,7 +251,7 @@ bool OpenGLManager::init_gl() 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."); + 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); } @@ -302,8 +303,7 @@ wxGLCanvas* OpenGLManager::create_wxglcanvas(wxWindow& parent) 0 }; - if (s_multisample == EMultisampleState::Unknown) - { + if (s_multisample == EMultisampleState::Unknown) { detect_multisample(attribList); // // debug output // std::cout << "Multisample " << (can_multisample() ? "enabled" : "disabled") << std::endl; @@ -319,7 +319,13 @@ void OpenGLManager::detect_multisample(int* attribList) { int wxVersion = wxMAJOR_VERSION * 10000 + wxMINOR_VERSION * 100 + wxRELEASE_NUMBER; bool enable_multisample = wxVersion >= 30003; - s_multisample = (enable_multisample && wxGLCanvas::IsDisplaySupported(attribList)) ? EMultisampleState::Enabled : EMultisampleState::Disabled; + s_multisample = + enable_multisample && + // Disable multi-sampling on ChromeOS, as the OpenGL virtualization swaps Red/Blue channels with multi-sampling enabled, + // at least on some platforms. + platform_flavor() != PlatformFlavor::LinuxOnChromium && + wxGLCanvas::IsDisplaySupported(attribList) + ? EMultisampleState::Enabled : EMultisampleState::Disabled; // Alternative method: it was working on previous version of wxWidgets but not with the latest, at least on Windows // s_multisample = enable_multisample && wxGLCanvas::IsExtensionSupported("WGL_ARB_multisample"); } diff --git a/src/slic3r/GUI/OpenGLManager.hpp b/src/slic3r/GUI/OpenGLManager.hpp index 5f8cd7959c..ca9452db66 100644 --- a/src/slic3r/GUI/OpenGLManager.hpp +++ b/src/slic3r/GUI/OpenGLManager.hpp @@ -22,14 +22,14 @@ public: class GLInfo { - mutable bool m_detected{ false }; - mutable int m_max_tex_size{ 0 }; - mutable float m_max_anisotropy{ 0.0f }; + bool m_detected{ false }; + int m_max_tex_size{ 0 }; + float m_max_anisotropy{ 0.0f }; - mutable std::string m_version; - mutable std::string m_glsl_version; - mutable std::string m_vendor; - mutable std::string m_renderer; + std::string m_version; + std::string m_glsl_version; + std::string m_vendor; + std::string m_renderer; public: GLInfo() = default; diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index b87e2047d4..f70d678d21 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -25,23 +25,27 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id) { const t_field& OptionsGroup::build_field(const t_config_option_key& id, const ConfigOptionDef& opt) { // Check the gui_type field first, fall through // is the normal type. - if (opt.gui_type == "select") { - } else if (opt.gui_type == "select_open") { + switch (opt.gui_type) { + case ConfigOptionDef::GUIType::select_open: m_fields.emplace(id, Choice::Create(this->ctrl_parent(), opt, id)); - } else if (opt.gui_type == "color") { + break; + case ConfigOptionDef::GUIType::color: m_fields.emplace(id, ColourPicker::Create(this->ctrl_parent(), opt, id)); - } else if (opt.gui_type == "f_enum_open" || - opt.gui_type == "i_enum_open" || - opt.gui_type == "i_enum_closed") { + break; + case ConfigOptionDef::GUIType::f_enum_open: + case ConfigOptionDef::GUIType::i_enum_open: m_fields.emplace(id, Choice::Create(this->ctrl_parent(), opt, id)); - } else if (opt.gui_type == "slider") { + break; + case ConfigOptionDef::GUIType::slider: m_fields.emplace(id, SliderCtrl::Create(this->ctrl_parent(), opt, id)); - } else if (opt.gui_type == "i_spin") { // Spinctrl - } else if (opt.gui_type == "legend") { // StaticText + break; + case ConfigOptionDef::GUIType::legend: // StaticText m_fields.emplace(id, StaticText::Create(this->ctrl_parent(), opt, id)); - } else if (opt.gui_type == "one_string") { + break; + case ConfigOptionDef::GUIType::one_string: m_fields.emplace(id, TextCtrl::Create(this->ctrl_parent(), opt, id)); - } else { + break; + default: switch (opt.type) { case coFloatOrPercent: case coFloat: @@ -122,7 +126,7 @@ bool OptionsGroup::is_legend_line() { if (m_lines.size() == 1) { const std::vector