diff --git a/CMakeLists.txt b/CMakeLists.txt index fa776709ff..4db9ea5514 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -613,6 +613,7 @@ function(bambustudio_copy_dlls target config postfix output_dlls) ${CMAKE_PREFIX_PATH}/bin/occt/TKXCAF.dll ${CMAKE_PREFIX_PATH}/bin/occt/TKXDESTEP.dll ${CMAKE_PREFIX_PATH}/bin/occt/TKXSBase.dll + ${CMAKE_PREFIX_PATH}/bin/freetype.dll DESTINATION ${_out_dir}) set(${output_dlls} @@ -647,6 +648,8 @@ function(bambustudio_copy_dlls target config postfix output_dlls) ${_out_dir}/TKXDESTEP.dll ${_out_dir}/TKXSBase.dll + ${_out_dir}/freetype.dll + PARENT_SCOPE ) diff --git a/bbl/i18n/BambuStudio.pot b/bbl/i18n/BambuStudio.pot index 6b746fd6d0..41cc462563 100644 --- a/bbl/i18n/BambuStudio.pot +++ b/bbl/i18n/BambuStudio.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-08-08 20:01+0800\n" +"POT-Creation-Date: 2022-08-12 15:19+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -2238,6 +2238,9 @@ msgstr "" msgid "Preferences" msgstr "" +msgid "About" +msgstr "" + msgid "View" msgstr "" @@ -4040,6 +4043,16 @@ msgstr "" msgid "Shift+Mouse wheel" msgstr "" +msgid "Release Note" +msgstr "" + +#, possible-c-format, possible-boost-format +msgid "version %s update information :" +msgstr "" + +msgid "New version of Bambu Studio" +msgstr "" + msgid "Saving objects into the 3mf failed." msgstr "" diff --git a/bbl/i18n/de/BambuStudio_de.po b/bbl/i18n/de/BambuStudio_de.po index 29e09ac492..a47d247b74 100644 --- a/bbl/i18n/de/BambuStudio_de.po +++ b/bbl/i18n/de/BambuStudio_de.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: Bambu Studio\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-08-08 20:01+0800\n" +"POT-Creation-Date: 2022-08-12 15:19+0800\n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -2401,6 +2401,9 @@ msgstr "Orthogonale Ansicht verwenden" msgid "Preferences" msgstr "Vorlieben" +msgid "About" +msgstr "" + msgid "View" msgstr "Ansicht" @@ -4317,6 +4320,16 @@ msgstr "Schieberegler 5x schneller bewegen" msgid "Shift+Mouse wheel" msgstr "Umschalttaste+Mausrad" +msgid "Release Note" +msgstr "" + +#, c-format, boost-format +msgid "version %s update information :" +msgstr "" + +msgid "New version of Bambu Studio" +msgstr "Neue Version von Bambu Studio" + msgid "Saving objects into the 3mf failed." msgstr "Das Speichern von Objekten in der 3mf ist fehlgeschlagen." @@ -6714,9 +6727,6 @@ msgstr "Support: Verbreiten von Zweigen auf Ebene %d" #~ msgid "Monitoring" #~ msgstr "Überwachung" -#~ msgid "New version of Bambu Studio" -#~ msgstr "Neue Version von Bambu Studio" - #~ msgid "Output file" #~ msgstr "Ausgabedatei" diff --git a/bbl/i18n/en/BambuStudio_en.po b/bbl/i18n/en/BambuStudio_en.po index 7f12a2141d..f5dc875dda 100644 --- a/bbl/i18n/en/BambuStudio_en.po +++ b/bbl/i18n/en/BambuStudio_en.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: Bambu Studio\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-08-08 20:01+0800\n" +"POT-Creation-Date: 2022-08-12 15:19+0800\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: \n" @@ -2362,6 +2362,9 @@ msgstr "Use Orthogonal View" msgid "Preferences" msgstr "Preferences" +msgid "About" +msgstr "" + msgid "View" msgstr "View" @@ -4230,6 +4233,16 @@ msgstr "Move slider 5x faster" msgid "Shift+Mouse wheel" msgstr "" +msgid "Release Note" +msgstr "" + +#, c-format, boost-format +msgid "version %s update information :" +msgstr "" + +msgid "New version of Bambu Studio" +msgstr "New version of Bambu Studio" + msgid "Saving objects into the 3mf failed." msgstr "Saving objects into the 3mf failed." @@ -6590,9 +6603,6 @@ msgstr "Support: propagate branches at layer %d" #~ msgid "Module" #~ msgstr "Module" -#~ msgid "New version of Bambu Studio" -#~ msgstr "New version of Bambu Studio" - #~ msgid "Output file" #~ msgstr "Output file" diff --git a/bbl/i18n/es/BambuStudio_es.po b/bbl/i18n/es/BambuStudio_es.po index f444f3bdb0..fe5cae4c0d 100644 --- a/bbl/i18n/es/BambuStudio_es.po +++ b/bbl/i18n/es/BambuStudio_es.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: Bambu Studio\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-08-08 20:01+0800\n" +"POT-Creation-Date: 2022-08-12 15:19+0800\n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -2399,6 +2399,9 @@ msgstr "Utilizar Vista Ortogonal" msgid "Preferences" msgstr "Preferencias" +msgid "About" +msgstr "" + msgid "View" msgstr "Vista" @@ -4307,6 +4310,16 @@ msgstr "Mover el deslizador 5 veces más rápido" msgid "Shift+Mouse wheel" msgstr "" +msgid "Release Note" +msgstr "" + +#, c-format, boost-format +msgid "version %s update information :" +msgstr "" + +msgid "New version of Bambu Studio" +msgstr "Nueva versión de Bambu Studio" + msgid "Saving objects into the 3mf failed." msgstr "El guardado de objetos en el 3mf no ha funcionado." @@ -6700,9 +6713,6 @@ msgstr "Soporte: propagar ramas en la capa %d" #~ msgid "Monitoring" #~ msgstr "Monitorizando" -#~ msgid "New version of Bambu Studio" -#~ msgstr "Nueva versión de Bambu Studio" - #~ msgid "Output file" #~ msgstr "Archivo de salida" diff --git a/bbl/i18n/fr/BambuStudio_fr.po b/bbl/i18n/fr/BambuStudio_fr.po index 951483f829..dd50f7b362 100644 --- a/bbl/i18n/fr/BambuStudio_fr.po +++ b/bbl/i18n/fr/BambuStudio_fr.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: Bambu Studio\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-08-08 20:01+0800\n" +"POT-Creation-Date: 2022-08-12 15:19+0800\n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -2368,6 +2368,9 @@ msgstr "Utiliser la vue orthogonale" msgid "Preferences" msgstr "Préférences" +msgid "About" +msgstr "" + msgid "View" msgstr "Vue" @@ -4267,6 +4270,16 @@ msgstr "Déplacez le curseur 5 fois plus vite" msgid "Shift+Mouse wheel" msgstr "" +msgid "Release Note" +msgstr "" + +#, c-format, boost-format +msgid "version %s update information :" +msgstr "" + +msgid "New version of Bambu Studio" +msgstr "Nouvelle version de Bambu Studio" + msgid "Saving objects into the 3mf failed." msgstr "L'enregistrement d'objets dans le 3mf a échoué." @@ -6667,9 +6680,6 @@ msgstr "Support: propagate branches at layer %d" #~ msgid "Monitoring" #~ msgstr "Surveillance" -#~ msgid "New version of Bambu Studio" -#~ msgstr "Nouvelle version de Bambu Studio" - #~ msgid "Output file" #~ msgstr "Fichier de sortie" diff --git a/bbl/i18n/hu/BambuStudio_hu.po b/bbl/i18n/hu/BambuStudio_hu.po index 029ba742d8..d03679417b 100644 --- a/bbl/i18n/hu/BambuStudio_hu.po +++ b/bbl/i18n/hu/BambuStudio_hu.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: Bambu Studio\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-08-08 20:01+0800\n" +"POT-Creation-Date: 2022-08-12 15:19+0800\n" "Language: hu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -2394,6 +2394,9 @@ msgstr "Ortogonális nézet használata" msgid "Preferences" msgstr "Beállítások" +msgid "About" +msgstr "" + msgid "View" msgstr "Nézet" @@ -4284,6 +4287,16 @@ msgstr "Move slider 5x faster" msgid "Shift+Mouse wheel" msgstr "Shift+Egérgörgő" +msgid "Release Note" +msgstr "" + +#, c-format, boost-format +msgid "version %s update information :" +msgstr "" + +msgid "New version of Bambu Studio" +msgstr "New version of Bambu Studio" + msgid "Saving objects into the 3mf failed." msgstr "Az objektumok mentése a 3mf-be sikertelen volt." @@ -6662,9 +6675,6 @@ msgstr "Support: propagate branches at layer %d" #~ msgid "Monitoring" #~ msgstr "Monitoring" -#~ msgid "New version of Bambu Studio" -#~ msgstr "New version of Bambu Studio" - #~ msgid "Output file" #~ msgstr "Output file" diff --git a/bbl/i18n/list.txt b/bbl/i18n/list.txt index e8e87f4a27..57ceea33b3 100644 --- a/bbl/i18n/list.txt +++ b/bbl/i18n/list.txt @@ -91,6 +91,8 @@ src/slic3r/GUI/WebUserLoginDialog.cpp src/slic3r/GUI/WebGuideDialog.cpp src/slic3r/GUI/KBShortcutsDialog.hpp src/slic3r/GUI/KBShortcutsDialog.cpp +src/slic3r/GUI/ReleaseNote.cpp +src/slic3r/GUI/ReleaseNote.hpp src/slic3r/Utils/FixModelByWin10.cpp src/slic3r/Utils/PresetUpdater.cpp src/slic3r/Utils/Http.cpp diff --git a/bbl/i18n/nl/BambuStudio_nl.po b/bbl/i18n/nl/BambuStudio_nl.po index 19580a4d3c..34359a62ac 100644 --- a/bbl/i18n/nl/BambuStudio_nl.po +++ b/bbl/i18n/nl/BambuStudio_nl.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: Bambu Studio\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-08-08 20:01+0800\n" +"POT-Creation-Date: 2022-08-12 15:19+0800\n" "Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -2391,6 +2391,9 @@ msgstr "Orthogonale weergave gebruiken" msgid "Preferences" msgstr "Voorkeuren" +msgid "About" +msgstr "" + msgid "View" msgstr "Weergave" @@ -4310,6 +4313,16 @@ msgstr "Schuifregelaar 5x sneller verplaatsen" msgid "Shift+Mouse wheel" msgstr "" +msgid "Release Note" +msgstr "" + +#, c-format, boost-format +msgid "version %s update information :" +msgstr "" + +msgid "New version of Bambu Studio" +msgstr "Nieuwe versie van Bambu Studio" + msgid "Saving objects into the 3mf failed." msgstr "Het opslaan van de objecten naar het 3mf bestand is mislukt." @@ -6716,9 +6729,6 @@ msgstr "Support: propagate branches at layer %d" #~ msgid "Monitoring" #~ msgstr "Monitoren" -#~ msgid "New version of Bambu Studio" -#~ msgstr "Nieuwe versie van Bambu Studio" - #~ msgid "Output file" #~ msgstr "Bestand weergeven" diff --git a/bbl/i18n/sv/BambuStudio_sv.po b/bbl/i18n/sv/BambuStudio_sv.po index d4d7aae098..a4810b1823 100644 --- a/bbl/i18n/sv/BambuStudio_sv.po +++ b/bbl/i18n/sv/BambuStudio_sv.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: Bambu Studio\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-08-08 20:01+0800\n" +"POT-Creation-Date: 2022-08-12 15:19+0800\n" "Language: sv\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -2365,6 +2365,9 @@ msgstr "Använd Ortogonal Vy" msgid "Preferences" msgstr "Inställningar" +msgid "About" +msgstr "" + msgid "View" msgstr "Vy" @@ -4239,6 +4242,16 @@ msgstr "Flytta reglage 5x snabbare" msgid "Shift+Mouse wheel" msgstr "" +msgid "Release Note" +msgstr "" + +#, c-format, boost-format +msgid "version %s update information :" +msgstr "" + +msgid "New version of Bambu Studio" +msgstr "Ny version av Bambu Studio" + msgid "Saving objects into the 3mf failed." msgstr "Sparande av objektet till 3mf misslyckades." @@ -6564,9 +6577,6 @@ msgstr "Support: föröka grenar vid lager %d" #~ msgid "Monitoring" #~ msgstr "Övervakar" -#~ msgid "New version of Bambu Studio" -#~ msgstr "Ny version av Bambu Studio" - #~ msgid "Output file" #~ msgstr "Utdatafil" diff --git a/bbl/i18n/zh_cn/BambuStudio_zh_CN.po b/bbl/i18n/zh_cn/BambuStudio_zh_CN.po index 5767801a21..66a940c724 100644 --- a/bbl/i18n/zh_cn/BambuStudio_zh_CN.po +++ b/bbl/i18n/zh_cn/BambuStudio_zh_CN.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Slic3rPE\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-08-08 20:01+0800\n" +"POT-Creation-Date: 2022-08-12 15:19+0800\n" "PO-Revision-Date: 2022-08-07 21:57+0800\n" "Last-Translator: Jiang Yue \n" "Language-Team: \n" @@ -1275,8 +1275,8 @@ msgid "" "Bambu Studio is based on PrusaSlicer by PrusaResearch and SuperSlicer by " "Merill(supermerill)." msgstr "" -"Bambu Studio是以PrusaResearch的PrusaSlicer和Merill(supermerill)的" -"SuperSlicer为基础的。" +"Bambu Studio是以PrusaResearch的PrusaSlicer和Merill(supermerill)的SuperSlicer" +"为基础的。" msgid "PrusaSlicer is originally based on Slic3r by Alessandro Ranellucci." msgstr "MerillPrusasicle最初是以Alessandro Ranellucci为基础的Slic3r。" @@ -1294,8 +1294,8 @@ msgid "" "we're unable to list them one-by-one, and instead, they'll be attributed in " "the corresponding code comments." msgstr "" -"软件中的很多部分都来自于社区贡献,因此,我们无法逐一列出他们,相反的,他们将被" -"注释于相应的代码中。" +"软件中的很多部分都来自于社区贡献,因此,我们无法逐一列出他们,相反的,他们将" +"被注释于相应的代码中。" msgid "AMSMaterialsSetting" msgstr "" @@ -2311,6 +2311,9 @@ msgstr "使用正交视角" msgid "Preferences" msgstr "偏好设置" +msgid "About" +msgstr "" + msgid "View" msgstr "视图" @@ -4143,6 +4146,16 @@ msgstr "5倍速移动滑动条" msgid "Shift+Mouse wheel" msgstr "Shift+鼠标滚轮" +msgid "Release Note" +msgstr "" + +#, c-format, boost-format +msgid "version %s update information :" +msgstr "版本 %s 更新信息" + +msgid "New version of Bambu Studio" +msgstr "新版本的Bambu Studio" + msgid "Saving objects into the 3mf failed." msgstr "保存对象到3mf失败。" @@ -6337,9 +6350,6 @@ msgstr "支撑:正在生长层%d的树枝" #~ "2. 打印丝预设\n" #~ "3. 打印机预设\n" -#~ msgid "New version of Bambu Studio" -#~ msgstr "新版本的Bambu Studio" - #~ msgid "Fix model through cloud" #~ msgstr "通过云端修复模型" diff --git a/cmake/modules/FindGTK3.cmake b/cmake/modules/FindGTK3.cmake index 9f62658d0f..8fee6f9f41 100644 --- a/cmake/modules/FindGTK3.cmake +++ b/cmake/modules/FindGTK3.cmake @@ -42,5 +42,10 @@ set(VERSION_OK FALSE) endif () endif () endif () +# Check for GDK Wayland support +include(CheckSymbolExists) +set(CMAKE_REQUIRED_INCLUDES ${GTK3_INCLUDE_DIRS}) +check_symbol_exists(GDK_WINDOWING_WAYLAND "gdk/gdk.h" wxHAVE_GDK_WAYLAND) +check_symbol_exists(GDK_WINDOWING_X11 "gdk/gdk.h" wxHAVE_GDK_X11) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(GTK3 DEFAULT_MSG GTK3_INCLUDE_DIRS GTK3_LIBRARIES VERSION_OK) diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 5e8f00fefc..3b2639413c 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -191,6 +191,7 @@ include(JPEG/JPEG.cmake) include(TIFF/TIFF.cmake) include(wxWidgets/wxWidgets.cmake) include(OCCT/OCCT.cmake) +include(FREETYPE/FREETYPE.cmake) set(_dep_list dep_Boost @@ -219,6 +220,7 @@ else() endif() list(APPEND _dep_list "dep_OCCT") +list(APPEND _dep_list "dep_FREETYPE") add_custom_target(deps ALL DEPENDS ${_dep_list}) diff --git a/deps/FREETYPE/FREETYPE.cmake b/deps/FREETYPE/FREETYPE.cmake new file mode 100644 index 0000000000..c8aa400a2b --- /dev/null +++ b/deps/FREETYPE/FREETYPE.cmake @@ -0,0 +1,23 @@ +if(WIN32) + set(library_build_shared "1") +else() + set(library_build_shared "0") +endif() + +bambustudio_add_cmake_project(FREETYPE + URL https://mirror.ossplanet.net/nongnu/freetype/freetype-2.12.1.tar.gz + URL_HASH SHA256=efe71fd4b8246f1b0b1b9bfca13cfff1c9ad85930340c27df469733bbb620938 + #DEPENDS ${ZLIB_PKG} + #"${_patch_step}" + CMAKE_ARGS + -D BUILD_SHARED_LIBS=${library_build_shared} + -D FT_DISABLE_ZLIB=TRUE + -D FT_DISABLE_BZIP2=TRUE + -D FT_DISABLE_PNG=TRUE + -D FT_DISABLE_HARFBUZZ=TRUE + -D FT_DISABLE_BROTLI=TRUE +) + +if(MSVC) + add_debug_dep(dep_FREETYPE) +endif() diff --git a/deps/OCCT/OCCT.cmake b/deps/OCCT/OCCT.cmake index 89af498084..2943a3cbbd 100644 --- a/deps/OCCT/OCCT.cmake +++ b/deps/OCCT/OCCT.cmake @@ -10,19 +10,21 @@ bambustudio_add_cmake_project(OCCT #PATCH_COMMAND ${PATCH_CMD} ${CMAKE_CURRENT_LIST_DIR}/0001-OCCT-fix.patch PATCH_COMMAND git apply --directory deps/build/dep_OCCT-prefix/src/dep_OCCT --verbose --ignore-space-change --whitespace=fix ${CMAKE_CURRENT_LIST_DIR}/0001-OCCT-fix.patch #DEPENDS dep_Boost + #DEPENDS dep_FREETYPE CMAKE_ARGS -DBUILD_LIBRARY_TYPE=${library_build_type} -DUSE_TK=OFF -DUSE_TBB=OFF - -DUSE_FREETYPE=OFF - -DUSE_FFMPEG=OFF - -DUSE_VTK=OFF - -DUSE_FREETYPE=OFF - -DBUILD_MODULE_ApplicationFramework=OFF - #-DBUILD_MODULE_DataExchange=OFF + #-DUSE_FREETYPE=OFF + -DUSE_FFMPEG=OFF + -DUSE_VTK=OFF + -DBUILD_MODULE_ApplicationFramework=OFF + #-DBUILD_MODULE_DataExchange=OFF -DBUILD_MODULE_Draw=OFF - -DBUILD_MODULE_FoundationClasses=OFF - -DBUILD_MODULE_ModelingAlgorithms=OFF - -DBUILD_MODULE_ModelingData=OFF - -DBUILD_MODULE_Visualization=OFF + -DBUILD_MODULE_FoundationClasses=OFF + -DBUILD_MODULE_ModelingAlgorithms=OFF + -DBUILD_MODULE_ModelingData=OFF + -DBUILD_MODULE_Visualization=OFF ) + +add_dependencies(dep_OCCT dep_FREETYPE) diff --git a/deps/wxWidgets/0001-wxWidget-fix.patch b/deps/wxWidgets/0001-wxWidget-fix.patch index 91ad5e6053..8541c58caa 100644 --- a/deps/wxWidgets/0001-wxWidget-fix.patch +++ b/deps/wxWidgets/0001-wxWidget-fix.patch @@ -1,3 +1,19 @@ +diff --git a/build/cmake/init.cmake b/build/cmake/init.cmake +index 0bc4f934b9..479431a69c 100644 +--- a/build/cmake/init.cmake ++++ b/build/cmake/init.cmake +@@ -413,7 +413,11 @@ if(wxUSE_GUI) + else() + find_package(OpenGL) + if(WXGTK3 AND OpenGL_EGL_FOUND AND wxUSE_GLCANVAS_EGL) ++ if(UNIX AND NOT APPLE) ++ set(OPENGL_LIBRARIES OpenGL EGL) ++ else() + set(OPENGL_LIBRARIES OpenGL::OpenGL OpenGL::EGL) ++ endif() + find_package(WAYLANDEGL) + if(WAYLANDEGL_FOUND AND wxHAVE_GDK_WAYLAND) + list(APPEND OPENGL_LIBRARIES ${WAYLANDEGL_LIBRARIES}) diff --git a/include/wx/fontutil.h b/include/wx/fontutil.h index 09ad8c8ef3..3c0c2d8f7e 100644 --- a/include/wx/fontutil.h diff --git a/resources/i18n/de/BambuStudio.mo b/resources/i18n/de/BambuStudio.mo index 690812f4ee..c954408e1e 100644 Binary files a/resources/i18n/de/BambuStudio.mo and b/resources/i18n/de/BambuStudio.mo differ diff --git a/resources/i18n/en/BambuStudio.mo b/resources/i18n/en/BambuStudio.mo index 248a58b197..af36af44df 100644 Binary files a/resources/i18n/en/BambuStudio.mo and b/resources/i18n/en/BambuStudio.mo differ diff --git a/resources/i18n/es/BambuStudio.mo b/resources/i18n/es/BambuStudio.mo index 90268ce8d4..5266e7ac15 100644 Binary files a/resources/i18n/es/BambuStudio.mo and b/resources/i18n/es/BambuStudio.mo differ diff --git a/resources/i18n/fr/BambuStudio.mo b/resources/i18n/fr/BambuStudio.mo index acce4dcb7b..fa03152dc9 100644 Binary files a/resources/i18n/fr/BambuStudio.mo and b/resources/i18n/fr/BambuStudio.mo differ diff --git a/resources/i18n/hu/BambuStudio.mo b/resources/i18n/hu/BambuStudio.mo index 7a57f7573c..712afaed1b 100644 Binary files a/resources/i18n/hu/BambuStudio.mo and b/resources/i18n/hu/BambuStudio.mo differ diff --git a/resources/i18n/nl/BambuStudio.mo b/resources/i18n/nl/BambuStudio.mo index 2c41f343c3..4a0f6d9e73 100644 Binary files a/resources/i18n/nl/BambuStudio.mo and b/resources/i18n/nl/BambuStudio.mo differ diff --git a/resources/i18n/sv/BambuStudio.mo b/resources/i18n/sv/BambuStudio.mo index bb309d7d65..7c77ba48cf 100644 Binary files a/resources/i18n/sv/BambuStudio.mo and b/resources/i18n/sv/BambuStudio.mo differ diff --git a/resources/i18n/zh_cn/BambuStudio.mo b/resources/i18n/zh_cn/BambuStudio.mo index c3dcab310c..c5a2d5dab4 100644 Binary files a/resources/i18n/zh_cn/BambuStudio.mo and b/resources/i18n/zh_cn/BambuStudio.mo differ diff --git a/resources/images/hms_arrow.svg b/resources/images/hms_arrow.svg new file mode 100644 index 0000000000..5cebec4000 --- /dev/null +++ b/resources/images/hms_arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/images/hms_notify_lv1.svg b/resources/images/hms_notify_lv1.svg new file mode 100644 index 0000000000..3c030c381f --- /dev/null +++ b/resources/images/hms_notify_lv1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/images/hms_notify_lv2.svg b/resources/images/hms_notify_lv2.svg new file mode 100644 index 0000000000..bc02fb1a8a --- /dev/null +++ b/resources/images/hms_notify_lv2.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/images/hms_notify_lv3.svg b/resources/images/hms_notify_lv3.svg new file mode 100644 index 0000000000..fd127ed274 --- /dev/null +++ b/resources/images/hms_notify_lv3.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/resources/profiles/BBL.json b/resources/profiles/BBL.json index a826d16e80..5888a97c25 100644 --- a/resources/profiles/BBL.json +++ b/resources/profiles/BBL.json @@ -1,7 +1,7 @@ { "name": "Bambulab", "url": "http://www.bambulab.com/Parameters/vendor/BBL.json", - "version": "01.01.01.02", + "version": "01.01.01.03", "force_update": "0", "description": "the initial version of BBL configurations", "machine_model_list": [ @@ -301,6 +301,10 @@ "name": "Bambu PC @BBL X1C 0.8 nozzle", "sub_path": "filament/Bambu PC @BBL X1C 0.8 nozzle.json" }, + { + "name": "Bambu PC @BBL X1C 0.6 nozzle", + "sub_path": "filament/Bambu PC @BBL X1C 0.6 nozzle.json" + }, { "name": "PolyLite PLA @BBL X1C", "sub_path": "filament/PolyLite PLA @BBL X1C.json" diff --git a/resources/profiles/BBL/filament/Bambu PC @BBL X1C 0.6 nozzle.json b/resources/profiles/BBL/filament/Bambu PC @BBL X1C 0.6 nozzle.json new file mode 100644 index 0000000000..82f7bc217a --- /dev/null +++ b/resources/profiles/BBL/filament/Bambu PC @BBL X1C 0.6 nozzle.json @@ -0,0 +1,14 @@ +{ + "type": "filament", + "setting_id": "GFSC00_01", + "name": "Bambu PC @BBL X1C 0.6 nozzle", + "from": "system", + "instantiation": "true", + "inherits": "Bambu PC @base", + "nozzle_temperature": [ + "260" + ], + "compatible_printers": [ + "Bambu Lab X1 Carbon 0.6 nozzle" + ] +} diff --git a/resources/profiles/BBL/filament/Bambu PC @BBL X1C.json b/resources/profiles/BBL/filament/Bambu PC @BBL X1C.json index 6daa0d9954..fd464679c7 100644 --- a/resources/profiles/BBL/filament/Bambu PC @BBL X1C.json +++ b/resources/profiles/BBL/filament/Bambu PC @BBL X1C.json @@ -7,7 +7,6 @@ "inherits": "Bambu PC @base", "compatible_printers": [ "Bambu Lab X1 Carbon 0.4 nozzle", - "Bambu Lab X1 0.4 nozzle", - "Bambu Lab X1 Carbon 0.6 nozzle" + "Bambu Lab X1 0.4 nozzle" ] } diff --git a/resources/profiles/BBL/filament/Bambu Support G @BBL X1C.json b/resources/profiles/BBL/filament/Bambu Support G @BBL X1C.json index 36eddbed40..d78aa93bfc 100644 --- a/resources/profiles/BBL/filament/Bambu Support G @BBL X1C.json +++ b/resources/profiles/BBL/filament/Bambu Support G @BBL X1C.json @@ -6,6 +6,8 @@ "instantiation": "true", "inherits": "Bambu Support G @base", "compatible_printers": [ - "Bambu Lab X1 Carbon 0.4 nozzle" + "Bambu Lab X1 Carbon 0.4 nozzle", + "Bambu Lab X1 Carbon 0.6 nozzle", + "Bambu Lab X1 Carbon 0.8 nozzle" ] } diff --git a/resources/profiles/BBL/filament/Bambu Support G @base.json b/resources/profiles/BBL/filament/Bambu Support G @base.json index 3a90f36ef1..f9d1637e30 100644 --- a/resources/profiles/BBL/filament/Bambu Support G @base.json +++ b/resources/profiles/BBL/filament/Bambu Support G @base.json @@ -19,5 +19,11 @@ ], "nozzle_temperature": [ "280" + ], + "fan_cooling_layer_time": [ + "10" + ], + "slow_down_layer_time":[ + "6" ] } diff --git a/resources/profiles/BBL/filament/Bambu Support W @BBL X1C.json b/resources/profiles/BBL/filament/Bambu Support W @BBL X1C.json index 82bd6d15da..7dc4b4f9b7 100644 --- a/resources/profiles/BBL/filament/Bambu Support W @BBL X1C.json +++ b/resources/profiles/BBL/filament/Bambu Support W @BBL X1C.json @@ -6,6 +6,8 @@ "instantiation": "true", "inherits": "Bambu Support W @base", "compatible_printers": [ - "Bambu Lab X1 Carbon 0.4 nozzle" + "Bambu Lab X1 Carbon 0.4 nozzle", + "Bambu Lab X1 Carbon 0.6 nozzle", + "Bambu Lab X1 Carbon 0.8 nozzle" ] } diff --git a/resources/profiles/BBL/filament/Bambu Support W @base.json b/resources/profiles/BBL/filament/Bambu Support W @base.json index bc59cd4c63..62ca849148 100644 --- a/resources/profiles/BBL/filament/Bambu Support W @base.json +++ b/resources/profiles/BBL/filament/Bambu Support W @base.json @@ -25,5 +25,8 @@ ], "hot_plate_temp_initial_layer": [ "40" + ], + "slow_down_layer_time": [ + "8" ] } diff --git a/resources/profiles/BBL/filament/Generic ABS.json b/resources/profiles/BBL/filament/Generic ABS.json index 9df6f94e65..a2f2fa1597 100644 --- a/resources/profiles/BBL/filament/Generic ABS.json +++ b/resources/profiles/BBL/filament/Generic ABS.json @@ -14,6 +14,8 @@ ], "compatible_printers": [ "Bambu Lab X1 Carbon 0.4 nozzle", - "Bambu Lab X1 0.4 nozzle" + "Bambu Lab X1 0.4 nozzle", + "Bambu Lab X1 Carbon 0.6 nozzle", + "Bambu Lab X1 Carbon 0.8 nozzle" ] } diff --git a/resources/profiles/BBL/filament/Generic ASA.json b/resources/profiles/BBL/filament/Generic ASA.json index 0459e85ad4..2d63f41ec2 100644 --- a/resources/profiles/BBL/filament/Generic ASA.json +++ b/resources/profiles/BBL/filament/Generic ASA.json @@ -14,6 +14,8 @@ ], "compatible_printers": [ "Bambu Lab X1 Carbon 0.4 nozzle", - "Bambu Lab X1 0.4 nozzle" + "Bambu Lab X1 0.4 nozzle", + "Bambu Lab X1 Carbon 0.6 nozzle", + "Bambu Lab X1 Carbon 0.8 nozzle" ] } diff --git a/resources/profiles/BBL/filament/Generic PC.json b/resources/profiles/BBL/filament/Generic PC.json index 70bd718c24..35d6fcd54a 100644 --- a/resources/profiles/BBL/filament/Generic PC.json +++ b/resources/profiles/BBL/filament/Generic PC.json @@ -13,6 +13,8 @@ "0.94" ], "compatible_printers": [ - "Bambu Lab X1 Carbon 0.4 nozzle" + "Bambu Lab X1 Carbon 0.4 nozzle", + "Bambu Lab X1 Carbon 0.6 nozzle", + "Bambu Lab X1 Carbon 0.8 nozzle" ] } diff --git a/resources/profiles/BBL/filament/Generic PETG.json b/resources/profiles/BBL/filament/Generic PETG.json index 6bc8c20259..8d5fe9aa1c 100644 --- a/resources/profiles/BBL/filament/Generic PETG.json +++ b/resources/profiles/BBL/filament/Generic PETG.json @@ -44,6 +44,8 @@ ], "compatible_printers": [ "Bambu Lab X1 Carbon 0.4 nozzle", - "Bambu Lab X1 0.4 nozzle" + "Bambu Lab X1 0.4 nozzle", + "Bambu Lab X1 Carbon 0.6 nozzle", + "Bambu Lab X1 Carbon 0.8 nozzle" ] } diff --git a/resources/profiles/BBL/filament/Generic PLA-CF.json b/resources/profiles/BBL/filament/Generic PLA-CF.json index c1ae7e4658..01d3ae4bbf 100644 --- a/resources/profiles/BBL/filament/Generic PLA-CF.json +++ b/resources/profiles/BBL/filament/Generic PLA-CF.json @@ -18,6 +18,9 @@ "slow_down_layer_time": [ "7" ], + "additional_cooling_fan_speed": [ + "0" + ], "compatible_printers": [ "Bambu Lab X1 Carbon 0.4 nozzle" ] diff --git a/resources/profiles/BBL/filament/Generic PLA.json b/resources/profiles/BBL/filament/Generic PLA.json index cf52396cf4..014cd3942f 100644 --- a/resources/profiles/BBL/filament/Generic PLA.json +++ b/resources/profiles/BBL/filament/Generic PLA.json @@ -17,6 +17,8 @@ ], "compatible_printers": [ "Bambu Lab X1 Carbon 0.4 nozzle", - "Bambu Lab X1 0.4 nozzle" + "Bambu Lab X1 0.4 nozzle", + "Bambu Lab X1 Carbon 0.6 nozzle", + "Bambu Lab X1 Carbon 0.8 nozzle" ] } diff --git a/resources/profiles/BBL/filament/Generic TPU.json b/resources/profiles/BBL/filament/Generic TPU.json index 17e393dad9..163440eb56 100644 --- a/resources/profiles/BBL/filament/Generic TPU.json +++ b/resources/profiles/BBL/filament/Generic TPU.json @@ -11,6 +11,8 @@ ], "compatible_printers": [ "Bambu Lab X1 Carbon 0.4 nozzle", - "Bambu Lab X1 0.4 nozzle" + "Bambu Lab X1 0.4 nozzle", + "Bambu Lab X1 Carbon 0.6 nozzle", + "Bambu Lab X1 Carbon 0.8 nozzle" ] } diff --git a/resources/profiles/BBL/filament/fdm_filament_pva.json b/resources/profiles/BBL/filament/fdm_filament_pva.json index 36b7b7b123..d8bb1b18c7 100644 --- a/resources/profiles/BBL/filament/fdm_filament_pva.json +++ b/resources/profiles/BBL/filament/fdm_filament_pva.json @@ -89,6 +89,6 @@ "70" ], "filament_start_gcode": [ - "; filament start gcode\n{if (bed_temperature[current_extruder] >45)||(bed_temperature_initial_layer[current_extruder] >45)}M106 P3 S180\n{elsif (bed_temperature[current_extruder] >50)||(bed_temperature_initial_layer[current_extruder] >50)}M106 P3 S255\n{endif};Prevent PLA from jamming" + "; filament start gcode\n{if (bed_temperature[current_extruder] >35)||(bed_temperature_initial_layer[current_extruder] >35)}M106 P3 S180\n{elsif (bed_temperature[current_extruder] >45)||(bed_temperature_initial_layer[current_extruder] >45)}M106 P3 S255\n{endif};Prevent PLA from jamming" ] } diff --git a/resources/profiles/BBL/machine/Bambu Lab X1 Carbon 0.8 nozzle.json b/resources/profiles/BBL/machine/Bambu Lab X1 Carbon 0.8 nozzle.json index a9f4abc641..e06a0c188f 100644 --- a/resources/profiles/BBL/machine/Bambu Lab X1 Carbon 0.8 nozzle.json +++ b/resources/profiles/BBL/machine/Bambu Lab X1 Carbon 0.8 nozzle.json @@ -26,5 +26,8 @@ "retraction_minimum_travel": [ "1" ], + "retract_length_toolchange": [ + "3" + ], "machine_start_gcode": "\n;===== date: 202200731 =====================\n;===== reset machine status =================\nG91\nM17 Z0.3 ; lower the z-motor current\nG0 Z7 F300 ; lower the hotbed , to prevent the nozzle is below the hotbed\nG90\nM17 X1.2 Y1.2 Z0.75 ; reset motor current to default\nM960 S5 P1 ; turn on logo lamp\nG90\nM220 S100 ;Reset Feedrate\nM221 S100 ;Reset Flowrate\nM73.2 R1.0 ;Reset left time magnitude\nM1002 set_gcode_claim_speed_level : 5\n\n;===== heatbed preheat ====================\nM1002 gcode_claim_action : 2\n{if bbl_bed_temperature_gcode}\nM1002 set_heatbed_surface_temp:[bed_temperature_initial_layer_vector] ;config bed temps\nM140 A S[bed_temperature_initial_layer_single] ;set bed temp\nM190 A S[bed_temperature_initial_layer_single] ;wait for bed temp\n{else}\nM140 S[bed_temperature_initial_layer_single] ;set bed temp\nM190 S[bed_temperature_initial_layer_single] ;wait for bed temp\n{endif}\n\n{if scan_first_layer}\n;=========register first layer scan=====\nM977 S1 P60\n{endif}\n\n=============turn on fans to prevent PLA jamming=================\n{if filament_type[initial_tool]==\"PLA\"}\n {if (bed_temperature[current_extruder] >45)||(bed_temperature_initial_layer[current_extruder] >45)}\n M106 P3 S180\n {elsif (bed_temperature[current_extruder] >50)||(bed_temperature_initial_layer[current_extruder] >50)}\n M106 P3 S255\n {endif};Prevent PLA from jamming\n{endif}\nM106 P2 S100 ; turn on big fan ,to cool down toolhead\n\n;===== prepare print temperature and material ==========\nM104 S[nozzle_temperature_initial_layer] ;set extruder temp\nG91\nG0 Z2 F1200\nG90\nG28 X\nM975 S1 ; turn on \nG1 X60 F12000\nG1 Y245\nG1 Y265 F3000\nM620 M\nM620 S[initial_tool]A ; switch material if AMS exist\n M109 S[nozzle_temperature_initial_layer]\n G1 X120 F12000\n\n G1 X20 Y50 F12000\n G1 Y-3\n T[initial_tool]\n G1 X54 F12000\n G1 Y265\n M400\nM621 S[initial_tool]A\n\nM412 S1 ; ===turn on filament runout detection===\n\nM109 S250 ;set nozzle to common flush temp\nM106 P1 S0\nG92 E0\nG1 E50 F200\nM400\nM104 S[nozzle_temperature_initial_layer]\nG92 E0\nG1 E50 F200\nM400\nM106 P1 S255\nG92 E0\nG1 E5 F300\nM109 S{nozzle_temperature_initial_layer[initial_extruder]-20} ; drop nozzle temp, make filament shink a bit\nG92 E0\nG1 E-0.5 F300\n\nG1 X70 F9000\nG1 X76 F15000\nG1 X65 F15000\nG1 X76 F15000\nG1 X65 F15000; shake to put down garbage\nG1 X80 F6000\nG1 X95 F15000\nG1 X80 F15000\nG1 X165 F15000; wipe and shake\nM400\nM106 P1 S0\nM975 S1\n;===== prepare print temperature and material end =====\n\n\n;===== wipe mouth ===============================\nM1002 gcode_claim_action : 14\nM975 S1\nM106 S255\nG1 X65 Y230 F18000\nG1 Y264 F6000\nM109 S{nozzle_temperature_initial_layer[initial_extruder]-20}\nG1 X100 F18000 ; first wipe mouth\n\nG0 X135 Y253 F20000 ; move to exposed steel surface edge\nG28 Z P0 T300; home z with low precision,permit 300deg temperature\nG29.2 S0 ; turn off ABL\nG0 Z2 F20000\n\nG1 X60 Y265\nG92 E0\nG1 E-0.5 F300 ; retrack more\nG1 X100 F5000; second wipe mouth\nG1 X70 F15000\nG1 X100 F5000\nG1 X70 F15000\nG1 X100 F5000\nG1 X70 F15000\nG1 X100 F5000\nG1 X70 F15000\nG1 X90 F5000\nG0 X128 Y261 Z-1.5 F20000 ; move to exposed steel surface and stop the nozzle\nM104 S140 ; set temp down to heatbed acceptable\nM106 S255 ; turn on fan (G28 has turn off fan)\n\nG0 X126 F120\nG0 X130\nG0 X126\nG0 X130\nG0 X128\nM109 S140 ; wait nozzle temp down to heatbed acceptable\nG1 Z10 F1200\nM400\nG1 Z10\nG1 F30000\nG1 X230 Y15\nG29.2 S1 ; turn on ABL\n;G28 ; home again after hard wipe mouth\nM106 S0 ; turn off fan , too noisy\n;===== wipe mouth end ================================\n\n\n;===== bed leveling ==================================\nM1002 judge_flag g29_before_print_flag\nM622 J1\n\n M1002 gcode_claim_action : 1\n G29 A X{first_layer_print_min[0]} Y{first_layer_print_min[1]} I{first_layer_print_size[0]} J{first_layer_print_size[1]}\n M400\n M500 ; save cali data\n\nM623\n;===== bed leveling end ================================\n\n;===== home after wipe mouth============================\nM1002 judge_flag g29_before_print_flag\nM622 J0\n\n M1002 gcode_claim_action : 13\n G28\n\nM623\n;===== home after wipe mouth end =======================\n\nM975 S1 ; turn on vibration supression\n\n;===== check scanner clarity ===========================\nM972 S5 P0 \nM400 P500\n;===== check scanner clarity end =======================\n\n=============turn on fans to prevent PLA jamming=================\n{if filament_type[initial_tool]==\"PLA\"}\n {if (bed_temperature[current_extruder] >45)||(bed_temperature_initial_layer[current_extruder] >45)}\n M106 P3 S180\n {elsif (bed_temperature[current_extruder] >50)||(bed_temperature_initial_layer[current_extruder] >50)}\n M106 P3 S255\n {endif};Prevent PLA from jamming\n{endif}\nM106 P2 S100 ; turn on big fan ,to cool down toolhead\n\n{if scan_first_layer}\n;start heatbed scan====================================\nM976 S2 P1 \nM400 S3\n{endif}\n\nM104 S{nozzle_temperature_initial_layer[initial_extruder]} ; set extrude temp earlier, to reduce wait time\n\n;===== mech mode fast check============================\nG1 X128 Y128 Z5 F20000\nM400 P200\nM970.3 Q1 A7 B30 C80 H15 K0\nM974 Q1 S2 P0\n\nG1 X128 Y128 Z5 F20000\nM400 P200\nM970.3 Q0 A7 B30 C90 Q0 H15 K0\nM974 Q0 S2 P0\n\nG1 F30000\nG1 X230 Y15\nG28 X ; re-home XY \n;===== fmech mode fast check============================\n\n\n;===== noozle load line ===============================\nM975 S1\nG90 \nM83\nT1000\nG1 X18.0 Y4.5 Z0.3 F18000;Move to start position\nM109 S{nozzle_temperature_initial_layer[initial_extruder]}\nG0 E3 F300\nG0 X129 E15 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \nG0 X240 E15 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \nG0 Y5.5 \nG0 X129 E15\nG0 X18 E15\nM400\n\n;===== draw extrinsic para cali paint =================\nM1002 judge_flag extrude_cali_flag\nM622 J1\n\n M1002 gcode_claim_action : 8\n\n T1000 \n G0 F3000 X28.000 Y19.500 Z0.200\n G1 F1200.0 X28.000 Y45.000 Z0.200 E1.8660 \n G1 F1200.0 X28.500 Y45.000 Z0.200 E0.0360 \n G1 F1200.0 X28.500 Y19.500 Z0.200 E1.8660 \n G1 F1200.0 X31.000 Y19.500 Z0.200 E0.1820 \n G1 F1200.0 X31.000 Y49.000 Z0.200 E2.1600 \n G1 F1200.0 X37.500 Y49.000 Z0.200 E0.4760 \n G1 F1200.0 X37.500 Y60.000 Z0.200 E0.8060 \n G1 F1200.0 X42.500 Y60.000 Z0.200 E0.3660 \n G1 F1200.0 X42.500 Y49.000 Z0.200 E0.8060 \n G1 F1200.0 X48.000 Y49.000 Z0.200 E0.4020 \n G1 F1200.0 X48.000 Y20.000 Z0.200 E2.1220 \n G1 F1200.0 X30.000 Y20.000 Z0.200 E1.3180 \n G1 F1200.0 X30.000 Y41.000 Z0.200 E1.5380 \n G1 F1200.0 X50.000 Y41.000 Z0.200 E1.4640 \n G1 F1200.0 X50.000 Y34.000 Z0.200 E0.5120 \n G1 F1200.0 X30.000 Y34.000 Z0.200 E1.4640 \n G1 F1500.000 E-0.800 \n\n ;=========== extruder cali extrusion ================== \n\n T1000 \n M83 \n G0 X35.000 Y18.000 Z0.300 F30000 E0\n G1 F1500.000 E0.800 \n M106 S0 ; turn off fan\n G0 X110.000 E9.35441 F4800 \n G0 X185.000 E9.35441 F4800 \n G0 X187 Z0\n G1 F1500.000 E-0.800 \n G0 Z1\n G0 X180 Z0.3 F18000\n \n M900 L1000.0 M1.0\n M900 K0.020 \n G0 X45.000 F30000 \n G0 Y20.000 F30000 \n G1 F1500.000 E0.800 \n G1 X65.000 E2.4945 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60}\n G1 X70.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X75.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X80.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X85.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X90.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X95.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X100.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X105.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X110.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X115.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X120.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X125.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X130.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X135.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X140.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X145.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X150.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X155.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X160.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X165.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X170.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X175.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X180.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 F1500.000 E-0.800 \n G1 X183 Z0.15 F30000\n G1 X185\n G1 Z1.0\n G0 Y18.000 F30000 ; move y to clear pos \n G1 Z0.3\n M400\n\n G0 X45.000 F30000 \n M900 K0.010 \n G0 X45.000 F30000 \n G0 Y22.000 F30000 \n G1 F1500.000 E0.800 \n G1 X65.000 E2.4945 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60}\n G1 X70.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X75.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X80.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X85.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X90.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X95.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X100.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X105.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X110.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X115.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X120.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X125.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X130.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X135.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X140.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X145.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X150.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X155.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X160.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X165.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X170.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X175.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X180.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 F1500.000 E-0.800 \n G1 X183 Z0.15 F30000\n G1 X185\n G1 Z1.0\n G0 Y18.000 F30000 ; move y to clear pos \n G1 Z0.3\n M400\n\n G0 X45.000 F30000 \n M900 K0.000 \n G0 X45.000 F30000 \n G0 Y24.000 F30000 \n G1 F1500.000 E0.800 \n G1 X65.000 E2.4945 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60}\n G1 X70.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X75.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X80.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X85.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X90.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X95.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X100.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X105.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X110.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X115.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X120.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X125.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X130.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X135.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X140.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X145.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X150.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X155.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X160.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X165.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X170.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X175.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X180.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 F1500.000 E-0.800\n G1 X183 Z0.15 F30000\n G1 X185\n G1 Z1.0\n G0 Y18.000 F30000 ; move y to clear pos \n G1 Z0.3\n\n G0 X45.000 F30000 ; move to start point\n\nM623 ; end of \"draw extrinsic para cali paint\"\n\nM104 S140\n\n\n;=========== laser and rgb calibration =========== \nM400\nM18 E\nM500 R\n\nM973 S3 P14\n\nG1 X120 Y5.0 Z0.3 F18000.0;Move to first extrude line pos\nT1100\nG1 X143.0 Y5.0 Z0.3 F18000.0;Move to first extrude line pos\n\nM400 P100\n\nM960 S1 P1\nM400 P100\nM973 S6 ; use auto exposure by xcam\nM960 S0 P0\n\n;=========== handeye calibration ======================\nM1002 judge_flag extrude_cali_flag\nM622 J1\n\n M973 S3 P1 ; camera start stream\n M400 P500\n M973 S1 \n G0 F6000 X40.000 Y54.500 Z0.000 \n M960 S0 P1\n M973 S1\n M400 P800\n M971 S6 P0\n M973 S2 P16000\n M400 P500 \n G0 Z0.000 F12000\n M960 S0 P0\n M960 S1 P1 \n G0 Y37.50 \n M400 P200\n M971 S5 P1 \n M960 S0 P0\n M960 S2 P1 \n G0 Y54.50 \n M400 P200 \n M971 S5 P3 \n G0 Z0.500 F12000\n M960 S0 P0\n M960 S1 P1 \n G0 Y37.50 \n M400 P200\n M971 S5 P2 \n M960 S0 P0\n M960 S2 P1 \n G0 Y54.50 \n M400 P500 \n M971 S5 P4 \n M963 S1 \n M400 P1500 \n M964 \n T1100 \n G0 F6000 X40.000 Y54.500 Z0.000 \n M960 S0 P1\n M973 S1\n M400 P800\n M971 S6 P0\n M973 S2 P16000\n M400 P500 \n G0 Z0.000 F12000\n M960 S0 P0\n M960 S1 P1 \n G0 Y37.50 \n M400 P200\n M971 S5 P1 \n M960 S0 P0\n M960 S2 P1 \n G0 Y54.50 \n M400 P200 \n M971 S5 P3 \n G0 Z0.500 F12000\n M960 S0 P0\n M960 S1 P1 \n G0 Y37.50 \n M400 P200\n M971 S5 P2 \n M960 S0 P0\n M960 S2 P1 \n G0 Y54.50 \n M400 P500 \n M971 S5 P4 \n M963 S1 \n M400 P1500 \n M964 \n T1100 \n G1 Z3 F3000 \n\n M400\n M500 ; save cali data\n\n M104 S{nozzle_temperature_initial_layer[initial_extruder]} ; rise nozzle temp now ,to reduce temp waiting time.\n\n T1100 \n M400 P400 \n M960 S0 P0\n G0 F30000.000 Y22.000 X65.000 Z0.000\n M400 P400 \n M960 S1 P1 \n M400 P50 \n\n M969 S1 N3 A2000 \n G0 F360.000 X181.000 Z0.000\n M980.3 A70.000 B{outer_wall_volumetric_speed/(1.75*1.75/4*3.14)*60/4} C5.000 D{outer_wall_volumetric_speed/(1.75*1.75/4*3.14)*60} E5.000 F175.000 H1.000 I0.000 J0.010 K0.020\n M400 P100 \n G0 F20000\n G0 Z1 ; rise nozzle up\n T1000 ; change to nozzle space\n G0 X45.000 Y16.000 F30000 ; move to test line pos\n M969 S0 ; turn off scanning\n M960 S0 P0\n\n\n G1 Z2 F20000 \n T1000 \n G0 X45.000 Y16.000 F30000 E0\n M109 S{nozzle_temperature_initial_layer[initial_extruder]} \n G0 Z0.3\n G1 F1500.000 E3.600 \n G1 X65.000 E2.4945 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60}\n G1 X70.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X75.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X80.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X85.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X90.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X95.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X100.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X105.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X110.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X115.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X120.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X125.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X130.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X135.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60}\n\n ; see if extrude cali success, if not ,use default value\n M1002 judge_last_extrude_cali_success\n M622 J0\n M400\n M900 K0.01 M{outer_wall_volumetric_speed/(1.75*1.75/4*3.14) *0.01}\n M623 \n\n G1 X140.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X145.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X150.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X155.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X160.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X165.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X170.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X175.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X180.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X185.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X190.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X195.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X200.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X205.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X210.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X215.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n G1 X220.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) / 4 * 60} \n G1 X225.000 E0.6236 F{outer_wall_volumetric_speed/(0.3*1.0) * 60} \n M973 S4 \n\nM623\n\n;========turn off light and wait extrude temperature =============\nM1002 gcode_claim_action : 0\nM973 S4 ; turn off scanner\nM400 ; wait all motion done before implement the emprical L parameters\n;M900 L500.0 ; Empirical parameters\nM109 S[nozzle_temperature_initial_layer]\nM960 S1 P0 ; turn off laser\nM960 S2 P0 ; turn off laser\nM106 S0 ; turn off fan\nM106 P2 S0 ; turn off big fan \nM106 P3 S0 ; turn off chamber fan\n\nM975 S1 ; turn on mech mode supression\nG90 \nM83\nT1000\nG1 X128.0 Y253.0 Z0.2 F6000.0;Move to start position\nM109 S{nozzle_temperature_initial_layer[initial_extruder]}\nG0 X253 E6.4 F{outer_wall_volumetric_speed/(0.3*0.6) * 60} \nG0 Y128 E6.4\nG0 X252.5\nG0 Y252.5 E6.4\nG0 X128 E6.4" } diff --git a/resources/web/guide/22/22.js b/resources/web/guide/22/22.js index 1f402a222d..bab324c4e1 100644 --- a/resources/web/guide/22/22.js +++ b/resources/web/guide/22/22.js @@ -62,7 +62,7 @@ function SortUI() let OneMode=m_ProfileItem["model"][n]; if( OneMode["nozzle_selected"]!="" ) - ModelList.push(OneMode["model"]); + ModelList.push(OneMode); } //machine @@ -89,14 +89,14 @@ function SortUI() // $('#MachineList').hide(); // } - //machine + //model let HtmlMode=''; nMode=ModelList.length; for(let n=0;n'+sModel+''; + HtmlMode+='
'+sModel['model']+'
'; } $('#MachineList .CValues').append(HtmlMode); @@ -122,14 +122,16 @@ function SortUI() let fSelect=OneFila['selected']; let fModel=OneFila['models'] -// if(OneFila['name'].indexOf("K5 PLA Wood")>0) +// if(OneFila['name'].indexOf("Bambu PA-CF")>=0) // { +// alert( fShortName+' - '+fVendor+' - '+fType+' - '+fSelect+' - '+fModel ) +// // let b=1+2; // } let bFind=false; - let bCheck=$("#MachineList input:first").prop("checked"); - if(bCheck) + //let bCheck=$("#MachineList input:first").prop("checked"); + if( fModel=='') { bFind=true; } @@ -141,11 +143,20 @@ function SortUI() { let sOne=ModelList[m]; - if(fModel.indexOf(sOne)>=0) - { - bFind=true; - break; - } + let OneName=sOne['model']; + let NozzleArray=sOne["nozzle_selected"].split(';'); + + let nNozzle=NozzleArray.length; + + for( let b=0;b=0) + { + bFind=true; + break; + } + } } } @@ -335,7 +346,24 @@ function SortFilament() for(let n=0;n=0) + if( fModel.indexOf(ModelSrc)>=0) { HasModel=true; break; diff --git a/resources/web/guide/22/test.js b/resources/web/guide/22/test.js index 2fc6f9b254..6acd8f705a 100644 --- a/resources/web/guide/22/test.js +++ b/resources/web/guide/22/test.js @@ -1,511 +1,364 @@ var cData={ - "filament": { - "BBL PA-CF @BBL": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "BBL PA-CF @BBL", - "selected": 1, - "sub_path": "filament/BBL PA-CF @BBL.json", - "type": "PA6+CF", - "vendor": "BBL" - }, - "Generic ABS": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "Generic ABS", - "selected": 1, - "sub_path": "filament/Generic ABS.json", - "type": "ABS", - "vendor": "Unknow" - }, - "Generic PETG": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "Generic PETG", - "selected": 1, - "sub_path": "filament/Generic PETG.json", - "type": "PET", - "vendor": "Unknow" - }, - "Generic PLA": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "Generic PLA", - "selected": 1, - "sub_path": "filament/Generic PLA.json", - "type": "PLA", - "vendor": "Unknow" - }, - "Generic TPU": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "Generic TPU", - "selected": 0, - "sub_path": "filament/Generic TPU.json", - "type": "TPU", - "vendor": "Unknow" - }, - "Generic TPU83": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "Generic TPU83", - "selected": 1, - "sub_path": "filament/Generic TPU83.json", - "type": "TPU", - "vendor": "Unknow" - }, - "Generic TPU87": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "Generic TPU87", - "selected": 1, - "sub_path": "filament/Generic TPU87.json", - "type": "TPU", - "vendor": "Unknow" - }, - "Generic TPU90": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "Generic TPU90", - "selected": 0, - "sub_path": "filament/Generic TPU90.json", - "type": "TPU", - "vendor": "Unknow" - }, - "Generic TPU95": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "Generic TPU95", - "selected": 1, - "sub_path": "filament/Generic TPU95.json", - "type": "TPU", - "vendor": "Unknow" - }, - "K5 ABS @Kexcelled": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "K5 ABS @Kexcelled", - "selected": 0, - "sub_path": "filament/K5 ABS @Kexcelled.json", - "type": "ABS", - "vendor": "Kexcelled" - }, - "K5 ASA @Kexcelled": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "K5 ASA @Kexcelled", - "selected": 0, - "sub_path": "filament/K5 ASA @Kexcelled.json", - "type": "ABS", - "vendor": "Kexcelled" - }, - "K5 PETG @Kexcelled": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "K5 PETG @Kexcelled", - "selected": 0, - "sub_path": "filament/K5 PETG @Kexcelled.json", - "type": "PET", - "vendor": "Kexcelled" - }, - "K5 PLA Magic @Kexcelled": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "K5 PLA Magic @Kexcelled", - "selected": 0, - "sub_path": "filament/K5 PLA Magic @Kexcelled.json", - "type": "PLA", - "vendor": "Kexcelled" - }, - "K5 PLA Wood @Kexcelled": { - "models": "", - "name": "K5 PLA Wood @Kexcelled", - "selected": 0, - "sub_path": "filament/K5 PLA Wood @Kexcelled.json", - "type": "PLA", - "vendor": "Kexcelled" - }, - "K5 PLA~ @Kexcelled": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "K5 PLA~ @Kexcelled", - "selected": 0, - "sub_path": "filament/K5 PLA~ @Kexcelled.json", - "type": "PLA", - "vendor": "Kexcelled" - }, - "K5 Silk PLA~ @Kexcelled": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "K5 Silk PLA~ @Kexcelled", - "selected": 0, - "sub_path": "filament/K5 Silk PLA~ @Kexcelled.json", - "type": "PLA", - "vendor": "Kexcelled" - }, - "K5 Sparkle PLA @Kexcelled": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "K5 Sparkle PLA @Kexcelled", - "selected": 0, - "sub_path": "filament/K5 Sparkle PLA @Kexcelled.json", - "type": "PLA", - "vendor": "Kexcelled" - }, - "K5M PLA~ @Kexcelled": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "K5M PLA~ @Kexcelled", - "selected": 0, - "sub_path": "filament/K5M PLA~ @Kexcelled.json", - "type": "PLA", - "vendor": "Kexcelled" - }, - "K5P PLA~ @Kexcelled": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "K5P PLA~ @Kexcelled", - "selected": 0, - "sub_path": "filament/K5P PLA~ @Kexcelled.json", - "type": "PLA", - "vendor": "Kexcelled" - }, - "K5T ABS @Kexcelled": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "K5T ABS @Kexcelled", - "selected": 0, - "sub_path": "filament/K5T ABS @Kexcelled.json", - "type": "ABS", - "vendor": "Kexcelled" - }, - "K6 PETG @Kexcelled": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "K6 PETG @Kexcelled", - "selected": 0, - "sub_path": "filament/K6 PETG @Kexcelled.json", - "type": "PET", - "vendor": "Kexcelled" - }, - "K6 PLA~ @Kexcelled": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "K6 PLA~ @Kexcelled", - "selected": 0, - "sub_path": "filament/K6 PLA~ @Kexcelled.json", - "type": "PLA", - "vendor": "Kexcelled" - }, - "K6CF PLA~ @Kexcelled ": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "K6CF PLA~ @Kexcelled ", - "selected": 0, - "sub_path": "filament/K6CF PLA~ @Kexcelled.json", - "type": "PLA", - "vendor": "Kexcelled" - }, - "K7 PC @Kexcelled ": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "K7 PC @Kexcelled ", - "selected": 0, - "sub_path": "filament/K7 PC @Kexcelled.json", - "type": "PC", - "vendor": "Kexcelled" - }, - "K7CF PAHT @Kexcelled": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "K7CF PAHT @Kexcelled", - "selected": 0, - "sub_path": "filament/K7CF PAHT @Kexcelled.json", - "type": "PA6+CF", - "vendor": "Kexcelled" - }, - "K7CF PET @Kexcelled": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "K7CF PET @Kexcelled", - "selected": 0, - "sub_path": "filament/K7CF PET @Kexcelled.json", - "type": "PET", - "vendor": "Kexcelled" - }, - "K7CFLM PAHT @Kexcelled": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "K7CFLM PAHT @Kexcelled", - "selected": 0, - "sub_path": "filament/K7CFLM PAHT @Kexcelled.json", - "type": "PA6+CF", - "vendor": "Kexcelled" - }, - "PLA Silk with Glue @ALL": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "PLA Silk with Glue @ALL", - "selected": 0, - "sub_path": "filament/PLA Silk with Glue @ALL.json", - "type": "PLA", - "vendor": "ALL" - }, - "PLA with Glue except Silk @ALL": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "PLA with Glue except Silk @ALL", - "selected": 0, - "sub_path": "filament/PLA with Glue except Silk @ALL.json", - "type": "PLA", - "vendor": "ALL" - }, - "PolyDissolve @Polymaker": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "PolyDissolve @Polymaker", - "selected": 0, - "sub_path": "filament/PolyDissolve @Polymaker.json", - "type": "PLA", - "vendor": "Polymaker" - }, - "PolyFlex TPU95HF @Polymaker": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "PolyFlex TPU95HF @Polymaker", - "selected": 0, - "sub_path": "filament/PolyFlex TPU95HF @Polymaker.json", - "type": "TPU", - "vendor": "Polymaker" - }, - "PolyLite ABS @Polymaker": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "PolyLite ABS @Polymaker", - "selected": 0, - "sub_path": "filament/PolyLite ABS @Polymaker.json", - "type": "ABS", - "vendor": "Polymaker" - }, - "PolyLite ASA @Polymaker": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "PolyLite ASA @Polymaker", - "selected": 0, - "sub_path": "filament/PolyLite ASA @Polymaker.json", - "type": "ABS", - "vendor": "Polymaker" - }, - "PolyLite PC @Polymaker": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "PolyLite PC @Polymaker", - "selected": 0, - "sub_path": "filament/PolyLite PC @Polymaker.json", - "type": "PC", - "vendor": "Polymaker" - }, - "PolyLite PETG @Polymaker": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "PolyLite PETG @Polymaker", - "selected": 0, - "sub_path": "filament/PolyLite PETG @Polymaker.json", - "type": "PET", - "vendor": "Polymaker" - }, - "PolyLite PLA Pro~ @Polymaker": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "PolyLite PLA Pro~ @Polymaker", - "selected": 0, - "sub_path": "filament/PolyLite PLA Pro~ @Polymaker.json", - "type": "PLA", - "vendor": "Polymaker" - }, - "PolyLite PLA Silk~ @Polymaker": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "PolyLite PLA Silk~ @Polymaker", - "selected": 0, - "sub_path": "filament/PolyLite PLA Silk~ @Polymaker.json", - "type": "PLA", - "vendor": "Polymaker" - }, - "PolyLite PLA~ @Polymaker": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "PolyLite PLA~ @Polymaker", - "selected": 0, - "sub_path": "filament/PolyLite PLA~ @Polymaker.json", - "type": "PLA", - "vendor": "Polymaker" - }, - "PolyMax PC @Polymaker": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "PolyMax PC @Polymaker", - "selected": 0, - "sub_path": "filament/PolyMax PC @Polymaker.json", - "type": "PC", - "vendor": "Polymaker" - }, - "PolyMax PETG @Polymaker": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "PolyMax PETG @Polymaker", - "selected": 0, - "sub_path": "filament/PolyMax PETG @Polymaker.json", - "type": "PET", - "vendor": "Polymaker" - }, - "PolyMax PLA~ @Polymaker": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "PolyMax PLA~ @Polymaker", - "selected": 0, - "sub_path": "filament/PolyMax PLA~ @Polymaker.json", - "type": "PLA", - "vendor": "Polymaker" - }, - "PolyMide CoPA @Polymaker": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "PolyMide CoPA @Polymaker", - "selected": 0, - "sub_path": "filament/PolyMide CoPA @Polymaker.json", - "type": "PA6+CF", - "vendor": "Polymaker" - }, - "PolyMide PA12-CF @Polymaker": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "PolyMide PA12-CF @Polymaker", - "selected": 0, - "sub_path": "filament/PolyMide PA12-CF @Polymaker.json", - "type": "PA6+CF", - "vendor": "Polymaker" - }, - "PolyMide PA6-CF @Polymaker": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "PolyMide PA6-CF @Polymaker", - "selected": 0, - "sub_path": "filament/PolyMide PA6-CF @Polymaker.json", - "type": "PA6+CF", - "vendor": "Polymaker" - }, - "PolyMide PA6-GF @Polymaker": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "PolyMide PA6-GF @Polymaker", - "selected": 0, - "sub_path": "filament/PolyMide PA6-GF @Polymaker.json", - "type": "PA6+CF", - "vendor": "Polymaker" - }, - "PolySupport @Polymaker": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "PolySupport @Polymaker", - "selected": 0, - "sub_path": "filament/PolySupport @Polymaker.json", - "type": "PLA", - "vendor": "Polymaker" - }, - "PolyTerra PLA~ @Polymaker": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "PolyTerra PLA~ @Polymaker", - "selected": 0, - "sub_path": "filament/PolyTerra PLA~ @Polymaker.json", - "type": "PLA", - "vendor": "Polymaker" - }, - "PolyWood PLA~ @Polymaker": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "PolyWood PLA~ @Polymaker", - "selected": 0, - "sub_path": "filament/PolyWood PLA~ @Polymaker.json", - "type": "PLA", - "vendor": "Polymaker" - }, - "eSUN ABS @eSUN": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "eSUN ABS @eSUN", - "selected": 0, - "sub_path": "filament/eSUN ABS @eSUN.json", - "type": "ABS", - "vendor": "eSUN" - }, - "eSUN ABS+ @eSUN": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "eSUN ABS+ @eSUN", - "selected": 0, - "sub_path": "filament/eSUN ABS+ @eSUN.json", - "type": "ABS", - "vendor": "eSUN" - }, - "eSUN PETG @eSUN": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "eSUN PETG @eSUN", - "selected": 0, - "sub_path": "filament/eSUN PETG @eSUN.json", - "type": "PET", - "vendor": "eSUN" - }, - "eSUN PLA @eSUN": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "eSUN PLA @eSUN", - "selected": 0, - "sub_path": "filament/eSUN PLA @eSUN.json", - "type": "PLA", - "vendor": "eSUN" - }, - "eSUN PLA Matte~ @eSUN": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "eSUN PLA Matte~ @eSUN", - "selected": 0, - "sub_path": "filament/eSUN PLA Matte~ @eSUN.json", - "type": "PLA", - "vendor": "eSUN" - }, - "eSUN PLA ST @eSUN": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "eSUN PLA ST @eSUN", - "selected": 0, - "sub_path": "filament/eSUN PLA ST @eSUN.json", - "type": "PLA", - "vendor": "eSUN" - }, - "eSUN PLA Silk~ @eSUN": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "eSUN PLA Silk~ @eSUN", - "selected": 0, - "sub_path": "filament/eSUN PLA Silk~ @eSUN.json", - "type": "PLA", - "vendor": "eSUN" - }, - "eSUN PLA+~ @eSUN": { - "models": "[BBL-3DP-V5NORMAL]", - "name": "eSUN PLA+~ @eSUN", - "selected": 0, - "sub_path": "filament/eSUN PLA+~ @eSUN.json", - "type": "PLA", - "vendor": "eSUN" - } + "filament": { + "Bambu ABS @BBL X1C": { + "models": "[Bambu Lab X1 Carbon++0.4][Bambu Lab X1++0.4][Bambu Lab X1 Carbon++0.6]", + "name": "Bambu ABS @BBL X1C", + "selected": 1, + "sub_path": "filament/Bambu ABS @BBL X1C.json", + "type": "ABS", + "vendor": "Bambu Lab" }, - "machine": [ - { - "model": "BBL-3DP-V5NORMAL", - "name": "Bambulab BBL-3DP-001-V5-normal", - "sub_path": "machine/Bambulab BBL-3DP-001-V5-normal.json" - } - ], - "model": [ - { - "cover": "E:\\Document\\DevCode\\Slicer2\\bamboo_slicer\\build\\src\\Debug\\resources\\profiles\\BBL\\BBL-3DP-V5NORMAL_cover.png", - "materials": "Generic PLA;PolyDissolve @Polymaker;PolyFlex TPU95HF @Polymaker;K5 Sparkle PLA @Kexcelled;Rock PLA @Polymaker;Generic TPU95;Generic TPU90;Generic TPU87;Generic TPU83;PolyMide CoPA @Polymaker;PolyMide PA6-GF @Polymaker;PolyMide PA12-CF @Polymaker;PolyMide PA6-CF @Polymaker;PolyLite ASA @Polymaker;K5 ASA @Kexcelled;K5T ABS @Kexcelled;PLA with Glue except Silk @ALL;PLA Silk with Glue @ALL;PolyTerra PLA~ @Polymaker; PolyLite PLA~ @Polymaker; PolyLite PLA Pro~ @Polymaker; PolyLite PLA Silk~ @Polymaker; PolyMax PLA~ @Polymaker; PolyWood PLA~ @Polymaker;K5 Silk PLA~ @Kexcelled;K5 PLA~ @Kexcelled;K6 PLA~ @Kexcelled;K6CF PLA~ @Kexcelled;K5M PLA~ @Kexcelled;K5P PLA~ @Kexcelled;eSUN PLA @eSUN; eSUN PLA+~ @eSUN; eSUN PLA Matte~ @eSUN; eSUN PLA Silk~ @eSUN; eSUN PLA ST @eSUN; PolyLite ABS @Polymaker; K5 ABS @Kexcelled; eSUN ABS @eSUN; eSUN ABS+ @eSUN; K5 PETG @Kexcelled; K6 PETG @Kexcelled; PolyMax PETG @Polymaker; PolyLite PETG @Polymaker; eSUN PETG @eSUN; PolySupport @Polymaker;K7CF PET @Kexcelled;K7CFLM PAHT @Kexcelled;K7CF PAHT @Kexcelled; K7LM PAHT @Kexcelled; BBL PA-CF @BBL;K7 PC @Kexcelled; PolyLite PC @Polymaker; PolyMax PC @Polymaker;K5 PLA Magic @Kexcelled;", - "model": "BBL-3DP-V5NORMAL", - "nozzle_diameter": "0.4;0.2", - "nozzle_selected": "0.4", - "sub_path": "machine/BBL-3DP-V5NORMAL.json", - "vendor": "BBL" - } - ], - "process": [ - { - "name": "0.08mm SUPERDETAIL @BBL-3DP", - "sub_path": "process/0.08mm SUPERDETAIL @BBL-3DP.json" - }, - { - "name": "0.10mm HIGHDETAIL @BBL-3DP", - "sub_path": "process/0.10mm HIGHDETAIL @BBL-3DP.json" - }, - { - "name": "0.12mm DETAIL @BBL-3DP", - "sub_path": "process/0.12mm DETAIL @BBL-3DP.json" - }, - { - "name": "0.16mm OPTIMAL @BBL-3DP", - "sub_path": "process/0.16mm OPTIMAL @BBL-3DP.json" - }, - { - "name": "0.20mm NORMAL @BBL-3DP", - "sub_path": "process/0.20mm NORMAL @BBL-3DP.json" - }, - { - "name": "0.24mm DRAFT @BBL-3DP", - "sub_path": "process/0.24mm DRAFT @BBL-3DP.json" - }, - { - "name": "0.28mm SUPERDRAFT @BBL-3DP", - "sub_path": "process/0.28mm SUPERDRAFT @BBL-3DP.json" - }, - { - "name": "0.20mm TreeSupport @BBL-3DP", - "sub_path": "process/0.20mm TreeSupport @BBL-3DP.json" - }, - { - "name": "0.20mm PolySupport @BBL-3DP", - "sub_path": "process/0.20mm PolySupport @BBL-3DP.json" - } - ] - }; + "Bambu ABS @BBL X1C 0.2 nozzle": { + "models": "[Bambu Lab X1 Carbon++0.2]", + "name": "Bambu ABS @BBL X1C 0.2 nozzle", + "selected": 1, + "sub_path": "filament/Bambu ABS @BBL X1C 0.2 nozzle.json", + "type": "ABS", + "vendor": "Bambu Lab" + }, + "Bambu ABS @BBL X1C 0.8 nozzle": { + "models": "[Bambu Lab X1 Carbon++0.8]", + "name": "Bambu ABS @BBL X1C 0.8 nozzle", + "selected": 1, + "sub_path": "filament/Bambu ABS @BBL X1C 0.8 nozzle.json", + "type": "ABS", + "vendor": "Bambu Lab" + }, + "Bambu PA-CF @BBL X1C": { + "models": "[Bambu Lab X1 Carbon++0.4]", + "name": "Bambu PA-CF @BBL X1C", + "selected": 0, + "sub_path": "filament/Bambu PA-CF @BBL X1C.json", + "type": "PA-CF", + "vendor": "Bambu Lab" + }, + "Bambu PC @BBL X1C": { + "models": "[Bambu Lab X1 Carbon++0.4][Bambu Lab X1++0.4][Bambu Lab X1 Carbon++0.6]", + "name": "Bambu PC @BBL X1C", + "selected": 1, + "sub_path": "filament/Bambu PC @BBL X1C.json", + "type": "PC", + "vendor": "Bambu Lab" + }, + "Bambu PC @BBL X1C 0.8 nozzle": { + "models": "[Bambu Lab X1 Carbon++0.8]", + "name": "Bambu PC @BBL X1C 0.8 nozzle", + "selected": 1, + "sub_path": "filament/Bambu PC @BBL X1C 0.8 nozzle.json", + "type": "PC", + "vendor": "Bambu Lab" + }, + "Bambu PLA Basic @BBL X1": { + "models": "[Bambu Lab X1++0.4]", + "name": "Bambu PLA Basic @BBL X1", + "selected": 1, + "sub_path": "filament/Bambu PLA Basic @BBL X1.json", + "type": "PLA", + "vendor": "Bambu Lab" + }, + "Bambu PLA Basic @BBL X1C": { + "models": "[Bambu Lab X1 Carbon++0.4][Bambu Lab X1 Carbon++0.6]", + "name": "Bambu PLA Basic @BBL X1C", + "selected": 1, + "sub_path": "filament/Bambu PLA Basic @BBL X1C.json", + "type": "PLA", + "vendor": "Bambu Lab" + }, + "Bambu PLA Basic @BBL X1C 0.2 nozzle": { + "models": "[Bambu Lab X1 Carbon++0.2]", + "name": "Bambu PLA Basic @BBL X1C 0.2 nozzle", + "selected": 1, + "sub_path": "filament/Bambu PLA Basic @BBL X1C 0.2 nozzle.json", + "type": "PLA", + "vendor": "Bambu Lab" + }, + "Bambu PLA Basic @BBL X1C 0.8 nozzle": { + "models": "[Bambu Lab X1 Carbon++0.8]", + "name": "Bambu PLA Basic @BBL X1C 0.8 nozzle", + "selected": 1, + "sub_path": "filament/Bambu PLA Basic @BBL X1C 0.8 nozzle.json", + "type": "PLA", + "vendor": "Bambu Lab" + }, + "Bambu PLA Matte @BBL X1": { + "models": "[Bambu Lab X1++0.4]", + "name": "Bambu PLA Matte @BBL X1", + "selected": 1, + "sub_path": "filament/Bambu PLA Matte @BBL X1.json", + "type": "PLA", + "vendor": "Bambu Lab" + }, + "Bambu PLA Matte @BBL X1C": { + "models": "[Bambu Lab X1 Carbon++0.4][Bambu Lab X1 Carbon++0.6]", + "name": "Bambu PLA Matte @BBL X1C", + "selected": 1, + "sub_path": "filament/Bambu PLA Matte @BBL X1C.json", + "type": "PLA", + "vendor": "Bambu Lab" + }, + "Bambu PLA Matte @BBL X1C 0.2 nozzle": { + "models": "[Bambu Lab X1 Carbon++0.2]", + "name": "Bambu PLA Matte @BBL X1C 0.2 nozzle", + "selected": 1, + "sub_path": "filament/Bambu PLA Matte @BBL X1C 0.2 nozzle.json", + "type": "PLA", + "vendor": "Bambu Lab" + }, + "Bambu PLA Matte @BBL X1C 0.8 nozzle": { + "models": "[Bambu Lab X1 Carbon++0.8]", + "name": "Bambu PLA Matte @BBL X1C 0.8 nozzle", + "selected": 1, + "sub_path": "filament/Bambu PLA Matte @BBL X1C 0.8 nozzle.json", + "type": "PLA", + "vendor": "Bambu Lab" + }, + "Bambu Support G @BBL X1C": { + "models": "[Bambu Lab X1 Carbon++0.4]", + "name": "Bambu Support G @BBL X1C", + "selected": 0, + "sub_path": "filament/Bambu Support G @BBL X1C.json", + "type": "PA", + "vendor": "Bambu Lab" + }, + "Bambu Support W @BBL X1": { + "models": "[Bambu Lab X1++0.4]", + "name": "Bambu Support W @BBL X1", + "selected": 1, + "sub_path": "filament/Bambu Support W @BBL X1.json", + "type": "PLA", + "vendor": "Bambu Lab" + }, + "Bambu Support W @BBL X1C": { + "models": "[Bambu Lab X1 Carbon++0.4]", + "name": "Bambu Support W @BBL X1C", + "selected": 1, + "sub_path": "filament/Bambu Support W @BBL X1C.json", + "type": "PLA", + "vendor": "Bambu Lab" + }, + "Bambu TPU 95A @BBL X1": { + "models": "[Bambu Lab X1++0.4]", + "name": "Bambu TPU 95A @BBL X1", + "selected": 1, + "sub_path": "filament/Bambu TPU 95A @BBL X1.json", + "type": "TPU", + "vendor": "Bambu Lab" + }, + "Bambu TPU 95A @BBL X1C": { + "models": "[Bambu Lab X1 Carbon++0.4][Bambu Lab X1 Carbon++0.6][Bambu Lab X1 Carbon++0.8]", + "name": "Bambu TPU 95A @BBL X1C", + "selected": 1, + "sub_path": "filament/Bambu TPU 95A @BBL X1C.json", + "type": "TPU", + "vendor": "Bambu Lab" + }, + "Generic ABS": { + "models": "[Bambu Lab X1 Carbon++0.4][Bambu Lab X1++0.4]", + "name": "Generic ABS", + "selected": 0, + "sub_path": "filament/Generic ABS.json", + "type": "ABS", + "vendor": "Generic" + }, + "Generic ASA": { + "models": "[Bambu Lab X1 Carbon++0.4][Bambu Lab X1++0.4]", + "name": "Generic ASA", + "selected": 0, + "sub_path": "filament/Generic ASA.json", + "type": "ASA", + "vendor": "Generic" + }, + "Generic PA": { + "models": "[Bambu Lab X1 Carbon++0.4]", + "name": "Generic PA", + "selected": 0, + "sub_path": "filament/Generic PA.json", + "type": "PA", + "vendor": "Generic" + }, + "Generic PA-CF": { + "models": "[Bambu Lab X1 Carbon++0.4]", + "name": "Generic PA-CF", + "selected": 0, + "sub_path": "filament/Generic PA-CF.json", + "type": "PA-CF", + "vendor": "Generic" + }, + "Generic PC": { + "models": "[Bambu Lab X1 Carbon++0.4]", + "name": "Generic PC", + "selected": 0, + "sub_path": "filament/Generic PC.json", + "type": "PC", + "vendor": "Generic" + }, + "Generic PETG": { + "models": "[Bambu Lab X1 Carbon++0.4][Bambu Lab X1++0.4]", + "name": "Generic PETG", + "selected": 0, + "sub_path": "filament/Generic PETG.json", + "type": "PETG", + "vendor": "Generic" + }, + "Generic PLA": { + "models": "[Bambu Lab X1 Carbon++0.4][Bambu Lab X1++0.4]", + "name": "Generic PLA", + "selected": 1, + "sub_path": "filament/Generic PLA.json", + "type": "PLA", + "vendor": "Generic" + }, + "Generic PLA-CF": { + "models": "[Bambu Lab X1 Carbon++0.4]", + "name": "Generic PLA-CF", + "selected": 0, + "sub_path": "filament/Generic PLA-CF.json", + "type": "PLA-CF", + "vendor": "Generic" + }, + "Generic PVA": { + "models": "[Bambu Lab X1 Carbon++0.4][Bambu Lab X1++0.4]", + "name": "Generic PVA", + "selected": 0, + "sub_path": "filament/Generic PVA.json", + "type": "PVA", + "vendor": "Generic" + }, + "Generic TPU": { + "models": "[Bambu Lab X1 Carbon++0.4][Bambu Lab X1++0.4]", + "name": "Generic TPU", + "selected": 0, + "sub_path": "filament/Generic TPU.json", + "type": "TPU", + "vendor": "Generic" + }, + "PolyLite PLA @BBL X1": { + "models": "[Bambu Lab X1++0.4]", + "name": "PolyLite PLA @BBL X1", + "selected": 1, + "sub_path": "filament/PolyLite PLA @BBL X1.json", + "type": "PLA", + "vendor": "Polymaker" + }, + "PolyLite PLA @BBL X1C": { + "models": "[Bambu Lab X1 Carbon++0.4]", + "name": "PolyLite PLA @BBL X1C", + "selected": 1, + "sub_path": "filament/PolyLite PLA @BBL X1C.json", + "type": "PLA", + "vendor": "Polymaker" + }, + "PolyTerra PLA @BBL X1": { + "models": "[Bambu Lab X1++0.4]", + "name": "PolyTerra PLA @BBL X1", + "selected": 1, + "sub_path": "filament/PolyTerra PLA @BBL X1.json", + "type": "PLA", + "vendor": "Polymaker" + }, + "PolyTerra PLA @BBL X1C": { + "models": "[Bambu Lab X1 Carbon++0.4]", + "name": "PolyTerra PLA @BBL X1C", + "selected": 1, + "sub_path": "filament/PolyTerra PLA @BBL X1C.json", + "type": "PLA", + "vendor": "Polymaker" + } + }, + "machine": { + "Bambu Lab X1 0.4 nozzle": { + "model": "Bambu Lab X1", + "name": "Bambu Lab X1 0.4 nozzle", + "nozzle": "0.4", + "sub_path": "machine/Bambu Lab X1 0.4 nozzle.json" + }, + "Bambu Lab X1 Carbon 0.2 nozzle": { + "model": "Bambu Lab X1 Carbon", + "name": "Bambu Lab X1 Carbon 0.2 nozzle", + "nozzle": "0.2", + "sub_path": "machine/Bambu Lab X1 Carbon 0.2 nozzle.json" + }, + "Bambu Lab X1 Carbon 0.4 nozzle": { + "model": "Bambu Lab X1 Carbon", + "name": "Bambu Lab X1 Carbon 0.4 nozzle", + "nozzle": "0.4", + "sub_path": "machine/Bambu Lab X1 Carbon 0.4 nozzle.json" + }, + "Bambu Lab X1 Carbon 0.6 nozzle": { + "model": "Bambu Lab X1 Carbon", + "name": "Bambu Lab X1 Carbon 0.6 nozzle", + "nozzle": "0.6", + "sub_path": "machine/Bambu Lab X1 Carbon 0.6 nozzle.json" + }, + "Bambu Lab X1 Carbon 0.8 nozzle": { + "model": "Bambu Lab X1 Carbon", + "name": "Bambu Lab X1 Carbon 0.8 nozzle", + "nozzle": "0.8", + "sub_path": "machine/Bambu Lab X1 Carbon 0.8 nozzle.json" + } + }, + "model": [ + { + "cover": "C:\\Users\\zorro\\AppData\\Roaming\\BambuStudio\\system\\BBL\\Bambu Lab X1 Carbon_cover.png", + "materials": "Generic PLA;Bambu PLA Matte @BBL X1C;Bambu PLA Basic @BBL X1C;Bambu ABS @BBL X1C;Bambu PC @BBL X1C;Bambu Support W @BBL X1C;Bambu TPU 95A @BBL X1C;PolyTerra PLA @BBL X1C;PolyLite PLA @BBL X1C;", + "model": "Bambu Lab X1 Carbon", + "nozzle_diameter": "0.4;0.2;0.6;0.8", + "nozzle_selected": "0.2;0.6", + "sub_path": "machine/Bambu Lab X1 Carbon.json", + "vendor": "BBL" + }, + { + "cover": "C:\\Users\\zorro\\AppData\\Roaming\\BambuStudio\\system\\BBL\\Bambu Lab X1_cover.png", + "materials": "Generic PLA;Bambu PLA Matte @BBL X1;Bambu PLA Basic @BBL X1;Bambu ABS @BBL X1C;Bambu PC @BBL X1C;Bambu Support W @BBL X1;Bambu TPU 95A @BBL X1;PolyTerra PLA @BBL X1;PolyLite PLA @BBL X1;", + "model": "Bambu Lab X1", + "nozzle_diameter": "0.4", + "nozzle_selected": "0.4", + "sub_path": "machine/Bambu Lab X1.json", + "vendor": "BBL" + } + ], + "network_plugin_compability": "0", + "network_plugin_install": "1", + "process": [ + { + "name": "0.08mm Extra Fine @BBL X1C", + "sub_path": "process/0.08mm Extra Fine @BBL X1C.json" + }, + { + "name": "0.12mm Fine @BBL X1C", + "sub_path": "process/0.12mm Fine @BBL X1C.json" + }, + { + "name": "0.16mm Optimal @BBL X1C", + "sub_path": "process/0.16mm Optimal @BBL X1C.json" + }, + { + "name": "0.20mm Standard @BBL X1C", + "sub_path": "process/0.20mm Standard @BBL X1C.json" + }, + { + "name": "0.24mm Draft @BBL X1C", + "sub_path": "process/0.24mm Draft @BBL X1C.json" + }, + { + "name": "0.28mm Extra Draft @BBL X1C", + "sub_path": "process/0.28mm Extra Draft @BBL X1C.json" + }, + { + "name": "0.10mm Standard @BBL X1C 0.2 nozzle", + "sub_path": "process/0.10mm Standard @BBL X1C 0.2 nozzle.json" + }, + { + "name": "0.30mm Standard @BBL X1C 0.6 nozzle", + "sub_path": "process/0.30mm Standard @BBL X1C 0.6 nozzle.json" + }, + { + "name": "0.40mm Standard @BBL X1C 0.8 nozzle", + "sub_path": "process/0.40mm Standard @BBL X1C 0.8 nozzle.json" + } + ], + "region": "North America" +}; var mData={ diff --git a/resources/web/guide/23/23.js b/resources/web/guide/23/23.js index a736b2c5b3..eb7b73fa7c 100644 --- a/resources/web/guide/23/23.js +++ b/resources/web/guide/23/23.js @@ -59,7 +59,7 @@ function SortUI() let OneMode=m_ProfileItem["model"][n]; if( OneMode["nozzle_selected"]!="" ) - ModelList.push(OneMode["model"]); + ModelList.push(OneMode); } //machine @@ -86,14 +86,14 @@ function SortUI() // $('#MachineList').hide(); // } - //machine + //model let HtmlMode=''; nMode=ModelList.length; for(let n=0;n'+sModel+''; + HtmlMode+='
'+sModel['model']+'
'; } $('#MachineList .CValues').append(HtmlMode); @@ -119,14 +119,16 @@ function SortUI() let fSelect=OneFila['selected']; let fModel=OneFila['models'] -// if(OneFila['name'].indexOf("K5 PLA Wood")>0) +// if(OneFila['name'].indexOf("Bambu PA-CF")>=0) // { +// alert( fShortName+' - '+fVendor+' - '+fType+' - '+fSelect+' - '+fModel ) +// // let b=1+2; // } let bFind=false; - let bCheck=$("#MachineList input:first").prop("checked"); - if(bCheck) + //let bCheck=$("#MachineList input:first").prop("checked"); + if( fModel=='') { bFind=true; } @@ -138,11 +140,20 @@ function SortUI() { let sOne=ModelList[m]; - if(fModel.indexOf(sOne)>=0) + let OneName=sOne['model']; + let NozzleArray=sOne["nozzle_selected"].split(';'); + + let nNozzle=NozzleArray.length; + + for( let b=0;b=0) { bFind=true; break; - } + } + } } } @@ -325,7 +336,24 @@ function SortFilament() for(let n=0;n=0) + if( fModel.indexOf(ModelSrc)>=0) { HasModel=true; break; diff --git a/resources/web/homepage/index.html b/resources/web/homepage/index.html index b2f6a246e3..d05753b885 100644 --- a/resources/web/homepage/index.html +++ b/resources/web/homepage/index.html @@ -145,19 +145,18 @@ document.onkeydown = function (event) { var e = event || window.event || arguments.callee.caller.arguments[0]; + if (e.ctrlKey && e.metaKey) + OutputKey(e.keyCode, true, false, true); + else if (e.ctrlKey) + OutputKey(e.keyCode, true, false, false); + else if (e.metaKey) + OutputKey(e.keyCode, false, false, true); + if (e.shiftKey && e.ctrlKey) OutputKey(e.keyCode, true, true, false); - else if (e.ctrlKey) - OutputKey(e.keyCode, true, false, false); - else if (e.keyCode === 27) - OutputKey(e.keyCode, false, false, false); if (e.shiftKey && e.metaKey) OutputKey(e.keyCode, false, true, true); - else if (e.metaKey) - OutputKey(e.keyCode, false, false, true); - else if (e.keyCode === 27) - OutputKey(e.keyCode, false, false, false); if (window.event) { try { e.keyCode = 0; } catch (e) { } diff --git a/src/BambuStudio.cpp b/src/BambuStudio.cpp index dc3a00a8fe..6585ece0a8 100644 --- a/src/BambuStudio.cpp +++ b/src/BambuStudio.cpp @@ -143,7 +143,7 @@ int CLI::run(int argc, char **argv) return 1; } BOOST_LOG_TRIVIAL(info) << "finished setup params, argc="<< argc << std::endl; - std::string temp_path = wxStandardPaths::Get().GetTempDir().utf8_str().data(); + std::string temp_path = wxFileName::GetTempDir().utf8_str().data(); set_temporary_dir(temp_path); m_extra_config.apply(m_config, true); diff --git a/src/imgui/imgui.h b/src/imgui/imgui.h index 086ae77f27..4b3c13ac60 100644 --- a/src/imgui/imgui.h +++ b/src/imgui/imgui.h @@ -658,6 +658,7 @@ namespace ImGui IMGUI_API void EndMainMenuBar(); // only call EndMainMenuBar() if BeginMainMenuBar() returns true! IMGUI_API bool BeginMenu(const char* label, bool enabled = true); // create a sub-menu entry. only call EndMenu() if this returns true! IMGUI_API void EndMenu(); // only call EndMenu() if BeginMenu() returns true! + IMGUI_API bool BBLMenuItem(const char* label, const char* shortcut = NULL, bool selected = false, bool enabled = true); // return true when activated. IMGUI_API bool MenuItem(const char* label, const char* shortcut = NULL, bool selected = false, bool enabled = true); // return true when activated. IMGUI_API bool MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled = true); // return true when activated + toggle (*p_selected) if p_selected != NULL diff --git a/src/imgui/imgui_widgets.cpp b/src/imgui/imgui_widgets.cpp index c363347e5d..a9b59d241b 100644 --- a/src/imgui/imgui_widgets.cpp +++ b/src/imgui/imgui_widgets.cpp @@ -283,7 +283,15 @@ void ImGui::TextAlignCenter(const char *label) float item_width = ImGui::CalcItemWidth(); float font_size = ImGui::GetFontSize() * strlen(label) / 2; ImGui::SameLine(ImGui::GetCursorPos().x + (item_width - font_size) / 2); - ImGui::Text(label); + + if ('X' == *label) + ImGui::TextColored(ImVec4(1.0, 0.0, 0.0, 1.0),label); + else if ('Y' == *label) + ImGui::TextColored(ImVec4(0.0, 0.6, 0.2, 1.0),label); + else if ('Z' == *label) + ImGui::TextColored(ImVec4(0.0, 0.0, 1.0, 1.0),label); + else + ImGui::Text(label); } void ImGui::TextV(const char* fmt, va_list args) @@ -2070,7 +2078,7 @@ bool ImGui::BBLBeginCombo(const char *label, const char *preview_value, ImGuiCom const ImVec2 label_size = CalcTextSize(label, NULL, true); const float expected_w = CalcItemWidth(); const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : expected_w; - const ImRect frame_bb(window->DC.CursorPos - ImVec2(0.0, style.FramePadding.y), window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y)); + const ImRect frame_bb(window->DC.CursorPos - ImVec2(0.0, style.FramePadding.y), window->DC.CursorPos + ImVec2(w - arrow_size * 2, label_size.y + style.FramePadding.y)); const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); ItemSize(total_bb, style.FramePadding.y); if (!ItemAdd(total_bb, id, &frame_bb)) return false; @@ -2128,7 +2136,7 @@ bool ImGui::BBLBeginCombo(const char *label, const char *preview_value, ImGuiCom popup_max_height_in_items = 4; else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20; - SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items))); + SetNextWindowSizeConstraints(ImVec2(w - arrow_size * 2, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items))); } char name[16]; @@ -7096,7 +7104,11 @@ bool ImGui::BBLSelectable(const char *label, bool selected, ImGuiSelectableFlags bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); if (hovered || g.ActiveId == id) { ImGui::PushStyleColor(ImGuiCol_Border, GetColorU32(ImGuiCol_BorderActive)); - RenderFrameBorder(ImVec2(bb.Min.x + style.ItemSpacing.x,bb.Min.y), ImVec2(bb.Max.x - style.ItemSpacing.x,bb.Max.y), style.FrameRounding); + if(arrow_size == 0) { + RenderFrameBorder(bb.Min, ImVec2(bb.Max.x - style.WindowPadding.x, bb.Max.y), style.FrameRounding); + } else { + RenderFrameBorder(ImVec2(bb.Min.x + style.WindowPadding.x,bb.Min.y), ImVec2(bb.Max.x - style.WindowPadding.x,bb.Max.y), style.FrameRounding); + } ImGui::PopStyleColor(1); } // Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with gamepad/keyboard @@ -7118,7 +7130,11 @@ bool ImGui::BBLSelectable(const char *label, bool selected, ImGuiSelectableFlags if (held && (flags & ImGuiSelectableFlags_DrawHoveredWhenHeld)) hovered = true; if (hovered || selected) { const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); - RenderFrame(ImVec2(bb.Min.x + style.ItemSpacing.x, bb.Min.y), ImVec2(bb.Max.x - style.ItemSpacing.x, bb.Max.y), col, false, 0.0f); + if(arrow_size == 0) { + RenderFrame(bb.Min, ImVec2(bb.Max.x - style.WindowPadding.x, bb.Max.y), col, false, 0.0f); + } else { + RenderFrame(ImVec2(bb.Min.x + style.WindowPadding.x, bb.Min.y), ImVec2(bb.Max.x - style.WindowPadding.x, bb.Max.y), col, false, 0.0f); + } RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding); } @@ -7851,6 +7867,55 @@ void ImGui::EndMenu() EndPopup(); } +bool ImGui::BBLMenuItem(const char* label, const char* shortcut, bool selected, bool enabled) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + ImVec2 pos = window->DC.CursorPos; + ImVec2 label_size = CalcTextSize(label, NULL, true); + + // We've been using the equivalent of ImGuiSelectableFlags_SetNavIdOnHover on all Selectable() since early Nav system days (commit 43ee5d73), + // but I am unsure whether this should be kept at all. For now moved it to be an opt-in feature used by menus only. + ImGuiSelectableFlags flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_SetNavIdOnHover | (enabled ? 0 : ImGuiSelectableFlags_Disabled); + bool pressed; + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) + { + // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful + // Note that in this situation: we don't render the shortcut, we render a highlight instead of the selected tick mark. + float w = label_size.x; + window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f); + PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y)); + pressed = BBLSelectable(label, selected, flags, ImVec2(w, 0.0f)); + PopStyleVar(); + window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). + } + else + { + // Menu item inside a vertical menu + // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f. + // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system. + float shortcut_w = shortcut ? CalcTextSize(shortcut, NULL).x : 0.0f; + float min_w = window->DC.MenuColumns.DeclColumns(label_size.x, shortcut_w, IM_FLOOR(g.FontSize * 1.20f)); // Feedback for next frame + float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w); + pressed = BBLSelectable(label, false, flags | ImGuiSelectableFlags_SpanAvailWidth | ImGuiComboFlags_NoArrowButton, ImVec2(min_w, 0.0f)); + if (shortcut_w > 0.0f) + { + PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + RenderText(pos + ImVec2(window->DC.MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false); + PopStyleColor(); + } + if (selected) + RenderCheckMark(window->DrawList, pos + ImVec2(window->DC.MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize * 0.866f); + } + + IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.LastItemStatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0)); + return pressed; +} + bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled) { ImGuiWindow* window = GetCurrentWindow(); diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index 9e0e7149eb..2cf356401f 100644 --- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -922,27 +922,28 @@ private: #ifdef SVGTOOLS_HPP svg::SVGWriter svgwriter; + svgwriter.setSize(binbb); svgwriter.writeShape(box2RawShape(binbb), "none", "black"); for (int i = 0; i < nfps.size(); i++) svgwriter.writeShape(nfps[i], "none", "blue"); for (int i = 0; i < items_.size(); i++) svgwriter.writeItem(items_[i], "none", "black"); - //for (int i = 0; i < merged_pile_.size(); i++) - // svgwriter.writeShape(merged_pile_[i], "none", "yellow"); + for (int i = 0; i < merged_pile_.size(); i++) + svgwriter.writeShape(merged_pile_[i], "none", "yellow"); + svgwriter.writeItem(item, "none", "red", 2); - svgwriter.writeItem(item, "none", "red"); std::stringstream ss; ss.setf(std::ios::fixed | std::ios::showpoint); ss.precision(1); - ss << "trans=" << round(item.translation().x() / 1e6) << "," << round(item.translation().y() / 1e6) - << "-rot=" << round(item.rotation().toDegrees()) - << "-score=" << round(global_score); + ss << "t=" << round(item.translation().x() / 1e6) << "," << round(item.translation().y() / 1e6) + //<< "-rot=" << round(item.rotation().toDegrees()) + << "-sco=" << round(global_score); svgwriter.draw_text(20, 20, ss.str(), "blue", 20); ss.str(""); ss << "items.size=" << items_.size() << "-merged_pile.size=" << merged_pile_.size(); svgwriter.draw_text(20, 40, ss.str(), "blue", 20); - svgwriter.save("SVG/nfpplacer_" + std::to_string(plate_id) + "_" + item.name + "_" + ss.str()); + svgwriter.save(boost::filesystem::path("SVG")/ ("nfpplacer_" + std::to_string(plate_id) + "_" + ss.str() + "_" + item.name + ".svg")); #endif if(can_pack) { diff --git a/src/libnest2d/tools/svgtools.hpp b/src/libnest2d/tools/svgtools.hpp index 012c489bbe..d00a74227f 100644 --- a/src/libnest2d/tools/svgtools.hpp +++ b/src/libnest2d/tools/svgtools.hpp @@ -5,6 +5,7 @@ #include #include +#include #include namespace libnest2d { namespace svg { @@ -27,9 +28,10 @@ public: OrigoLocation origo_location; Coord mm_in_coord_units; double width, height; + double x0, y0; Config(): origo_location(BOTTOMLEFT), mm_in_coord_units(1000000), - width(500), height(500) {} + width(500), height(500),x0(100) {} }; @@ -42,10 +44,12 @@ public: SVGWriter(const Config& conf = Config()): conf_(conf) {} - void setSize(const Box& box) { - conf_.height = static_cast(box.height()) / + void setSize(const Box &box) { + conf_.x0 = box.width() / 5; + conf_.x0 = box.height() / 5; + conf_.height = static_cast(box.height() + conf_.y0*2) / conf_.mm_in_coord_units; - conf_.width = static_cast(box.width()) / + conf_.width = static_cast(box.width() + conf_.x0*2) / conf_.mm_in_coord_units; } @@ -56,11 +60,17 @@ public: std::round(conf_.height*conf_.mm_in_coord_units) ); auto& contour = shapelike::contour(tsh); - for(auto& v : contour) setY(v, -getY(v) + d); + for (auto &v : contour) { + setX(v, getX(v) + conf_.x0); // right shift so we can draw outside the bounding box + setY(v, -getY(v) + d + conf_.y0); + } auto& holes = shapelike::holes(tsh); - for(auto& h : holes) for(auto& v : h) setY(v, -getY(v) + d); - + for (auto &h : holes) + for (auto &v : h) { + setX(v, getX(v) + conf_.x0); + setY(v, -getY(v) + d + conf_.y0); + } } currentLayer() += shapelike::serialize(tsh, @@ -126,6 +136,22 @@ public: }; } + // save svg in utf-8 file name + void save(const boost::filesystem::path &filepath) + { + size_t lyrc = svg_layers_.size() > 1 ? 1 : 0; + size_t last = svg_layers_.size() > 1 ? svg_layers_.size() : 0; + + for (auto &lyr : svg_layers_) { + boost::filesystem::ofstream out(filepath, std::fstream::out); + if (out.is_open()) out << lyr; + if (lyrc == last && !finished_) out << "\n\n"; + out.flush(); + out.close(); + lyrc++; + }; + } + private: std::string& currentLayer() { return svg_layers_.back(); } diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 8eac50bd54..3636f3b436 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -44,7 +44,7 @@ static const std::string MODELS_STR = "models"; const std::string AppConfig::SECTION_FILAMENTS = "filaments"; const std::string AppConfig::SECTION_MATERIALS = "sla_materials"; -std::string AppConfig::get_langauge_code() +std::string AppConfig::get_language_code() { std::string get_lang = get("language"); if (get_lang.empty()) return ""; diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index 69f46cdeaf..cd415d439d 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -39,7 +39,7 @@ public: this->reset(); } - std::string get_langauge_code(); + std::string get_language_code(); std::string get_hms_host(); // Clear and reset to defaults. diff --git a/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.cpp b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.cpp new file mode 100644 index 0000000000..b57c84d639 --- /dev/null +++ b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.cpp @@ -0,0 +1,79 @@ +//Copyright (c) 2022 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#include + +#include "BeadingStrategy.hpp" +#include "Point.hpp" + +namespace Slic3r::Arachne +{ + +BeadingStrategy::BeadingStrategy(coord_t optimal_width, double wall_split_middle_threshold, double wall_add_middle_threshold, coord_t default_transition_length, float transitioning_angle) + : optimal_width(optimal_width) + , wall_split_middle_threshold(wall_split_middle_threshold) + , wall_add_middle_threshold(wall_add_middle_threshold) + , default_transition_length(default_transition_length) + , transitioning_angle(transitioning_angle) +{ + name = "Unknown"; +} + +BeadingStrategy::BeadingStrategy(const BeadingStrategy &other) + : optimal_width(other.optimal_width) + , wall_split_middle_threshold(other.wall_split_middle_threshold) + , wall_add_middle_threshold(other.wall_add_middle_threshold) + , default_transition_length(other.default_transition_length) + , transitioning_angle(other.transitioning_angle) + , name(other.name) +{} + +coord_t BeadingStrategy::getTransitioningLength(coord_t lower_bead_count) const +{ + if (lower_bead_count == 0) + return scaled(0.01); + return default_transition_length; +} + +float BeadingStrategy::getTransitionAnchorPos(coord_t lower_bead_count) const +{ + coord_t lower_optimum = getOptimalThickness(lower_bead_count); + coord_t transition_point = getTransitionThickness(lower_bead_count); + coord_t upper_optimum = getOptimalThickness(lower_bead_count + 1); + return 1.0 - float(transition_point - lower_optimum) / float(upper_optimum - lower_optimum); +} + +std::vector BeadingStrategy::getNonlinearThicknesses(coord_t lower_bead_count) const +{ + return {}; +} + +std::string BeadingStrategy::toString() const +{ + return name; +} + +double BeadingStrategy::getSplitMiddleThreshold() const +{ + return wall_split_middle_threshold; +} + +double BeadingStrategy::getTransitioningAngle() const +{ + return transitioning_angle; +} + +coord_t BeadingStrategy::getOptimalThickness(coord_t bead_count) const +{ + return optimal_width * bead_count; +} + +coord_t BeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const +{ + const coord_t lower_ideal_width = getOptimalThickness(lower_bead_count); + const coord_t higher_ideal_width = getOptimalThickness(lower_bead_count + 1); + const double threshold = lower_bead_count % 2 == 1 ? wall_split_middle_threshold : wall_add_middle_threshold; + return lower_ideal_width + threshold * (higher_ideal_width - lower_ideal_width); +} + +} // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp new file mode 100644 index 0000000000..99e38239f9 --- /dev/null +++ b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp @@ -0,0 +1,117 @@ +// Copyright (c) 2022 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef BEADING_STRATEGY_H +#define BEADING_STRATEGY_H + +#include + +#include "../../libslic3r.h" + +namespace Slic3r::Arachne +{ + +template constexpr T pi_div(const T div) { return static_cast(M_PI) / div; } + +/*! + * Mostly virtual base class template. + * + * Strategy for covering a given (constant) horizontal model thickness with a number of beads. + * + * The beads may have different widths. + * + * TODO: extend with printing order? + */ +class BeadingStrategy +{ +public: + /*! + * The beading for a given horizontal model thickness. + */ + struct Beading + { + coord_t total_thickness; + std::vector bead_widths; //! The line width of each bead from the outer inset inward + std::vector toolpath_locations; //! The distance of the toolpath location of each bead from the outline + coord_t left_over; //! The distance not covered by any bead; gap area. + }; + + BeadingStrategy(coord_t optimal_width, double wall_split_middle_threshold, double wall_add_middle_threshold, coord_t default_transition_length, float transitioning_angle = pi_div(3)); + + BeadingStrategy(const BeadingStrategy &other); + + virtual ~BeadingStrategy() = default; + + /*! + * Retrieve the bead widths with which to cover a given thickness. + * + * Requirement: Given a constant \p bead_count the output of each bead width must change gradually along with the \p thickness. + * + * \note The \p bead_count might be different from the \ref BeadingStrategy::optimal_bead_count + */ + virtual Beading compute(coord_t thickness, coord_t bead_count) const = 0; + + /*! + * The ideal thickness for a given \param bead_count + */ + virtual coord_t getOptimalThickness(coord_t bead_count) const; + + /*! + * The model thickness at which \ref BeadingStrategy::optimal_bead_count transitions from \p lower_bead_count to \p lower_bead_count + 1 + */ + virtual coord_t getTransitionThickness(coord_t lower_bead_count) const; + + /*! + * The number of beads should we ideally usefor a given model thickness + */ + virtual coord_t getOptimalBeadCount(coord_t thickness) const = 0; + + /*! + * The length of the transitioning region along the marked / significant regions of the skeleton. + * + * Transitions are used to smooth out the jumps in integer bead count; the jumps turn into ramps with some incline defined by their length. + */ + virtual coord_t getTransitioningLength(coord_t lower_bead_count) const; + + /*! + * The fraction of the transition length to put between the lower end of the transition and the point where the unsmoothed bead count jumps. + * + * Transitions are used to smooth out the jumps in integer bead count; the jumps turn into ramps which could be positioned relative to the jump location. + */ + virtual float getTransitionAnchorPos(coord_t lower_bead_count) const; + + /*! + * Get the locations in a bead count region where \ref BeadingStrategy::compute exhibits a bend in the widths. + * Ordered from lower thickness to higher. + * + * This is used to insert extra support bones into the skeleton, so that the resulting beads in long trapezoids don't linearly change between the two ends. + */ + virtual std::vector getNonlinearThicknesses(coord_t lower_bead_count) const; + + virtual std::string toString() const; + + double getSplitMiddleThreshold() const; + double getTransitioningAngle() const; + +protected: + std::string name; + + coord_t optimal_width; //! Optimal bead width, nominal width off the walls in 'ideal' circumstances. + + double wall_split_middle_threshold; //! Threshold when a middle wall should be split into two, as a ratio of the optimal wall width. + + double wall_add_middle_threshold; //! Threshold when a new middle wall should be added between an even number of walls, as a ratio of the optimal wall width. + + coord_t default_transition_length; //! The length of the region to smoothly transfer between bead counts + + /*! + * The maximum angle between outline segments smaller than which we are going to add transitions + * Equals 180 - the "limit bisector angle" from the paper + */ + double transitioning_angle; +}; + +using BeadingStrategyPtr = std::unique_ptr; + +} // namespace Slic3r::Arachne +#endif // BEADING_STRATEGY_H diff --git a/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.cpp b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.cpp new file mode 100644 index 0000000000..4044c90138 --- /dev/null +++ b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.cpp @@ -0,0 +1,52 @@ +//Copyright (c) 2022 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "BeadingStrategyFactory.hpp" + +#include "LimitedBeadingStrategy.hpp" +#include "WideningBeadingStrategy.hpp" +#include "DistributedBeadingStrategy.hpp" +#include "RedistributeBeadingStrategy.hpp" +#include "OuterWallInsetBeadingStrategy.hpp" + +#include +#include + +namespace Slic3r::Arachne +{ + +BeadingStrategyPtr BeadingStrategyFactory::makeStrategy( + const coord_t preferred_bead_width_outer, + const coord_t preferred_bead_width_inner, + const coord_t preferred_transition_length, + const float transitioning_angle, + const bool print_thin_walls, + const coord_t min_bead_width, + const coord_t min_feature_size, + const double wall_split_middle_threshold, + const double wall_add_middle_threshold, + const coord_t max_bead_count, + const coord_t outer_wall_offset, + const int inward_distributed_center_wall_count, + const double minimum_variable_line_ratio +) +{ + BeadingStrategyPtr ret = std::make_unique(preferred_bead_width_inner, preferred_transition_length, transitioning_angle, wall_split_middle_threshold, wall_add_middle_threshold, inward_distributed_center_wall_count); + BOOST_LOG_TRIVIAL(debug) << "Applying the Redistribute meta-strategy with outer-wall width = " << preferred_bead_width_outer << ", inner-wall width = " << preferred_bead_width_inner << "."; + ret = std::make_unique(preferred_bead_width_outer, minimum_variable_line_ratio, std::move(ret)); + + if (print_thin_walls) { + BOOST_LOG_TRIVIAL(debug) << "Applying the Widening Beading meta-strategy with minimum input width " << min_feature_size << " and minimum output width " << min_bead_width << "."; + ret = std::make_unique(std::move(ret), min_feature_size, min_bead_width); + } + if (outer_wall_offset > 0) { + BOOST_LOG_TRIVIAL(debug) << "Applying the OuterWallOffset meta-strategy with offset = " << outer_wall_offset << "."; + ret = std::make_unique(outer_wall_offset, std::move(ret)); + } + + //Apply the LimitedBeadingStrategy last, since that adds a 0-width marker wall which other beading strategies shouldn't touch. + BOOST_LOG_TRIVIAL(debug) << "Applying the Limited Beading meta-strategy with maximum bead count = " << max_bead_count << "."; + ret = std::make_unique(max_bead_count, std::move(ret)); + return ret; +} +} // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.hpp b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.hpp new file mode 100644 index 0000000000..a586906f45 --- /dev/null +++ b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.hpp @@ -0,0 +1,35 @@ +// Copyright (c) 2022 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef BEADING_STRATEGY_FACTORY_H +#define BEADING_STRATEGY_FACTORY_H + +#include "BeadingStrategy.hpp" +#include "../../Point.hpp" + +namespace Slic3r::Arachne +{ + +class BeadingStrategyFactory +{ +public: + static BeadingStrategyPtr makeStrategy + ( + coord_t preferred_bead_width_outer = scaled(0.0005), + coord_t preferred_bead_width_inner = scaled(0.0005), + coord_t preferred_transition_length = scaled(0.0004), + float transitioning_angle = M_PI / 4.0, + bool print_thin_walls = false, + coord_t min_bead_width = 0, + coord_t min_feature_size = 0, + double wall_split_middle_threshold = 0.5, + double wall_add_middle_threshold = 0.5, + coord_t max_bead_count = 0, + coord_t outer_wall_offset = 0, + int inward_distributed_center_wall_count = 2, + double minimum_variable_line_width = 0.5 + ); +}; + +} // namespace Slic3r::Arachne +#endif // BEADING_STRATEGY_FACTORY_H diff --git a/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.cpp b/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.cpp new file mode 100644 index 0000000000..494b7b0b67 --- /dev/null +++ b/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.cpp @@ -0,0 +1,82 @@ +// Copyright (c) 2022 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. +#include +#include "DistributedBeadingStrategy.hpp" + +namespace Slic3r::Arachne +{ + +DistributedBeadingStrategy::DistributedBeadingStrategy(const coord_t optimal_width, + const coord_t default_transition_length, + const double transitioning_angle, + const double wall_split_middle_threshold, + const double wall_add_middle_threshold, + const int distribution_radius) + : BeadingStrategy(optimal_width, wall_split_middle_threshold, wall_add_middle_threshold, default_transition_length, transitioning_angle) +{ + if(distribution_radius >= 2) + one_over_distribution_radius_squared = 1.0f / (distribution_radius - 1) * 1.0f / (distribution_radius - 1); + else + one_over_distribution_radius_squared = 1.0f / 1 * 1.0f / 1; + name = "DistributedBeadingStrategy"; +} + +DistributedBeadingStrategy::Beading DistributedBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const +{ + Beading ret; + + ret.total_thickness = thickness; + if (bead_count > 2) { + const coord_t to_be_divided = thickness - bead_count * optimal_width; + const float middle = static_cast(bead_count - 1) / 2; + + const auto getWeight = [middle, this](coord_t bead_idx) { + const float dev_from_middle = bead_idx - middle; + return std::max(0.0f, 1.0f - one_over_distribution_radius_squared * dev_from_middle * dev_from_middle); + }; + + std::vector weights; + weights.resize(bead_count); + for (coord_t bead_idx = 0; bead_idx < bead_count; bead_idx++) + weights[bead_idx] = getWeight(bead_idx); + + const float total_weight = std::accumulate(weights.cbegin(), weights.cend(), 0.f); + for (coord_t bead_idx = 0; bead_idx < bead_count; bead_idx++) { + const float weight_fraction = weights[bead_idx] / total_weight; + const coord_t splitup_left_over_weight = to_be_divided * weight_fraction; + const coord_t width = optimal_width + splitup_left_over_weight; + if (bead_idx == 0) + ret.toolpath_locations.emplace_back(width / 2); + else + ret.toolpath_locations.emplace_back(ret.toolpath_locations.back() + (ret.bead_widths.back() + width) / 2); + ret.bead_widths.emplace_back(width); + } + ret.left_over = 0; + } else if (bead_count == 2) { + const coord_t outer_width = thickness / 2; + ret.bead_widths.emplace_back(outer_width); + ret.bead_widths.emplace_back(outer_width); + ret.toolpath_locations.emplace_back(outer_width / 2); + ret.toolpath_locations.emplace_back(thickness - outer_width / 2); + ret.left_over = 0; + } else if (bead_count == 1) { + const coord_t outer_width = thickness; + ret.bead_widths.emplace_back(outer_width); + ret.toolpath_locations.emplace_back(outer_width / 2); + ret.left_over = 0; + } else { + ret.left_over = thickness; + } + + return ret; +} + +coord_t DistributedBeadingStrategy::getOptimalBeadCount(coord_t thickness) const +{ + const coord_t naive_count = thickness / optimal_width; // How many lines we can fit in for sure. + const coord_t remainder = thickness - naive_count * optimal_width; // Space left after fitting that many lines. + const coord_t minimum_line_width = optimal_width * (naive_count % 2 == 1 ? wall_split_middle_threshold : wall_add_middle_threshold); + return naive_count + (remainder >= minimum_line_width); // If there's enough space, fit an extra one. +} + +} // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.hpp b/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.hpp new file mode 100644 index 0000000000..4d651732d4 --- /dev/null +++ b/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.hpp @@ -0,0 +1,40 @@ +// Copyright (c) 2022 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef DISTRIBUTED_BEADING_STRATEGY_H +#define DISTRIBUTED_BEADING_STRATEGY_H + +#include "BeadingStrategy.hpp" + +namespace Slic3r::Arachne +{ + +/*! + * This beading strategy chooses a wall count that would make the line width + * deviate the least from the optimal line width, and then distributes the lines + * evenly among the thickness available. + */ +class DistributedBeadingStrategy : public BeadingStrategy +{ +protected: + float one_over_distribution_radius_squared; // (1 / distribution_radius)^2 + +public: + /*! + * \param distribution_radius the radius (in number of beads) over which to distribute the discrepancy between the feature size and the optimal thickness + */ + DistributedBeadingStrategy(coord_t optimal_width, + coord_t default_transition_length, + double transitioning_angle, + double wall_split_middle_threshold, + double wall_add_middle_threshold, + int distribution_radius); + + ~DistributedBeadingStrategy() override = default; + + Beading compute(coord_t thickness, coord_t bead_count) const override; + coord_t getOptimalBeadCount(coord_t thickness) const override; +}; + +} // namespace Slic3r::Arachne +#endif // DISTRIBUTED_BEADING_STRATEGY_H diff --git a/src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.cpp b/src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.cpp new file mode 100644 index 0000000000..97d854b418 --- /dev/null +++ b/src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.cpp @@ -0,0 +1,126 @@ +//Copyright (c) 2022 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#include +#include + +#include "LimitedBeadingStrategy.hpp" +#include "Point.hpp" + +namespace Slic3r::Arachne +{ + +std::string LimitedBeadingStrategy::toString() const +{ + return std::string("LimitedBeadingStrategy+") + parent->toString(); +} + +coord_t LimitedBeadingStrategy::getTransitioningLength(coord_t lower_bead_count) const +{ + return parent->getTransitioningLength(lower_bead_count); +} + +float LimitedBeadingStrategy::getTransitionAnchorPos(coord_t lower_bead_count) const +{ + return parent->getTransitionAnchorPos(lower_bead_count); +} + +LimitedBeadingStrategy::LimitedBeadingStrategy(const coord_t max_bead_count, BeadingStrategyPtr parent) + : BeadingStrategy(*parent) + , max_bead_count(max_bead_count) + , parent(std::move(parent)) +{ + if (max_bead_count % 2 == 1) + { + BOOST_LOG_TRIVIAL(warning) << "LimitedBeadingStrategy with odd bead count is odd indeed!"; + } +} + +LimitedBeadingStrategy::Beading LimitedBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const +{ + if (bead_count <= max_bead_count) + { + Beading ret = parent->compute(thickness, bead_count); + bead_count = ret.toolpath_locations.size(); + + if (bead_count % 2 == 0 && bead_count == max_bead_count) + { + const coord_t innermost_toolpath_location = ret.toolpath_locations[max_bead_count / 2 - 1]; + const coord_t innermost_toolpath_width = ret.bead_widths[max_bead_count / 2 - 1]; + ret.toolpath_locations.insert(ret.toolpath_locations.begin() + max_bead_count / 2, innermost_toolpath_location + innermost_toolpath_width / 2); + ret.bead_widths.insert(ret.bead_widths.begin() + max_bead_count / 2, 0); + } + return ret; + } + assert(bead_count == max_bead_count + 1); + if(bead_count != max_bead_count + 1) + { + BOOST_LOG_TRIVIAL(warning) << "Too many beads! " << bead_count << " != " << max_bead_count + 1; + } + + coord_t optimal_thickness = parent->getOptimalThickness(max_bead_count); + Beading ret = parent->compute(optimal_thickness, max_bead_count); + bead_count = ret.toolpath_locations.size(); + ret.left_over += thickness - ret.total_thickness; + ret.total_thickness = thickness; + + // Enforce symmetry + if (bead_count % 2 == 1) { + ret.toolpath_locations[bead_count / 2] = thickness / 2; + ret.bead_widths[bead_count / 2] = thickness - optimal_thickness; + } + for (coord_t bead_idx = 0; bead_idx < (bead_count + 1) / 2; bead_idx++) + ret.toolpath_locations[bead_count - 1 - bead_idx] = thickness - ret.toolpath_locations[bead_idx]; + + //Create a "fake" inner wall with 0 width to indicate the edge of the walled area. + //This wall can then be used by other structures to e.g. fill the infill area adjacent to the variable-width walls. + coord_t innermost_toolpath_location = ret.toolpath_locations[max_bead_count / 2 - 1]; + coord_t innermost_toolpath_width = ret.bead_widths[max_bead_count / 2 - 1]; + ret.toolpath_locations.insert(ret.toolpath_locations.begin() + max_bead_count / 2, innermost_toolpath_location + innermost_toolpath_width / 2); + ret.bead_widths.insert(ret.bead_widths.begin() + max_bead_count / 2, 0); + + //Symmetry on both sides. Symmetry is guaranteed since this code is stopped early if the bead_count <= max_bead_count, and never reaches this point then. + const size_t opposite_bead = bead_count - (max_bead_count / 2 - 1); + innermost_toolpath_location = ret.toolpath_locations[opposite_bead]; + innermost_toolpath_width = ret.bead_widths[opposite_bead]; + ret.toolpath_locations.insert(ret.toolpath_locations.begin() + opposite_bead, innermost_toolpath_location - innermost_toolpath_width / 2); + ret.bead_widths.insert(ret.bead_widths.begin() + opposite_bead, 0); + + return ret; +} + +coord_t LimitedBeadingStrategy::getOptimalThickness(coord_t bead_count) const +{ + if (bead_count <= max_bead_count) + return parent->getOptimalThickness(bead_count); + assert(false); + return scaled(1000.); // 1 meter (Cura was returning 10 meter) +} + +coord_t LimitedBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const +{ + if (lower_bead_count < max_bead_count) + return parent->getTransitionThickness(lower_bead_count); + + if (lower_bead_count == max_bead_count) + return parent->getOptimalThickness(lower_bead_count + 1) - scaled(0.01); + + assert(false); + return scaled(900.); // 0.9 meter; +} + +coord_t LimitedBeadingStrategy::getOptimalBeadCount(coord_t thickness) const +{ + coord_t parent_bead_count = parent->getOptimalBeadCount(thickness); + if (parent_bead_count <= max_bead_count) { + return parent->getOptimalBeadCount(thickness); + } else if (parent_bead_count == max_bead_count + 1) { + if (thickness < parent->getOptimalThickness(max_bead_count + 1) - scaled(0.01)) + return max_bead_count; + else + return max_bead_count + 1; + } + else return max_bead_count + 1; +} + +} // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.hpp b/src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.hpp new file mode 100644 index 0000000000..33292bc09f --- /dev/null +++ b/src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.hpp @@ -0,0 +1,49 @@ +//Copyright (c) 2022 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef LIMITED_BEADING_STRATEGY_H +#define LIMITED_BEADING_STRATEGY_H + +#include "BeadingStrategy.hpp" + +namespace Slic3r::Arachne +{ + +/*! + * This is a meta-strategy that can be applied on top of any other beading + * strategy, which limits the thickness of the walls to the thickness that the + * lines can reasonably print. + * + * The width of the wall is limited to the maximum number of contours times the + * maximum width of each of these contours. + * + * If the width of the wall gets limited, this strategy outputs one additional + * bead with 0 width. This bead is used to denote the limits of the walled area. + * Other structures can then use this border to align their structures to, such + * as to create correctly overlapping infill or skin, or to align the infill + * pattern to any extra infill walls. + */ +class LimitedBeadingStrategy : public BeadingStrategy +{ +public: + LimitedBeadingStrategy(coord_t max_bead_count, BeadingStrategyPtr parent); + + ~LimitedBeadingStrategy() override = default; + + Beading compute(coord_t thickness, coord_t bead_count) const override; + coord_t getOptimalThickness(coord_t bead_count) const override; + coord_t getTransitionThickness(coord_t lower_bead_count) const override; + coord_t getOptimalBeadCount(coord_t thickness) const override; + std::string toString() const override; + + coord_t getTransitioningLength(coord_t lower_bead_count) const override; + + float getTransitionAnchorPos(coord_t lower_bead_count) const override; + +protected: + const coord_t max_bead_count; + const BeadingStrategyPtr parent; +}; + +} // namespace Slic3r::Arachne +#endif // LIMITED_DISTRIBUTED_BEADING_STRATEGY_H diff --git a/src/libslic3r/Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.cpp b/src/libslic3r/Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.cpp new file mode 100644 index 0000000000..1406f7daa8 --- /dev/null +++ b/src/libslic3r/Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.cpp @@ -0,0 +1,59 @@ +//Copyright (c) 2022 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "OuterWallInsetBeadingStrategy.hpp" + +#include + +namespace Slic3r::Arachne +{ +OuterWallInsetBeadingStrategy::OuterWallInsetBeadingStrategy(coord_t outer_wall_offset, BeadingStrategyPtr parent) + : BeadingStrategy(*parent), parent(std::move(parent)), outer_wall_offset(outer_wall_offset) +{ + name = "OuterWallOfsetBeadingStrategy"; +} + +coord_t OuterWallInsetBeadingStrategy::getOptimalThickness(coord_t bead_count) const +{ + return parent->getOptimalThickness(bead_count); +} + +coord_t OuterWallInsetBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const +{ + return parent->getTransitionThickness(lower_bead_count); +} + +coord_t OuterWallInsetBeadingStrategy::getOptimalBeadCount(coord_t thickness) const +{ + return parent->getOptimalBeadCount(thickness); +} + +coord_t OuterWallInsetBeadingStrategy::getTransitioningLength(coord_t lower_bead_count) const +{ + return parent->getTransitioningLength(lower_bead_count); +} + +std::string OuterWallInsetBeadingStrategy::toString() const +{ + return std::string("OuterWallOfsetBeadingStrategy+") + parent->toString(); +} + +BeadingStrategy::Beading OuterWallInsetBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const +{ + Beading ret = parent->compute(thickness, bead_count); + + // Actual count and thickness as represented by extant walls. Don't count any potential zero-width 'signaling' walls. + bead_count = std::count_if(ret.bead_widths.begin(), ret.bead_widths.end(), [](const coord_t width) { return width > 0; }); + + // No need to apply any inset if there is just a single wall. + if (bead_count < 2) + { + return ret; + } + + // Actually move the outer wall inside. Ensure that the outer wall never goes beyond the middle line. + ret.toolpath_locations[0] = std::min(ret.toolpath_locations[0] + outer_wall_offset, thickness / 2); + return ret; +} + +} // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.hpp b/src/libslic3r/Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.hpp new file mode 100644 index 0000000000..45a700b02e --- /dev/null +++ b/src/libslic3r/Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.hpp @@ -0,0 +1,35 @@ +//Copyright (c) 2022 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef OUTER_WALL_INSET_BEADING_STRATEGY_H +#define OUTER_WALL_INSET_BEADING_STRATEGY_H + +#include "BeadingStrategy.hpp" + +namespace Slic3r::Arachne +{ + /* + * This is a meta strategy that allows for the outer wall to be inset towards the inside of the model. + */ + class OuterWallInsetBeadingStrategy : public BeadingStrategy + { + public: + OuterWallInsetBeadingStrategy(coord_t outer_wall_offset, BeadingStrategyPtr parent); + + ~OuterWallInsetBeadingStrategy() override = default; + + Beading compute(coord_t thickness, coord_t bead_count) const override; + + coord_t getOptimalThickness(coord_t bead_count) const override; + coord_t getTransitionThickness(coord_t lower_bead_count) const override; + coord_t getOptimalBeadCount(coord_t thickness) const override; + coord_t getTransitioningLength(coord_t lower_bead_count) const override; + + std::string toString() const override; + + private: + BeadingStrategyPtr parent; + coord_t outer_wall_offset; + }; +} // namespace Slic3r::Arachne +#endif // OUTER_WALL_INSET_BEADING_STRATEGY_H diff --git a/src/libslic3r/Arachne/BeadingStrategy/RedistributeBeadingStrategy.cpp b/src/libslic3r/Arachne/BeadingStrategy/RedistributeBeadingStrategy.cpp new file mode 100644 index 0000000000..2b4dda0272 --- /dev/null +++ b/src/libslic3r/Arachne/BeadingStrategy/RedistributeBeadingStrategy.cpp @@ -0,0 +1,97 @@ +//Copyright (c) 2022 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "RedistributeBeadingStrategy.hpp" + +#include +#include + +namespace Slic3r::Arachne +{ + +RedistributeBeadingStrategy::RedistributeBeadingStrategy(const coord_t optimal_width_outer, + const double minimum_variable_line_ratio, + BeadingStrategyPtr parent) + : BeadingStrategy(*parent) + , parent(std::move(parent)) + , optimal_width_outer(optimal_width_outer) + , minimum_variable_line_ratio(minimum_variable_line_ratio) +{ + name = "RedistributeBeadingStrategy"; +} + +coord_t RedistributeBeadingStrategy::getOptimalThickness(coord_t bead_count) const +{ + const coord_t inner_bead_count = std::max(static_cast(0), bead_count - 2); + const coord_t outer_bead_count = bead_count - inner_bead_count; + return parent->getOptimalThickness(inner_bead_count) + optimal_width_outer * outer_bead_count; +} + +coord_t RedistributeBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const +{ + switch (lower_bead_count) { + case 0: return minimum_variable_line_ratio * optimal_width_outer; + case 1: return (1.0 + parent->getSplitMiddleThreshold()) * optimal_width_outer; + default: return parent->getTransitionThickness(lower_bead_count - 2) + 2 * optimal_width_outer; + } +} + +coord_t RedistributeBeadingStrategy::getOptimalBeadCount(coord_t thickness) const +{ + if (thickness < minimum_variable_line_ratio * optimal_width_outer) + return 0; + if (thickness <= 2 * optimal_width_outer) + return thickness > (1.0 + parent->getSplitMiddleThreshold()) * optimal_width_outer ? 2 : 1; + return parent->getOptimalBeadCount(thickness - 2 * optimal_width_outer) + 2; +} + +coord_t RedistributeBeadingStrategy::getTransitioningLength(coord_t lower_bead_count) const +{ + return parent->getTransitioningLength(lower_bead_count); +} + +float RedistributeBeadingStrategy::getTransitionAnchorPos(coord_t lower_bead_count) const +{ + return parent->getTransitionAnchorPos(lower_bead_count); +} + +std::string RedistributeBeadingStrategy::toString() const +{ + return std::string("RedistributeBeadingStrategy+") + parent->toString(); +} + +BeadingStrategy::Beading RedistributeBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const +{ + Beading ret; + + // Take care of all situations in which no lines are actually produced: + if (bead_count == 0 || thickness < minimum_variable_line_ratio * optimal_width_outer) { + ret.left_over = thickness; + ret.total_thickness = thickness; + return ret; + } + + // Compute the beadings of the inner walls, if any: + const coord_t inner_bead_count = bead_count - 2; + const coord_t inner_thickness = thickness - 2 * optimal_width_outer; + if (inner_bead_count > 0 && inner_thickness > 0) { + ret = parent->compute(inner_thickness, inner_bead_count); + for (auto &toolpath_location : ret.toolpath_locations) toolpath_location += optimal_width_outer; + } + + // Insert the outer wall(s) around the previously computed inner wall(s), which may be empty: + const coord_t actual_outer_thickness = bead_count > 2 ? std::min(thickness / 2, optimal_width_outer) : thickness / bead_count; + ret.bead_widths.insert(ret.bead_widths.begin(), actual_outer_thickness); + ret.toolpath_locations.insert(ret.toolpath_locations.begin(), actual_outer_thickness / 2); + if (bead_count > 1) { + ret.bead_widths.push_back(actual_outer_thickness); + ret.toolpath_locations.push_back(thickness - actual_outer_thickness / 2); + } + + // Ensure correct total and left over thickness. + ret.total_thickness = thickness; + ret.left_over = thickness - std::accumulate(ret.bead_widths.cbegin(), ret.bead_widths.cend(), static_cast(0)); + return ret; +} + +} // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/BeadingStrategy/RedistributeBeadingStrategy.hpp b/src/libslic3r/Arachne/BeadingStrategy/RedistributeBeadingStrategy.hpp new file mode 100644 index 0000000000..f0fefe2389 --- /dev/null +++ b/src/libslic3r/Arachne/BeadingStrategy/RedistributeBeadingStrategy.hpp @@ -0,0 +1,56 @@ +//Copyright (c) 2022 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef REDISTRIBUTE_DISTRIBUTED_BEADING_STRATEGY_H +#define REDISTRIBUTE_DISTRIBUTED_BEADING_STRATEGY_H + +#include "BeadingStrategy.hpp" + +namespace Slic3r::Arachne +{ + /*! + * A meta-beading-strategy that takes outer and inner wall widths into account. + * + * The outer wall will try to keep a constant width by only applying the beading strategy on the inner walls. This + * ensures that this outer wall doesn't react to changes happening to inner walls. It will limit print artifacts on + * the surface of the print. Although this strategy technically deviates from the original philosophy of the paper. + * It will generally results in better prints because of a smoother motion and less variation in extrusion width in + * the outer walls. + * + * If the thickness of the model is less then two times the optimal outer wall width and once the minimum inner wall + * width it will keep the minimum inner wall at a minimum constant and vary the outer wall widths symmetrical. Until + * The thickness of the model is that of at least twice the optimal outer wall width it will then use two + * symmetrical outer walls only. Until it transitions into a single outer wall. These last scenario's are always + * symmetrical in nature, disregarding the user specified strategy. + */ + class RedistributeBeadingStrategy : public BeadingStrategy + { + public: + /*! + * /param optimal_width_outer Outer wall width, guaranteed to be the actual (save rounding errors) at a + * bead count if the parent strategies' optimum bead width is a weighted + * average of the outer and inner walls at that bead count. + * /param minimum_variable_line_ratio Minimum factor that the variable line might deviate from the optimal width. + */ + RedistributeBeadingStrategy(coord_t optimal_width_outer, double minimum_variable_line_ratio, BeadingStrategyPtr parent); + + ~RedistributeBeadingStrategy() override = default; + + Beading compute(coord_t thickness, coord_t bead_count) const override; + + coord_t getOptimalThickness(coord_t bead_count) const override; + coord_t getTransitionThickness(coord_t lower_bead_count) const override; + coord_t getOptimalBeadCount(coord_t thickness) const override; + coord_t getTransitioningLength(coord_t lower_bead_count) const override; + float getTransitionAnchorPos(coord_t lower_bead_count) const override; + + std::string toString() const override; + + protected: + BeadingStrategyPtr parent; + coord_t optimal_width_outer; + double minimum_variable_line_ratio; + }; + +} // namespace Slic3r::Arachne +#endif // INWARD_DISTRIBUTED_BEADING_STRATEGY_H diff --git a/src/libslic3r/Arachne/BeadingStrategy/WideningBeadingStrategy.cpp b/src/libslic3r/Arachne/BeadingStrategy/WideningBeadingStrategy.cpp new file mode 100644 index 0000000000..eefcab8e7b --- /dev/null +++ b/src/libslic3r/Arachne/BeadingStrategy/WideningBeadingStrategy.cpp @@ -0,0 +1,82 @@ +//Copyright (c) 2022 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "WideningBeadingStrategy.hpp" + +namespace Slic3r::Arachne +{ + +WideningBeadingStrategy::WideningBeadingStrategy(BeadingStrategyPtr parent, const coord_t min_input_width, const coord_t min_output_width) + : BeadingStrategy(*parent) + , parent(std::move(parent)) + , min_input_width(min_input_width) + , min_output_width(min_output_width) +{ +} + +std::string WideningBeadingStrategy::toString() const +{ + return std::string("Widening+") + parent->toString(); +} + +WideningBeadingStrategy::Beading WideningBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const +{ + if (thickness < optimal_width) { + Beading ret; + ret.total_thickness = thickness; + if (thickness >= min_input_width) + { + ret.bead_widths.emplace_back(std::max(thickness, min_output_width)); + ret.toolpath_locations.emplace_back(thickness / 2); + } else { + ret.left_over = thickness; + } + return ret; + } else { + return parent->compute(thickness, bead_count); + } +} + +coord_t WideningBeadingStrategy::getOptimalThickness(coord_t bead_count) const +{ + return parent->getOptimalThickness(bead_count); +} + +coord_t WideningBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const +{ + if (lower_bead_count == 0) + return min_input_width; + else + return parent->getTransitionThickness(lower_bead_count); +} + +coord_t WideningBeadingStrategy::getOptimalBeadCount(coord_t thickness) const +{ + if (thickness < min_input_width) + return 0; + coord_t ret = parent->getOptimalBeadCount(thickness); + if (thickness >= min_input_width && ret < 1) + return 1; + return ret; +} + +coord_t WideningBeadingStrategy::getTransitioningLength(coord_t lower_bead_count) const +{ + return parent->getTransitioningLength(lower_bead_count); +} + +float WideningBeadingStrategy::getTransitionAnchorPos(coord_t lower_bead_count) const +{ + return parent->getTransitionAnchorPos(lower_bead_count); +} + +std::vector WideningBeadingStrategy::getNonlinearThicknesses(coord_t lower_bead_count) const +{ + std::vector ret; + ret.emplace_back(min_output_width); + std::vector pret = parent->getNonlinearThicknesses(lower_bead_count); + ret.insert(ret.end(), pret.begin(), pret.end()); + return ret; +} + +} // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/BeadingStrategy/WideningBeadingStrategy.hpp b/src/libslic3r/Arachne/BeadingStrategy/WideningBeadingStrategy.hpp new file mode 100644 index 0000000000..3e799b9af7 --- /dev/null +++ b/src/libslic3r/Arachne/BeadingStrategy/WideningBeadingStrategy.hpp @@ -0,0 +1,46 @@ +//Copyright (c) 2022 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef WIDENING_BEADING_STRATEGY_H +#define WIDENING_BEADING_STRATEGY_H + +#include "BeadingStrategy.hpp" + +namespace Slic3r::Arachne +{ + +/*! + * This is a meta-strategy that can be applied on any other beading strategy. If + * the part is thinner than a single line, this strategy adjusts the part so + * that it becomes the minimum thickness of one line. + * + * This way, tiny pieces that are smaller than a single line will still be + * printed. + */ +class WideningBeadingStrategy : public BeadingStrategy +{ +public: + /*! + * Takes responsibility for deleting \param parent + */ + WideningBeadingStrategy(BeadingStrategyPtr parent, coord_t min_input_width, coord_t min_output_width); + + ~WideningBeadingStrategy() override = default; + + Beading compute(coord_t thickness, coord_t bead_count) const override; + coord_t getOptimalThickness(coord_t bead_count) const override; + coord_t getTransitionThickness(coord_t lower_bead_count) const override; + coord_t getOptimalBeadCount(coord_t thickness) const override; + coord_t getTransitioningLength(coord_t lower_bead_count) const override; + float getTransitionAnchorPos(coord_t lower_bead_count) const override; + std::vector getNonlinearThicknesses(coord_t lower_bead_count) const override; + std::string toString() const override; + +protected: + BeadingStrategyPtr parent; + const coord_t min_input_width; + const coord_t min_output_width; +}; + +} // namespace Slic3r::Arachne +#endif // WIDENING_BEADING_STRATEGY_H diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp new file mode 100644 index 0000000000..56d98ec5af --- /dev/null +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp @@ -0,0 +1,2140 @@ +//Copyright (c) 2021 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "SkeletalTrapezoidation.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "utils/VoronoiUtils.hpp" + +#include "utils/linearAlg2D.hpp" +#include "Utils.hpp" +#include "SVG.hpp" +#include "Geometry/VoronoiVisualUtils.hpp" +#include "../EdgeGrid.hpp" + +#define SKELETAL_TRAPEZOIDATION_BEAD_SEARCH_MAX 1000 //A limit to how long it'll keep searching for adjacent beads. Increasing will re-use beadings more often (saving performance), but search longer for beading (costing performance). + +namespace boost::polygon { + +template<> struct geometry_concept +{ + typedef segment_concept type; +}; + +template<> struct segment_traits +{ + typedef coord_t coordinate_type; + typedef Slic3r::Point point_type; + static inline point_type get(const Slic3r::Arachne::PolygonsSegmentIndex &CSegment, direction_1d dir) + { + return dir.to_int() ? CSegment.p() : CSegment.next().p(); + } +}; + +} // namespace boost::polygon + +namespace Slic3r::Arachne +{ + +SkeletalTrapezoidation::node_t& SkeletalTrapezoidation::makeNode(vd_t::vertex_type& vd_node, Point p) +{ + auto he_node_it = vd_node_to_he_node.find(&vd_node); + if (he_node_it == vd_node_to_he_node.end()) + { + graph.nodes.emplace_front(SkeletalTrapezoidationJoint(), p); + node_t& node = graph.nodes.front(); + vd_node_to_he_node.emplace(&vd_node, &node); + return node; + } + else + { + return *he_node_it->second; + } +} + +void SkeletalTrapezoidation::transferEdge(Point from, Point to, vd_t::edge_type& vd_edge, edge_t*& prev_edge, Point& start_source_point, Point& end_source_point, const std::vector& segments) +{ + auto he_edge_it = vd_edge_to_he_edge.find(vd_edge.twin()); + if (he_edge_it != vd_edge_to_he_edge.end()) + { // Twin segment(s) have already been made + edge_t* source_twin = he_edge_it->second; + assert(source_twin); + auto end_node_it = vd_node_to_he_node.find(vd_edge.vertex1()); + assert(end_node_it != vd_node_to_he_node.end()); + node_t* end_node = end_node_it->second; + for (edge_t* twin = source_twin; ;twin = twin->prev->twin->prev) + { + if(!twin) + { + BOOST_LOG_TRIVIAL(warning) << "Encountered a voronoi edge without twin."; + continue; //Prevent reading unallocated memory. + } + assert(twin); + graph.edges.emplace_front(SkeletalTrapezoidationEdge()); + edge_t* edge = &graph.edges.front(); + edge->from = twin->to; + edge->to = twin->from; + edge->twin = twin; + twin->twin = edge; + edge->from->incident_edge = edge; + + if (prev_edge) + { + edge->prev = prev_edge; + prev_edge->next = edge; + } + + prev_edge = edge; + + if (prev_edge->to == end_node) + { + return; + } + + if (!twin->prev || !twin->prev->twin || !twin->prev->twin->prev) + { + BOOST_LOG_TRIVIAL(error) << "Discretized segment behaves oddly!"; + return; + } + + assert(twin->prev); // Forth rib + assert(twin->prev->twin); // Back rib + assert(twin->prev->twin->prev); // Prev segment along parabola + + constexpr bool is_not_next_to_start_or_end = false; // Only ribs at the end of a cell should be skipped + graph.makeRib(prev_edge, start_source_point, end_source_point, is_not_next_to_start_or_end); + } + assert(prev_edge); + } + else + { + std::vector discretized = discretize(vd_edge, segments); + assert(discretized.size() >= 2); + if(discretized.size() < 2) + { + BOOST_LOG_TRIVIAL(warning) << "Discretized Voronoi edge is degenerate."; + } + + assert(!prev_edge || prev_edge->to); + if(prev_edge && !prev_edge->to) + { + BOOST_LOG_TRIVIAL(warning) << "Previous edge doesn't go anywhere."; + } + node_t* v0 = (prev_edge)? prev_edge->to : &makeNode(*vd_edge.vertex0(), from); // TODO: investigate whether boost:voronoi can produce multiple verts and violates consistency + Point p0 = discretized.front(); + for (size_t p1_idx = 1; p1_idx < discretized.size(); p1_idx++) + { + Point p1 = discretized[p1_idx]; + node_t* v1; + if (p1_idx < discretized.size() - 1) + { + graph.nodes.emplace_front(SkeletalTrapezoidationJoint(), p1); + v1 = &graph.nodes.front(); + } + else + { + v1 = &makeNode(*vd_edge.vertex1(), to); + } + + graph.edges.emplace_front(SkeletalTrapezoidationEdge()); + edge_t* edge = &graph.edges.front(); + edge->from = v0; + edge->to = v1; + edge->from->incident_edge = edge; + + if (prev_edge) + { + edge->prev = prev_edge; + prev_edge->next = edge; + } + + prev_edge = edge; + p0 = p1; + v0 = v1; + + if (p1_idx < discretized.size() - 1) + { // Rib for last segment gets introduced outside this function! + constexpr bool is_not_next_to_start_or_end = false; // Only ribs at the end of a cell should be skipped + graph.makeRib(prev_edge, start_source_point, end_source_point, is_not_next_to_start_or_end); + } + } + assert(prev_edge); + vd_edge_to_he_edge.emplace(&vd_edge, prev_edge); + } +} + +std::vector SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_edge, const std::vector& segments) +{ + /*Terminology in this function assumes that the edge moves horizontally from + left to right. This is not necessarily the case; the edge can go in any + direction, but it helps to picture it in a certain direction in your head.*/ + + const vd_t::cell_type* left_cell = vd_edge.cell(); + const vd_t::cell_type* right_cell = vd_edge.twin()->cell(); + + assert(VoronoiUtils::p(vd_edge.vertex0()).x() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge.vertex0()).x() >= std::numeric_limits::lowest()); + assert(VoronoiUtils::p(vd_edge.vertex0()).y() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge.vertex0()).y() >= std::numeric_limits::lowest()); + assert(VoronoiUtils::p(vd_edge.vertex1()).x() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge.vertex1()).x() >= std::numeric_limits::lowest()); + assert(VoronoiUtils::p(vd_edge.vertex1()).y() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge.vertex1()).y() >= std::numeric_limits::lowest()); + + Point start = VoronoiUtils::p(vd_edge.vertex0()).cast(); + Point end = VoronoiUtils::p(vd_edge.vertex1()).cast(); + + bool point_left = left_cell->contains_point(); + bool point_right = right_cell->contains_point(); + if ((!point_left && !point_right) || vd_edge.is_secondary()) // Source vert is directly connected to source segment + { + return std::vector({ start, end }); + } + else if (point_left != point_right) //This is a parabolic edge between a point and a line. + { + Point p = VoronoiUtils::getSourcePoint(*(point_left ? left_cell : right_cell), segments); + const Segment& s = VoronoiUtils::getSourceSegment(*(point_left ? right_cell : left_cell), segments); + return VoronoiUtils::discretizeParabola(p, s, start, end, discretization_step_size, transitioning_angle); + } + else //This is a straight edge between two points. + { + /*While the edge is straight, it is still discretized since the part + becomes narrower between the two points. As such it may need different + beadings along the way.*/ + Point left_point = VoronoiUtils::getSourcePoint(*left_cell, segments); + Point right_point = VoronoiUtils::getSourcePoint(*right_cell, segments); + coord_t d = (right_point - left_point).cast().norm(); + Point middle = (left_point + right_point) / 2; + Point x_axis_dir = Point(right_point - left_point).rotate_90_degree_ccw(); + coord_t x_axis_length = x_axis_dir.cast().norm(); + + const auto projected_x = [x_axis_dir, x_axis_length, middle](Point from) //Project a point on the edge. + { + Point vec = from - middle; + assert(( vec.cast().dot(x_axis_dir.cast())/ int64_t(x_axis_length)) <= std::numeric_limits::max()); + coord_t x = vec.cast().dot(x_axis_dir.cast()) / int64_t(x_axis_length); + return x; + }; + + coord_t start_x = projected_x(start); + coord_t end_x = projected_x(end); + + //Part of the edge will be bound to the markings on the endpoints of the edge. Calculate how far that is. + float bound = 0.5 / tan((M_PI - transitioning_angle) * 0.5); + int64_t marking_start_x = - int64_t(d) * bound; + int64_t marking_end_x = int64_t(d) * bound; + + assert((middle.cast() + x_axis_dir.cast() * marking_start_x / int64_t(x_axis_length)).x() <= std::numeric_limits::max()); + assert((middle.cast() + x_axis_dir.cast() * marking_start_x / int64_t(x_axis_length)).y() <= std::numeric_limits::max()); + assert((middle.cast() + x_axis_dir.cast() * marking_end_x / int64_t(x_axis_length)).x() <= std::numeric_limits::max()); + assert((middle.cast() + x_axis_dir.cast() * marking_end_x / int64_t(x_axis_length)).y() <= std::numeric_limits::max()); + Point marking_start = middle + (x_axis_dir.cast() * marking_start_x / int64_t(x_axis_length)).cast(); + Point marking_end = middle + (x_axis_dir.cast() * marking_end_x / int64_t(x_axis_length)).cast(); + int64_t direction = 1; + + if (start_x > end_x) //Oops, the Voronoi edge is the other way around. + { + direction = -1; + std::swap(marking_start, marking_end); + std::swap(marking_start_x, marking_end_x); + } + + //Start generating points along the edge. + Point a = start; + Point b = end; + std::vector ret; + ret.emplace_back(a); + + //Introduce an extra edge at the borders of the markings? + bool add_marking_start = marking_start_x * direction > int64_t(start_x) * direction; + bool add_marking_end = marking_end_x * direction > int64_t(start_x) * direction; + + //The edge's length may not be divisible by the step size, so calculate an integer step count and evenly distribute the vertices among those. + Point ab = b - a; + coord_t ab_size = ab.cast().norm(); + coord_t step_count = (ab_size + discretization_step_size / 2) / discretization_step_size; + if (step_count % 2 == 1) + { + step_count++; // enforce a discretization point being added in the middle + } + for (coord_t step = 1; step < step_count; step++) + { + Point here = a + (ab.cast() * int64_t(step) / int64_t(step_count)).cast(); //Now simply interpolate the coordinates to get the new vertices! + coord_t x_here = projected_x(here); //If we've surpassed the position of the extra markings, we may need to insert them first. + if (add_marking_start && marking_start_x * direction < int64_t(x_here) * direction) + { + ret.emplace_back(marking_start); + add_marking_start = false; + } + if (add_marking_end && marking_end_x * direction < int64_t(x_here) * direction) + { + ret.emplace_back(marking_end); + add_marking_end = false; + } + ret.emplace_back(here); + } + if (add_marking_end && marking_end_x * direction < int64_t(end_x) * direction) + { + ret.emplace_back(marking_end); + } + ret.emplace_back(b); + return ret; + } +} + + +bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector& segments) +{ + if (cell.incident_edge()->is_infinite()) + return false; //Infinite edges only occur outside of the polygon. Don't copy any part of this cell. + + // Check if any point of the cell is inside or outside polygon + // Copy whole cell into graph or not at all + + // If the cell.incident_edge()->vertex0() is far away so much that it doesn't even fit into Vec2i64, then there is no way that it will be inside the input polygon. + if (const vd_t::vertex_type &vert = *cell.incident_edge()->vertex0(); + vert.x() >= double(std::numeric_limits::max()) || vert.x() <= double(std::numeric_limits::lowest()) || + vert.y() >= double(std::numeric_limits::max()) || vert.y() <= double(std::numeric_limits::lowest())) + return false; // Don't copy any part of this cell + + const Point source_point = VoronoiUtils::getSourcePoint(cell, segments); + const PolygonsPointIndex source_point_index = VoronoiUtils::getSourcePointIndex(cell, segments); + Vec2i64 some_point = VoronoiUtils::p(cell.incident_edge()->vertex0()); + if (some_point == source_point.cast()) + some_point = VoronoiUtils::p(cell.incident_edge()->vertex1()); + + //Test if the some_point is even inside the polygon. + //The edge leading out of a polygon must have an endpoint that's not in the corner following the contour of the polygon at that vertex. + //So if it's inside the corner formed by the polygon vertex, it's all fine. + //But if it's outside of the corner, it must be a vertex of the Voronoi diagram that goes outside of the polygon towards infinity. + if (!LinearAlg2D::isInsideCorner(source_point_index.prev().p(), source_point_index.p(), source_point_index.next().p(), some_point)) + return false; // Don't copy any part of this cell + + vd_t::edge_type* vd_edge = cell.incident_edge(); + do { + assert(vd_edge->is_finite()); + if (Vec2i64 p1 = VoronoiUtils::p(vd_edge->vertex1()); p1 == source_point.cast()) { + start_source_point = source_point; + end_source_point = source_point; + starting_vd_edge = vd_edge->next(); + ending_vd_edge = vd_edge; + } else { + assert((VoronoiUtils::p(vd_edge->vertex0()) == source_point.cast() || !vd_edge->is_secondary()) && "point cells must end in the point! They cannot cross the point with an edge, because collinear edges are not allowed in the input."); + } + } + while (vd_edge = vd_edge->next(), vd_edge != cell.incident_edge()); + assert(starting_vd_edge && ending_vd_edge); + assert(starting_vd_edge != ending_vd_edge); + return true; +} + +void SkeletalTrapezoidation::computeSegmentCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector& segments) +{ + const Segment &source_segment = VoronoiUtils::getSourceSegment(cell, segments); + const Point from = source_segment.from(); + const Point to = source_segment.to(); + + // Find starting edge + // Find end edge + bool seen_possible_start = false; + bool after_start = false; + bool ending_edge_is_set_before_start = false; + vd_t::edge_type* edge = cell.incident_edge(); + do { + if (edge->is_infinite()) + continue; + + Vec2i64 v0 = VoronoiUtils::p(edge->vertex0()); + Vec2i64 v1 = VoronoiUtils::p(edge->vertex1()); + + assert(!(v0 == to.cast() && v1 == from.cast() )); + if (v0 == to.cast() && !after_start) { // Use the last edge which starts in source_segment.to + starting_vd_edge = edge; + seen_possible_start = true; + } + else if (seen_possible_start) { + after_start = true; + } + + if (v1 == from.cast() && (!ending_vd_edge || ending_edge_is_set_before_start)) { + ending_edge_is_set_before_start = !after_start; + ending_vd_edge = edge; + } + } while (edge = edge->next(), edge != cell.incident_edge()); + + assert(starting_vd_edge && ending_vd_edge); + assert(starting_vd_edge != ending_vd_edge); + + start_source_point = source_segment.to(); + end_source_point = source_segment.from(); +} + +SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const BeadingStrategy& beading_strategy, + double transitioning_angle, coord_t discretization_step_size, + coord_t transition_filter_dist, coord_t allowed_filter_deviation, + coord_t beading_propagation_transition_dist + ): transitioning_angle(transitioning_angle), + discretization_step_size(discretization_step_size), + transition_filter_dist(transition_filter_dist), + allowed_filter_deviation(allowed_filter_deviation), + beading_propagation_transition_dist(beading_propagation_transition_dist), + beading_strategy(beading_strategy) +{ + constructFromPolygons(polys); +} + +bool detect_missing_voronoi_vertex(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector &segments) { + for (VoronoiUtils::vd_t::cell_type cell : voronoi_diagram.cells()) { + if (!cell.incident_edge()) + continue; // There is no spoon + + if (cell.contains_segment()) { + const SkeletalTrapezoidation::Segment &source_segment = VoronoiUtils::getSourceSegment(cell, segments); + const Point from = source_segment.from(); + const Point to = source_segment.to(); + + // Find starting edge + // Find end edge + bool seen_possible_start = false; + bool after_start = false; + bool ending_edge_is_set_before_start = false; + VoronoiUtils::vd_t::edge_type *starting_vd_edge = nullptr; + VoronoiUtils::vd_t::edge_type *ending_vd_edge = nullptr; + VoronoiUtils::vd_t::edge_type *edge = cell.incident_edge(); + do { + if (edge->is_infinite()) continue; + + Vec2i64 v0 = VoronoiUtils::p(edge->vertex0()); + Vec2i64 v1 = VoronoiUtils::p(edge->vertex1()); + + assert(!(v0 == to.cast() && v1 == from.cast())); + if (v0 == to.cast() && !after_start) { // Use the last edge which starts in source_segment.to + starting_vd_edge = edge; + seen_possible_start = true; + } else if (seen_possible_start) { + after_start = true; + } + + if (v1 == from.cast() && (!ending_vd_edge || ending_edge_is_set_before_start)) { + ending_edge_is_set_before_start = !after_start; + ending_vd_edge = edge; + } + } while (edge = edge->next(), edge != cell.incident_edge()); + + if (!starting_vd_edge || !ending_vd_edge || starting_vd_edge == ending_vd_edge) + return true; + } + } + + return false; +} + +void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) +{ + // Check self intersections. + assert([&polys]() -> bool { + EdgeGrid::Grid grid; + grid.set_bbox(get_extents(polys)); + grid.create(polys, scaled(10.)); + return !grid.has_intersecting_edges(); + }()); + + vd_edge_to_he_edge.clear(); + vd_node_to_he_node.clear(); + + std::vector segments; + for (size_t poly_idx = 0; poly_idx < polys.size(); poly_idx++) + for (size_t point_idx = 0; point_idx < polys[poly_idx].size(); point_idx++) + segments.emplace_back(&polys, poly_idx, point_idx); + + Geometry::VoronoiDiagram voronoi_diagram; + construct_voronoi(segments.begin(), segments.end(), &voronoi_diagram); + + // Try to detect cases when some Voronoi vertex is missing. + // When any Voronoi vertex is missing, rotate input polygon and try again. + const bool has_missing_voronoi_vertex = detect_missing_voronoi_vertex(voronoi_diagram, segments); + const double fix_angle = PI / 6; + std::unordered_map vertex_mapping; + Polygons polys_copy = polys; + if (has_missing_voronoi_vertex) { + BOOST_LOG_TRIVIAL(debug) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth."; + for (Polygon &poly : polys_copy) + poly.rotate(fix_angle); + + assert(polys_copy.size() == polys.size()); + for (size_t poly_idx = 0; poly_idx < polys.size(); ++poly_idx) { + assert(polys_copy[poly_idx].size() == polys[poly_idx].size()); + for (size_t point_idx = 0; point_idx < polys[poly_idx].size(); ++point_idx) + vertex_mapping.insert({polys[poly_idx][point_idx], polys_copy[poly_idx][point_idx]}); + } + + segments.clear(); + for (size_t poly_idx = 0; poly_idx < polys_copy.size(); poly_idx++) + for (size_t point_idx = 0; point_idx < polys_copy[poly_idx].size(); point_idx++) + segments.emplace_back(&polys_copy, poly_idx, point_idx); + + voronoi_diagram.clear(); + construct_voronoi(segments.begin(), segments.end(), &voronoi_diagram); + assert(!detect_missing_voronoi_vertex(voronoi_diagram, segments)); + if (detect_missing_voronoi_vertex(voronoi_diagram, segments)) + BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex even after the rotation of input."; + } + + for (vd_t::cell_type cell : voronoi_diagram.cells()) { + if (!cell.incident_edge()) + continue; // There is no spoon + + Point start_source_point; + Point end_source_point; + vd_t::edge_type* starting_vonoroi_edge = nullptr; + vd_t::edge_type* ending_vonoroi_edge = nullptr; + // Compute and store result in above variables + + if (cell.contains_point()) { + const bool keep_going = computePointCellRange(cell, start_source_point, end_source_point, starting_vonoroi_edge, ending_vonoroi_edge, segments); + if (!keep_going) + continue; + } else { + assert(cell.contains_segment()); + computeSegmentCellRange(cell, start_source_point, end_source_point, starting_vonoroi_edge, ending_vonoroi_edge, segments); + } + + if (!starting_vonoroi_edge || !ending_vonoroi_edge) { + assert(false && "Each cell should start / end in a polygon vertex"); + continue; + } + + // Copy start to end edge to graph + edge_t* prev_edge = nullptr; + assert(VoronoiUtils::p(starting_vonoroi_edge->vertex1()).x() <= std::numeric_limits::max() && VoronoiUtils::p(starting_vonoroi_edge->vertex1()).x() >= std::numeric_limits::lowest()); + assert(VoronoiUtils::p(starting_vonoroi_edge->vertex1()).y() <= std::numeric_limits::max() && VoronoiUtils::p(starting_vonoroi_edge->vertex1()).y() >= std::numeric_limits::lowest()); + transferEdge(start_source_point, VoronoiUtils::p(starting_vonoroi_edge->vertex1()).cast(), *starting_vonoroi_edge, prev_edge, start_source_point, end_source_point, segments); + node_t* starting_node = vd_node_to_he_node[starting_vonoroi_edge->vertex0()]; + starting_node->data.distance_to_boundary = 0; + + constexpr bool is_next_to_start_or_end = true; + graph.makeRib(prev_edge, start_source_point, end_source_point, is_next_to_start_or_end); + for (vd_t::edge_type* vd_edge = starting_vonoroi_edge->next(); vd_edge != ending_vonoroi_edge; vd_edge = vd_edge->next()) { + assert(vd_edge->is_finite()); + + assert(VoronoiUtils::p(vd_edge->vertex0()).x() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge->vertex0()).x() >= std::numeric_limits::lowest()); + assert(VoronoiUtils::p(vd_edge->vertex0()).y() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge->vertex0()).y() >= std::numeric_limits::lowest()); + assert(VoronoiUtils::p(vd_edge->vertex1()).x() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge->vertex1()).x() >= std::numeric_limits::lowest()); + assert(VoronoiUtils::p(vd_edge->vertex1()).y() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge->vertex1()).y() >= std::numeric_limits::lowest()); + + Point v1 = VoronoiUtils::p(vd_edge->vertex0()).cast(); + Point v2 = VoronoiUtils::p(vd_edge->vertex1()).cast(); + transferEdge(v1, v2, *vd_edge, prev_edge, start_source_point, end_source_point, segments); + + graph.makeRib(prev_edge, start_source_point, end_source_point, vd_edge->next() == ending_vonoroi_edge); + } + + assert(VoronoiUtils::p(starting_vonoroi_edge->vertex0()).x() <= std::numeric_limits::max() && VoronoiUtils::p(starting_vonoroi_edge->vertex0()).x() >= std::numeric_limits::lowest()); + assert(VoronoiUtils::p(starting_vonoroi_edge->vertex0()).y() <= std::numeric_limits::max() && VoronoiUtils::p(starting_vonoroi_edge->vertex0()).y() >= std::numeric_limits::lowest()); + transferEdge(VoronoiUtils::p(ending_vonoroi_edge->vertex0()).cast(), end_source_point, *ending_vonoroi_edge, prev_edge, start_source_point, end_source_point, segments); + prev_edge->to->data.distance_to_boundary = 0; + } + + if (has_missing_voronoi_vertex) { + for (node_t &node : graph.nodes) { + // If a mapping exists between a rotated point and an original point, use this mapping. Otherwise, rotate a point in the opposite direction. + if (auto node_it = vertex_mapping.find(node.p); node_it != vertex_mapping.end()) + node.p = node_it->second; + else + node.p.rotate(-fix_angle); + } + } + + separatePointyQuadEndNodes(); + + graph.collapseSmallEdges(); + + // Set [incident_edge] the the first possible edge that way we can iterate over all reachable edges from node.incident_edge, + // without needing to iterate backward + for (edge_t& edge : graph.edges) + if (!edge.prev) + edge.from->incident_edge = &edge; +} + +void SkeletalTrapezoidation::separatePointyQuadEndNodes() +{ + std::unordered_set visited_nodes; + for (edge_t& edge : graph.edges) + { + if (edge.prev) + { + continue; + } + edge_t* quad_start = &edge; + if (visited_nodes.find(quad_start->from) == visited_nodes.end()) + { + visited_nodes.emplace(quad_start->from); + } + else + { // Needs to be duplicated + graph.nodes.emplace_back(*quad_start->from); + node_t* new_node = &graph.nodes.back(); + new_node->incident_edge = quad_start; + quad_start->from = new_node; + quad_start->twin->to = new_node; + } + } +} + +// +// ^^^^^^^^^^^^^^^^^^^^^ +// INITIALIZATION +// ===================== +// +// ===================== +// TRANSTISIONING +// vvvvvvvvvvvvvvvvvvvvv +// + +#if 0 +static void export_graph_to_svg(const std::string &path, const SkeletalTrapezoidationGraph &graph, const Polygons &polys) +{ + const std::vector colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "green", "yellow"}; + coordf_t stroke_width = scale_(0.05); + BoundingBox bbox; + for (const auto &node : graph.nodes) + bbox.merge(node.p); + + bbox.offset(scale_(1.)); + ::Slic3r::SVG svg(path.c_str(), bbox); + for (const auto &line : to_lines(polys)) + svg.draw(line, "red", stroke_width); + + for (const auto &edge : graph.edges) + svg.draw(Line(edge.from->p, edge.to->p), "cyan", scale_(0.01)); +} +#endif + +void SkeletalTrapezoidation::generateToolpaths(std::vector &generated_toolpaths, bool filter_outermost_central_edges) +{ + p_generated_toolpaths = &generated_toolpaths; + + updateIsCentral(); + + filterCentral(central_filter_dist); + + if (filter_outermost_central_edges) + filterOuterCentral(); + + updateBeadCount(); + + filterNoncentralRegions(); + + generateTransitioningRibs(); + + generateExtraRibs(); + + generateSegments(); +} + +void SkeletalTrapezoidation::updateIsCentral() +{ + // _.-'^` A and B are the endpoints of an edge we're checking. + // _.-'^` Part of the line AB will be used as a cap, + // _.-'^` \ because the polygon is too narrow there. + // _.-'^` \ If |AB| minus the cap is still bigger than dR, + // _.-'^` \ R2 the edge AB is considered central. It's then + // _.-'^` \ _.-'\`\ significant compared to the edges around it. + // _.-'^` \R1 _.-'^` '`\ dR + // _.-'^`a/2 \_.-'^`a \ Line AR2 is parallel to the polygon contour. + // `^'-._````````````````A```````````v````````B``````` dR is the remaining diameter at B. + // `^'-._ dD = |AB| As a result, AB is less often central if the polygon + // `^'-._ corner is obtuse. + // sin a = dR / dD + + coord_t outer_edge_filter_length = beading_strategy.getTransitionThickness(0) / 2; + + float cap = sin(beading_strategy.getTransitioningAngle() * 0.5); // = cos(bisector_angle / 2) + for (edge_t& edge: graph.edges) + { + assert(edge.twin); + if(!edge.twin) + { + BOOST_LOG_TRIVIAL(warning) << "Encountered a Voronoi edge without twin!"; + continue; + } + if(edge.twin->data.centralIsSet()) + { + edge.data.setIsCentral(edge.twin->data.isCentral()); + } + else if(edge.data.type == SkeletalTrapezoidationEdge::EdgeType::EXTRA_VD) + { + edge.data.setIsCentral(false); + } + else if(std::max(edge.from->data.distance_to_boundary, edge.to->data.distance_to_boundary) < outer_edge_filter_length) + { + edge.data.setIsCentral(false); + } + else + { + Point a = edge.from->p; + Point b = edge.to->p; + Point ab = b - a; + coord_t dR = std::abs(edge.to->data.distance_to_boundary - edge.from->data.distance_to_boundary); + coord_t dD = ab.cast().norm(); + edge.data.setIsCentral(dR < dD * cap); + } + } +} + +void SkeletalTrapezoidation::filterCentral(coord_t max_length) +{ + for (edge_t& edge : graph.edges) + { + if (isEndOfCentral(edge) && edge.to->isLocalMaximum() && !edge.to->isLocalMaximum()) + { + filterCentral(edge.twin, 0, max_length); + } + } +} + +bool SkeletalTrapezoidation::filterCentral(edge_t* starting_edge, coord_t traveled_dist, coord_t max_length) +{ + coord_t length = (starting_edge->from->p - starting_edge->to->p).cast().norm(); + if (traveled_dist + length > max_length) + { + return false; + } + + bool should_dissolve = true; //Should we unmark this as central and propagate that? + for (edge_t* next_edge = starting_edge->next; next_edge && next_edge != starting_edge->twin; next_edge = next_edge->twin->next) + { + if (next_edge->data.isCentral()) + { + should_dissolve &= filterCentral(next_edge, traveled_dist + length, max_length); + } + } + + should_dissolve &= !starting_edge->to->isLocalMaximum(); // Don't filter central regions with a local maximum! + if (should_dissolve) + { + starting_edge->data.setIsCentral(false); + starting_edge->twin->data.setIsCentral(false); + } + return should_dissolve; +} + +void SkeletalTrapezoidation::filterOuterCentral() +{ + for (edge_t& edge : graph.edges) + { + if (!edge.prev) + { + edge.data.setIsCentral(false); + edge.twin->data.setIsCentral(false); + } + } +} + +void SkeletalTrapezoidation::updateBeadCount() +{ + for (edge_t& edge : graph.edges) + { + if (edge.data.isCentral()) + { + edge.to->data.bead_count = beading_strategy.getOptimalBeadCount(edge.to->data.distance_to_boundary * 2); + } + } + + // Fix bead count at locally maximal R, also for central regions!! See TODO s in generateTransitionEnd(.) + for (node_t& node : graph.nodes) + { + if (node.isLocalMaximum()) + { + if (node.data.distance_to_boundary < 0) + { + BOOST_LOG_TRIVIAL(warning) << "Distance to boundary not yet computed for local maximum!"; + node.data.distance_to_boundary = std::numeric_limits::max(); + edge_t* edge = node.incident_edge; + do + { + node.data.distance_to_boundary = std::min(node.data.distance_to_boundary, edge->to->data.distance_to_boundary + coord_t((edge->from->p - edge->to->p).cast().norm())); + } while (edge = edge->twin->next, edge != node.incident_edge); + } + coord_t bead_count = beading_strategy.getOptimalBeadCount(node.data.distance_to_boundary * 2); + node.data.bead_count = bead_count; + } + } +} + +void SkeletalTrapezoidation::filterNoncentralRegions() +{ + for (edge_t& edge : graph.edges) + { + if (!isEndOfCentral(edge)) + { + continue; + } + if(edge.to->data.bead_count < 0 && edge.to->data.distance_to_boundary != 0) + { + BOOST_LOG_TRIVIAL(warning) << "Encountered an uninitialized bead at the boundary!"; + } + assert(edge.to->data.bead_count >= 0 || edge.to->data.distance_to_boundary == 0); + constexpr coord_t max_dist = scaled(0.4); + filterNoncentralRegions(&edge, edge.to->data.bead_count, 0, max_dist); + } +} + +bool SkeletalTrapezoidation::filterNoncentralRegions(edge_t* to_edge, coord_t bead_count, coord_t traveled_dist, coord_t max_dist) +{ + coord_t r = to_edge->to->data.distance_to_boundary; + + edge_t* next_edge = to_edge->next; + for (; next_edge && next_edge != to_edge->twin; next_edge = next_edge->twin->next) + { + if (next_edge->to->data.distance_to_boundary >= r || shorter_then(next_edge->to->p - next_edge->from->p, scaled(0.01))) + { + break; // Only walk upward + } + } + if (next_edge == to_edge->twin || ! next_edge) + { + return false; + } + + const coord_t length = (next_edge->to->p - next_edge->from->p).cast().norm(); + + bool dissolve = false; + if (next_edge->to->data.bead_count == bead_count) + { + dissolve = true; + } + else if (next_edge->to->data.bead_count < 0) + { + dissolve = filterNoncentralRegions(next_edge, bead_count, traveled_dist + length, max_dist); + } + else // Upward bead count is different + { + // Dissolve if two central regions with different bead count are closer together than the max_dist (= transition distance) + dissolve = (traveled_dist + length < max_dist) && std::abs(next_edge->to->data.bead_count - bead_count) == 1; + } + + if (dissolve) + { + next_edge->data.setIsCentral(true); + next_edge->twin->data.setIsCentral(true); + next_edge->to->data.bead_count = beading_strategy.getOptimalBeadCount(next_edge->to->data.distance_to_boundary * 2); + next_edge->to->data.transition_ratio = 0; + } + return dissolve; // Dissolving only depend on the one edge going upward. There cannot be multiple edges going upward. +} + +void SkeletalTrapezoidation::generateTransitioningRibs() +{ + // Store the upward edges to the transitions. + // We only store the halfedge for which the distance_to_boundary is higher at the end than at the beginning. + ptr_vector_t> edge_transitions; + generateTransitionMids(edge_transitions); + + for (edge_t& edge : graph.edges) + { // Check if there is a transition in between nodes with different bead counts + if (edge.data.isCentral() && edge.from->data.bead_count != edge.to->data.bead_count) + { + assert(edge.data.hasTransitions() || edge.twin->data.hasTransitions()); + } + } + + filterTransitionMids(); + + ptr_vector_t> edge_transition_ends; // We only map the half edge in the upward direction. mapped items are not sorted + generateAllTransitionEnds(edge_transition_ends); + + applyTransitions(edge_transition_ends); + // Note that the shared pointer lists will be out of scope and thus destroyed here, since the remaining refs are weak_ptr. +} + + +void SkeletalTrapezoidation::generateTransitionMids(ptr_vector_t>& edge_transitions) +{ + for (edge_t& edge : graph.edges) + { + assert(edge.data.centralIsSet()); + if (!edge.data.isCentral()) + { // Only central regions introduce transitions + continue; + } + coord_t start_R = edge.from->data.distance_to_boundary; + coord_t end_R = edge.to->data.distance_to_boundary; + int start_bead_count = edge.from->data.bead_count; + int end_bead_count = edge.to->data.bead_count; + + if (start_R == end_R) + { // No transitions occur when both end points have the same distance_to_boundary + assert(edge.from->data.bead_count == edge.to->data.bead_count); + if(edge.from->data.bead_count != edge.to->data.bead_count) + { + BOOST_LOG_TRIVIAL(warning) << "Bead count " << edge.from->data.bead_count << " is different from " << edge.to->data.bead_count << " even though distance to boundary is the same."; + } + continue; + } + else if (start_R > end_R) + { // Only consider those half-edges which are going from a lower to a higher distance_to_boundary + continue; + } + + if (edge.from->data.bead_count == edge.to->data.bead_count) + { // No transitions should occur according to the enforced bead counts + continue; + } + + if (start_bead_count > beading_strategy.getOptimalBeadCount(start_R * 2) + || end_bead_count > beading_strategy.getOptimalBeadCount(end_R * 2)) + { // Wasn't the case earlier in this function because of already introduced transitions + BOOST_LOG_TRIVIAL(error) << "transitioning segment overlap! (?)"; + } + assert(start_R < end_R); + if(start_R >= end_R) + { + BOOST_LOG_TRIVIAL(warning) << "Transitioning the wrong way around! This function expects to transition from small R to big R, but was transitioning from " << start_R << " to " << end_R; + } + coord_t edge_size = (edge.from->p - edge.to->p).cast().norm(); + for (int transition_lower_bead_count = start_bead_count; transition_lower_bead_count < end_bead_count; transition_lower_bead_count++) + { + coord_t mid_R = beading_strategy.getTransitionThickness(transition_lower_bead_count) / 2; + if (mid_R > end_R) + { + BOOST_LOG_TRIVIAL(error) << "transition on segment lies outside of segment!"; + mid_R = end_R; + } + if (mid_R < start_R) + { + BOOST_LOG_TRIVIAL(error) << "transition on segment lies outside of segment!"; + mid_R = start_R; + } + coord_t mid_pos = int64_t(edge_size) * int64_t(mid_R - start_R) / int64_t(end_R - start_R); + + assert(mid_pos >= 0); + assert(mid_pos <= edge_size); + if(mid_pos < 0 || mid_pos > edge_size) + { + BOOST_LOG_TRIVIAL(warning) << "Transition mid is out of bounds of the edge."; + } + auto transitions = edge.data.getTransitions(); + constexpr bool ignore_empty = true; + assert((! edge.data.hasTransitions(ignore_empty)) || mid_pos >= transitions->back().pos); + if (! edge.data.hasTransitions(ignore_empty)) + { + edge_transitions.emplace_back(std::make_shared>()); + edge.data.setTransitions(edge_transitions.back()); // initialization + transitions = edge.data.getTransitions(); + } + transitions->emplace_back(mid_pos, transition_lower_bead_count, mid_R); + } + assert((edge.from->data.bead_count == edge.to->data.bead_count) || edge.data.hasTransitions()); + } +} + +void SkeletalTrapezoidation::filterTransitionMids() +{ + for (edge_t& edge : graph.edges) + { + if (! edge.data.hasTransitions()) + { + continue; + } + auto& transitions = *edge.data.getTransitions(); + + // This is how stuff should be stored in transitions + assert(transitions.front().lower_bead_count <= transitions.back().lower_bead_count); + assert(edge.from->data.distance_to_boundary <= edge.to->data.distance_to_boundary); + + const Point a = edge.from->p; + const Point b = edge.to->p; + Point ab = b - a; + coord_t ab_size = ab.cast().norm(); + + bool going_up = true; + std::list to_be_dissolved_back = dissolveNearbyTransitions(&edge, transitions.back(), ab_size - transitions.back().pos, transition_filter_dist, going_up); + bool should_dissolve_back = !to_be_dissolved_back.empty(); + for (TransitionMidRef& ref : to_be_dissolved_back) + { + dissolveBeadCountRegion(&edge, transitions.back().lower_bead_count + 1, transitions.back().lower_bead_count); + ref.edge->data.getTransitions()->erase(ref.transition_it); + } + + { + coord_t trans_bead_count = transitions.back().lower_bead_count; + coord_t upper_transition_half_length = (1.0 - beading_strategy.getTransitionAnchorPos(trans_bead_count)) * beading_strategy.getTransitioningLength(trans_bead_count); + should_dissolve_back |= filterEndOfCentralTransition(&edge, ab_size - transitions.back().pos, upper_transition_half_length, trans_bead_count); + } + + if (should_dissolve_back) + { + transitions.pop_back(); + } + if (transitions.empty()) + { // FilterEndOfCentralTransition gives inconsistent new bead count when executing for the same transition in two directions. + continue; + } + + going_up = false; + std::list to_be_dissolved_front = dissolveNearbyTransitions(edge.twin, transitions.front(), transitions.front().pos, transition_filter_dist, going_up); + bool should_dissolve_front = !to_be_dissolved_front.empty(); + for (TransitionMidRef& ref : to_be_dissolved_front) + { + dissolveBeadCountRegion(edge.twin, transitions.front().lower_bead_count, transitions.front().lower_bead_count + 1); + ref.edge->data.getTransitions()->erase(ref.transition_it); + } + + { + coord_t trans_bead_count = transitions.front().lower_bead_count; + coord_t lower_transition_half_length = beading_strategy.getTransitionAnchorPos(trans_bead_count) * beading_strategy.getTransitioningLength(trans_bead_count); + should_dissolve_front |= filterEndOfCentralTransition(edge.twin, transitions.front().pos, lower_transition_half_length, trans_bead_count + 1); + } + + if (should_dissolve_front) + { + transitions.pop_front(); + } + if (transitions.empty()) + { // FilterEndOfCentralTransition gives inconsistent new bead count when executing for the same transition in two directions. + continue; + } + } +} + +std::list SkeletalTrapezoidation::dissolveNearbyTransitions(edge_t* edge_to_start, TransitionMiddle& origin_transition, coord_t traveled_dist, coord_t max_dist, bool going_up) +{ + std::list to_be_dissolved; + if (traveled_dist > max_dist) + return to_be_dissolved; + + bool should_dissolve = true; + for (edge_t* edge = edge_to_start->next; edge && edge != edge_to_start->twin; edge = edge->twin->next){ + if (!edge->data.isCentral()) + continue; + + Point a = edge->from->p; + Point b = edge->to->p; + Point ab = b - a; + coord_t ab_size = ab.cast().norm(); + bool is_aligned = edge->isUpward(); + edge_t* aligned_edge = is_aligned? edge : edge->twin; + bool seen_transition_on_this_edge = false; + + const coord_t origin_radius = origin_transition.feature_radius; + const coord_t radius_here = edge->from->data.distance_to_boundary; + const bool dissolve_result_is_odd = bool(origin_transition.lower_bead_count % 2) == going_up; + const coord_t width_deviation = std::abs(origin_radius - radius_here) * 2; // times by two because the deviation happens at both sides of the significant edge + const coord_t line_width_deviation = dissolve_result_is_odd ? width_deviation : width_deviation / 2; // assume the deviation will be split over either 1 or 2 lines, i.e. assume wall_distribution_count = 1 + if (line_width_deviation > allowed_filter_deviation) + should_dissolve = false; + + if (should_dissolve && aligned_edge->data.hasTransitions()) { + auto& transitions = *aligned_edge->data.getTransitions(); + for (auto transition_it = transitions.begin(); transition_it != transitions.end(); ++ transition_it) { // Note: this is not necessarily iterating in the traveling direction! + // Check whether we should dissolve + coord_t pos = is_aligned? transition_it->pos : ab_size - transition_it->pos; + if (traveled_dist + pos < max_dist && transition_it->lower_bead_count == origin_transition.lower_bead_count) { // Only dissolve local optima + if (traveled_dist + pos < beading_strategy.getTransitioningLength(transition_it->lower_bead_count)) { + // Consecutive transitions both in/decreasing in bead count should never be closer together than the transition distance + assert(going_up != is_aligned || transition_it->lower_bead_count == 0); + } + to_be_dissolved.emplace_back(aligned_edge, transition_it); + seen_transition_on_this_edge = true; + } + } + } + if (should_dissolve && !seen_transition_on_this_edge) { + std::list to_be_dissolved_here = dissolveNearbyTransitions(edge, origin_transition, traveled_dist + ab_size, max_dist, going_up); + if (to_be_dissolved_here.empty()) { // The region is too long to be dissolved in this direction, so it cannot be dissolved in any direction. + to_be_dissolved.clear(); + return to_be_dissolved; + } + to_be_dissolved.splice(to_be_dissolved.end(), to_be_dissolved_here); // Transfer to_be_dissolved_here into to_be_dissolved + should_dissolve = should_dissolve && !to_be_dissolved.empty(); + } + } + + if (!should_dissolve) + to_be_dissolved.clear(); + + return to_be_dissolved; +} + + +void SkeletalTrapezoidation::dissolveBeadCountRegion(edge_t* edge_to_start, coord_t from_bead_count, coord_t to_bead_count) +{ + assert(from_bead_count != to_bead_count); + if (edge_to_start->to->data.bead_count != from_bead_count) + return; + + edge_to_start->to->data.bead_count = to_bead_count; + for (edge_t* edge = edge_to_start->next; edge && edge != edge_to_start->twin; edge = edge->twin->next) + { + if (!edge->data.isCentral()) + { + continue; + } + dissolveBeadCountRegion(edge, from_bead_count, to_bead_count); + } +} + +bool SkeletalTrapezoidation::filterEndOfCentralTransition(edge_t* edge_to_start, coord_t traveled_dist, coord_t max_dist, coord_t replacing_bead_count) +{ + if (traveled_dist > max_dist) + { + return false; + } + + bool is_end_of_central = true; + bool should_dissolve = false; + for (edge_t* next_edge = edge_to_start->next; next_edge && next_edge != edge_to_start->twin; next_edge = next_edge->twin->next) + { + if (next_edge->data.isCentral()) + { + coord_t length = (next_edge->to->p - next_edge->from->p).cast().norm(); + should_dissolve |= filterEndOfCentralTransition(next_edge, traveled_dist + length, max_dist, replacing_bead_count); + is_end_of_central = false; + } + } + if (is_end_of_central && traveled_dist < max_dist) + { + should_dissolve = true; + } + + if (should_dissolve) + { + edge_to_start->to->data.bead_count = replacing_bead_count; + } + return should_dissolve; +} + +void SkeletalTrapezoidation::generateAllTransitionEnds(ptr_vector_t>& edge_transition_ends) +{ + for (edge_t& edge : graph.edges) + { + if (! edge.data.hasTransitions()) + { + continue; + } + auto& transition_positions = *edge.data.getTransitions(); + + assert(edge.from->data.distance_to_boundary <= edge.to->data.distance_to_boundary); + for (TransitionMiddle& transition_middle : transition_positions) + { + assert(transition_positions.front().pos <= transition_middle.pos); + assert(transition_middle.pos <= transition_positions.back().pos); + generateTransitionEnds(edge, transition_middle.pos, transition_middle.lower_bead_count, edge_transition_ends); + } + } +} + +void SkeletalTrapezoidation::generateTransitionEnds(edge_t& edge, coord_t mid_pos, coord_t lower_bead_count, ptr_vector_t>& edge_transition_ends) +{ + const Point a = edge.from->p; + const Point b = edge.to->p; + const Point ab = b - a; + const coord_t ab_size = ab.cast().norm(); + + const coord_t transition_length = beading_strategy.getTransitioningLength(lower_bead_count); + const float transition_mid_position = beading_strategy.getTransitionAnchorPos(lower_bead_count); + constexpr float inner_bead_width_ratio_after_transition = 1.0; + + constexpr coord_t start_rest = 0; + const float mid_rest = transition_mid_position * inner_bead_width_ratio_after_transition; + constexpr float end_rest = inner_bead_width_ratio_after_transition; + + { // Lower bead count transition end + const coord_t start_pos = ab_size - mid_pos; + const coord_t transition_half_length = transition_mid_position * int64_t(transition_length); + const coord_t end_pos = start_pos + transition_half_length; + generateTransitionEnd(*edge.twin, start_pos, end_pos, transition_half_length, mid_rest, start_rest, lower_bead_count, edge_transition_ends); + } + + { // Upper bead count transition end + const coord_t start_pos = mid_pos; + const coord_t transition_half_length = (1.0 - transition_mid_position) * transition_length; + const coord_t end_pos = mid_pos + transition_half_length; +#ifdef DEBUG + if (! generateTransitionEnd(edge, start_pos, end_pos, transition_half_length, mid_rest, end_rest, lower_bead_count, edge_transition_ends)) + { + BOOST_LOG_TRIVIAL(warning) << "There must have been at least one direction in which the bead count is increasing enough for the transition to happen!"; + } +#else + generateTransitionEnd(edge, start_pos, end_pos, transition_half_length, mid_rest, end_rest, lower_bead_count, edge_transition_ends); +#endif + } +} + +bool SkeletalTrapezoidation::generateTransitionEnd(edge_t& edge, coord_t start_pos, coord_t end_pos, coord_t transition_half_length, double start_rest, double end_rest, coord_t lower_bead_count, ptr_vector_t>& edge_transition_ends) +{ + Point a = edge.from->p; + Point b = edge.to->p; + Point ab = b - a; + coord_t ab_size = ab.cast().norm(); // TODO: prevent recalculation of these values + + assert(start_pos <= ab_size); + if(start_pos > ab_size) + { + BOOST_LOG_TRIVIAL(warning) << "Start position of edge is beyond edge range."; + } + + bool going_up = end_rest > start_rest; + + assert(edge.data.isCentral()); + if (!edge.data.isCentral()) + { + BOOST_LOG_TRIVIAL(warning) << "This function shouldn't generate ends in or beyond non-central regions."; + return false; + } + + if (end_pos > ab_size) + { // Recurse on all further edges + float rest = end_rest - (start_rest - end_rest) * (end_pos - ab_size) / (start_pos - end_pos); + assert(rest >= 0); + assert(rest <= std::max(end_rest, start_rest)); + assert(rest >= std::min(end_rest, start_rest)); + + coord_t central_edge_count = 0; + for (edge_t* outgoing = edge.next; outgoing && outgoing != edge.twin; outgoing = outgoing->twin->next) + { + if (!outgoing->data.isCentral()) continue; + central_edge_count++; + } + + bool is_only_going_down = true; + bool has_recursed = false; + for (edge_t* outgoing = edge.next; outgoing && outgoing != edge.twin;) + { + edge_t* next = outgoing->twin->next; // Before we change the outgoing edge itself + if (!outgoing->data.isCentral()) + { + outgoing = next; + continue; // Don't put transition ends in non-central regions + } + if (central_edge_count > 1 && going_up && isGoingDown(outgoing, 0, end_pos - ab_size + transition_half_length, lower_bead_count)) + { // We're after a 3-way_all-central_junction-node and going in the direction of lower bead count + // don't introduce a transition end along this central direction, because this direction is the downward direction + // while we are supposed to be [going_up] + outgoing = next; + continue; + } + bool is_going_down = generateTransitionEnd(*outgoing, 0, end_pos - ab_size, transition_half_length, rest, end_rest, lower_bead_count, edge_transition_ends); + is_only_going_down &= is_going_down; + outgoing = next; + has_recursed = true; + } + if (!going_up || (has_recursed && !is_only_going_down)) + { + edge.to->data.transition_ratio = rest; + edge.to->data.bead_count = lower_bead_count; + } + return is_only_going_down; + } + else // end_pos < ab_size + { // Add transition end point here + bool is_lower_end = end_rest == 0; // TODO collapse this parameter into the bool for which it is used here! + coord_t pos = -1; + + edge_t* upward_edge = nullptr; + if (edge.isUpward()) + { + upward_edge = &edge; + pos = end_pos; + } + else + { + upward_edge = edge.twin; + pos = ab_size - end_pos; + } + + if(!upward_edge->data.hasTransitionEnds()) + { + //This edge doesn't have a data structure yet for the transition ends. Make one. + edge_transition_ends.emplace_back(std::make_shared>()); + upward_edge->data.setTransitionEnds(edge_transition_ends.back()); + } + auto transitions = upward_edge->data.getTransitionEnds(); + + //Add a transition to it (on the correct side). + assert(ab_size == (edge.twin->from->p - edge.twin->to->p).cast().norm()); + assert(pos <= ab_size); + if (transitions->empty() || pos < transitions->front().pos) + { // Preorder so that sorting later on is faster + transitions->emplace_front(pos, lower_bead_count, is_lower_end); + } + else + { + transitions->emplace_back(pos, lower_bead_count, is_lower_end); + } + return false; + } +} + + +bool SkeletalTrapezoidation::isGoingDown(edge_t* outgoing, coord_t traveled_dist, coord_t max_dist, coord_t lower_bead_count) const +{ + // NOTE: the logic below is not fully thought through. + // TODO: take transition mids into account + if (outgoing->to->data.distance_to_boundary == 0) + { + return true; + } + bool is_upward = outgoing->to->data.distance_to_boundary >= outgoing->from->data.distance_to_boundary; + edge_t* upward_edge = is_upward? outgoing : outgoing->twin; + if (outgoing->to->data.bead_count > lower_bead_count + 1) + { + assert(upward_edge->data.hasTransitions() && "If the bead count is going down there has to be a transition mid!"); + if(!upward_edge->data.hasTransitions()) + { + BOOST_LOG_TRIVIAL(warning) << "If the bead count is going down there has to be a transition mid!"; + } + return false; + } + coord_t length = (outgoing->to->p - outgoing->from->p).cast().norm(); + if (upward_edge->data.hasTransitions()) + { + auto& transition_mids = *upward_edge->data.getTransitions(); + TransitionMiddle& mid = is_upward? transition_mids.front() : transition_mids.back(); + if ( + mid.lower_bead_count == lower_bead_count && + ((is_upward && mid.pos + traveled_dist < max_dist) + || (!is_upward && length - mid.pos + traveled_dist < max_dist)) + ) + { + return true; + } + } + if (traveled_dist + length > max_dist) + { + return false; + } + if (outgoing->to->data.bead_count <= lower_bead_count + && !(outgoing->to->data.bead_count == lower_bead_count && outgoing->to->data.transition_ratio > 0.0)) + { + return true; + } + + bool is_only_going_down = true; + bool has_recursed = false; + for (edge_t* next = outgoing->next; next && next != outgoing->twin; next = next->twin->next) + { + if (!next->data.isCentral()) + { + continue; + } + bool is_going_down = isGoingDown(next, traveled_dist + length, max_dist, lower_bead_count); + is_only_going_down &= is_going_down; + has_recursed = true; + } + return has_recursed && is_only_going_down; +} + +static inline Point normal(const Point& p0, coord_t len) +{ + int64_t _len = p0.cast().norm(); + if (_len < 1) + return Point(len, 0); + return (p0.cast() * int64_t(len) / _len).cast(); +}; + +void SkeletalTrapezoidation::applyTransitions(ptr_vector_t>& edge_transition_ends) +{ + for (edge_t& edge : graph.edges) + { + if (edge.twin->data.hasTransitionEnds()) + { + coord_t length = (edge.from->p - edge.to->p).cast().norm(); + auto& twin_transition_ends = *edge.twin->data.getTransitionEnds(); + if (! edge.data.hasTransitionEnds()) + { + edge_transition_ends.emplace_back(std::make_shared>()); + edge.data.setTransitionEnds(edge_transition_ends.back()); + } + auto& transition_ends = *edge.data.getTransitionEnds(); + for (TransitionEnd& end : twin_transition_ends) + { + transition_ends.emplace_back(length - end.pos, end.lower_bead_count, end.is_lower_end); + } + twin_transition_ends.clear(); + } + } + + for (edge_t& edge : graph.edges) + { + if (! edge.data.hasTransitionEnds()) + { + continue; + } + + assert(edge.data.isCentral()); + + auto& transitions = *edge.data.getTransitionEnds(); + transitions.sort([](const TransitionEnd& a, const TransitionEnd& b) { return a.pos < b.pos; } ); + + node_t* from = edge.from; + node_t* to = edge.to; + Point a = from->p; + Point b = to->p; + Point ab = b - a; + coord_t ab_size = (ab).cast().norm(); + + edge_t* last_edge_replacing_input = &edge; + for (TransitionEnd& transition_end : transitions) + { + coord_t new_node_bead_count = transition_end.is_lower_end? transition_end.lower_bead_count : transition_end.lower_bead_count + 1; + coord_t end_pos = transition_end.pos; + node_t* close_node = (end_pos < ab_size / 2)? from : to; + if ((end_pos < snap_dist || end_pos > ab_size - snap_dist) + && close_node->data.bead_count == new_node_bead_count + ) + { + assert(end_pos <= ab_size); + close_node->data.transition_ratio = 0; + continue; + } + Point mid = a + normal(ab, end_pos); + + assert(last_edge_replacing_input->data.isCentral()); + assert(last_edge_replacing_input->data.type != SkeletalTrapezoidationEdge::EdgeType::EXTRA_VD); + last_edge_replacing_input = graph.insertNode(last_edge_replacing_input, mid, new_node_bead_count); + assert(last_edge_replacing_input->data.type != SkeletalTrapezoidationEdge::EdgeType::EXTRA_VD); + assert(last_edge_replacing_input->data.isCentral()); + } + } +} + +bool SkeletalTrapezoidation::isEndOfCentral(const edge_t& edge_to) const +{ + if (!edge_to.data.isCentral()) + { + return false; + } + if (!edge_to.next) + { + return true; + } + for (const edge_t* edge = edge_to.next; edge && edge != edge_to.twin; edge = edge->twin->next) + { + if (edge->data.isCentral()) + { + return false; + } + assert(edge->twin); + } + return true; +} + +void SkeletalTrapezoidation::generateExtraRibs() +{ + for (auto edge_it = graph.edges.begin(); edge_it != graph.edges.end(); ++edge_it) + { + edge_t& edge = *edge_it; + + if (!edge.data.isCentral() + || shorter_then(edge.to->p - edge.from->p, discretization_step_size) + || edge.from->data.distance_to_boundary >= edge.to->data.distance_to_boundary) + { + continue; + } + + + std::vector rib_thicknesses = beading_strategy.getNonlinearThicknesses(edge.from->data.bead_count); + + if (rib_thicknesses.empty()) continue; + + // Preload some variables before [edge] gets changed + node_t* from = edge.from; + node_t* to = edge.to; + Point a = from->p; + Point b = to->p; + Point ab = b - a; + coord_t ab_size = ab.cast().norm(); + coord_t a_R = edge.from->data.distance_to_boundary; + coord_t b_R = edge.to->data.distance_to_boundary; + + edge_t* last_edge_replacing_input = &edge; + for (coord_t rib_thickness : rib_thicknesses) + { + if (rib_thickness / 2 <= a_R) + { + continue; + } + if (rib_thickness / 2 >= b_R) + { + break; + } + + coord_t new_node_bead_count = std::min(edge.from->data.bead_count, edge.to->data.bead_count); + coord_t end_pos = int64_t(ab_size) * int64_t(rib_thickness / 2 - a_R) / int64_t(b_R - a_R); + assert(end_pos > 0); + assert(end_pos < ab_size); + node_t* close_node = (end_pos < ab_size / 2)? from : to; + if ((end_pos < snap_dist || end_pos > ab_size - snap_dist) + && close_node->data.bead_count == new_node_bead_count + ) + { + assert(end_pos <= ab_size); + close_node->data.transition_ratio = 0; + continue; + } + Point mid = a + normal(ab, end_pos); + + assert(last_edge_replacing_input->data.isCentral()); + assert(last_edge_replacing_input->data.type != SkeletalTrapezoidationEdge::EdgeType::EXTRA_VD); + last_edge_replacing_input = graph.insertNode(last_edge_replacing_input, mid, new_node_bead_count); + assert(last_edge_replacing_input->data.type != SkeletalTrapezoidationEdge::EdgeType::EXTRA_VD); + assert(last_edge_replacing_input->data.isCentral()); + } + } +} + +// +// ^^^^^^^^^^^^^^^^^^^^^ +// TRANSTISIONING +// ===================== +// TOOLPATH GENERATION +// vvvvvvvvvvvvvvvvvvvvv +// + +void SkeletalTrapezoidation::generateSegments() +{ + std::vector upward_quad_mids; + for (edge_t& edge : graph.edges) + { + if (edge.prev && edge.next && edge.isUpward()) + { + upward_quad_mids.emplace_back(&edge); + } + } + + std::sort(upward_quad_mids.begin(), upward_quad_mids.end(), [](edge_t* a, edge_t* b) + { + if (a->to->data.distance_to_boundary == b->to->data.distance_to_boundary) + { // Ordering between two 'upward' edges of the same distance is important when one of the edges is flat and connected to the other + if (a->from->data.distance_to_boundary == a->to->data.distance_to_boundary + && b->from->data.distance_to_boundary == b->to->data.distance_to_boundary) + { + coord_t max = std::numeric_limits::max(); + coord_t a_dist_from_up = std::min(a->distToGoUp().value_or(max), a->twin->distToGoUp().value_or(max)) - (a->to->p - a->from->p).cast().norm(); + coord_t b_dist_from_up = std::min(b->distToGoUp().value_or(max), b->twin->distToGoUp().value_or(max)) - (b->to->p - b->from->p).cast().norm(); + return a_dist_from_up < b_dist_from_up; + } + else if (a->from->data.distance_to_boundary == a->to->data.distance_to_boundary) + { + return true; // Edge a might be 'above' edge b + } + else if (b->from->data.distance_to_boundary == b->to->data.distance_to_boundary) + { + return false; // Edge b might be 'above' edge a + } + else + { + // Ordering is not important + } + } + return a->to->data.distance_to_boundary > b->to->data.distance_to_boundary; + }); + + ptr_vector_t node_beadings; + { // Store beading + for (node_t& node : graph.nodes) + { + if (node.data.bead_count <= 0) + { + continue; + } + if (node.data.transition_ratio == 0) + { + node_beadings.emplace_back(new BeadingPropagation(beading_strategy.compute(node.data.distance_to_boundary * 2, node.data.bead_count))); + node.data.setBeading(node_beadings.back()); + assert(node_beadings.back()->beading.total_thickness == node.data.distance_to_boundary * 2); + if(node_beadings.back()->beading.total_thickness != node.data.distance_to_boundary * 2) + { + BOOST_LOG_TRIVIAL(warning) << "If transitioning to an endpoint (ratio 0), the node should be exactly in the middle."; + } + } + else + { + Beading low_count_beading = beading_strategy.compute(node.data.distance_to_boundary * 2, node.data.bead_count); + Beading high_count_beading = beading_strategy.compute(node.data.distance_to_boundary * 2, node.data.bead_count + 1); + Beading merged = interpolate(low_count_beading, 1.0 - node.data.transition_ratio, high_count_beading); + node_beadings.emplace_back(new BeadingPropagation(merged)); + node.data.setBeading(node_beadings.back()); + assert(merged.total_thickness == node.data.distance_to_boundary * 2); + if(merged.total_thickness != node.data.distance_to_boundary * 2) + { + BOOST_LOG_TRIVIAL(warning) << "If merging two beads, the new bead must be exactly in the middle."; + } + } + } + } + + propagateBeadingsUpward(upward_quad_mids, node_beadings); + + propagateBeadingsDownward(upward_quad_mids, node_beadings); + + ptr_vector_t edge_junctions; // junctions ordered high R to low R + generateJunctions(node_beadings, edge_junctions); + + connectJunctions(edge_junctions); + + generateLocalMaximaSingleBeads(); +} + +SkeletalTrapezoidation::edge_t* SkeletalTrapezoidation::getQuadMaxRedgeTo(edge_t* quad_start_edge) +{ + assert(quad_start_edge->prev == nullptr); + assert(quad_start_edge->from->data.distance_to_boundary == 0); + coord_t max_R = -1; + edge_t* ret = nullptr; + for (edge_t* edge = quad_start_edge; edge; edge = edge->next) + { + coord_t r = edge->to->data.distance_to_boundary; + if (r > max_R) + { + max_R = r; + ret = edge; + } + } + + if (!ret->next && ret->to->data.distance_to_boundary - scaled(0.005) < ret->from->data.distance_to_boundary) + { + ret = ret->prev; + } + assert(ret); + assert(ret->next); + return ret; +} + +void SkeletalTrapezoidation::propagateBeadingsUpward(std::vector& upward_quad_mids, ptr_vector_t& node_beadings) +{ + for (auto upward_quad_mids_it = upward_quad_mids.rbegin(); upward_quad_mids_it != upward_quad_mids.rend(); ++upward_quad_mids_it) + { + edge_t* upward_edge = *upward_quad_mids_it; + if (upward_edge->to->data.bead_count >= 0) + { // Don't override local beading + continue; + } + if (! upward_edge->from->data.hasBeading()) + { // Only propagate if we have something to propagate + continue; + } + BeadingPropagation& lower_beading = *upward_edge->from->data.getBeading(); + if (upward_edge->to->data.hasBeading()) + { // Only propagate to places where there is place + continue; + } + assert((upward_edge->from->data.distance_to_boundary != upward_edge->to->data.distance_to_boundary || shorter_then(upward_edge->to->p - upward_edge->from->p, central_filter_dist)) && "zero difference R edges should always be central"); + coord_t length = (upward_edge->to->p - upward_edge->from->p).cast().norm(); + BeadingPropagation upper_beading = lower_beading; + upper_beading.dist_to_bottom_source += length; + upper_beading.is_upward_propagated_only = true; + node_beadings.emplace_back(new BeadingPropagation(upper_beading)); + upward_edge->to->data.setBeading(node_beadings.back()); + assert(upper_beading.beading.total_thickness <= upward_edge->to->data.distance_to_boundary * 2); + } +} + +void SkeletalTrapezoidation::propagateBeadingsDownward(std::vector& upward_quad_mids, ptr_vector_t& node_beadings) +{ + for (edge_t* upward_quad_mid : upward_quad_mids) + { + // Transfer beading information to lower nodes + if (!upward_quad_mid->data.isCentral()) + { + // for equidistant edge: propagate from known beading to node with unknown beading + if (upward_quad_mid->from->data.distance_to_boundary == upward_quad_mid->to->data.distance_to_boundary + && upward_quad_mid->from->data.hasBeading() + && ! upward_quad_mid->to->data.hasBeading() + ) + { + propagateBeadingsDownward(upward_quad_mid->twin, node_beadings); + } + else + { + propagateBeadingsDownward(upward_quad_mid, node_beadings); + } + } + } +} + +void SkeletalTrapezoidation::propagateBeadingsDownward(edge_t* edge_to_peak, ptr_vector_t& node_beadings) +{ + coord_t length = (edge_to_peak->to->p - edge_to_peak->from->p).cast().norm(); + BeadingPropagation& top_beading = *getOrCreateBeading(edge_to_peak->to, node_beadings); + assert(top_beading.beading.total_thickness >= edge_to_peak->to->data.distance_to_boundary * 2); + if(top_beading.beading.total_thickness < edge_to_peak->to->data.distance_to_boundary * 2) + { + BOOST_LOG_TRIVIAL(warning) << "Top bead is beyond the center of the total width."; + } + assert(!top_beading.is_upward_propagated_only); + + if(!edge_to_peak->from->data.hasBeading()) + { // Set new beading if there is no beading associated with the node yet + BeadingPropagation propagated_beading = top_beading; + propagated_beading.dist_from_top_source += length; + node_beadings.emplace_back(new BeadingPropagation(propagated_beading)); + edge_to_peak->from->data.setBeading(node_beadings.back()); + assert(propagated_beading.beading.total_thickness >= edge_to_peak->from->data.distance_to_boundary * 2); + if(propagated_beading.beading.total_thickness < edge_to_peak->from->data.distance_to_boundary * 2) + { + BOOST_LOG_TRIVIAL(warning) << "Propagated bead is beyond the center of the total width."; + } + } + else + { + BeadingPropagation& bottom_beading = *edge_to_peak->from->data.getBeading(); + coord_t total_dist = top_beading.dist_from_top_source + length + bottom_beading.dist_to_bottom_source; + double ratio_of_top = static_cast(bottom_beading.dist_to_bottom_source) / std::min(total_dist, beading_propagation_transition_dist); + ratio_of_top = std::max(0.0, ratio_of_top); + if (ratio_of_top >= 1.0) + { + bottom_beading = top_beading; + bottom_beading.dist_from_top_source += length; + } + else + { + Beading merged_beading = interpolate(top_beading.beading, ratio_of_top, bottom_beading.beading, edge_to_peak->from->data.distance_to_boundary); + bottom_beading = BeadingPropagation(merged_beading); + bottom_beading.is_upward_propagated_only = false; + assert(merged_beading.total_thickness >= edge_to_peak->from->data.distance_to_boundary * 2); + if(merged_beading.total_thickness < edge_to_peak->from->data.distance_to_boundary * 2) + { + BOOST_LOG_TRIVIAL(warning) << "Merged bead is beyond the center of the total width."; + } + } + } +} + + +SkeletalTrapezoidation::Beading SkeletalTrapezoidation::interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right, coord_t switching_radius) const +{ + assert(ratio_left_to_whole >= 0.0 && ratio_left_to_whole <= 1.0); + Beading ret = interpolate(left, ratio_left_to_whole, right); + + // TODO: don't use toolpath locations past the middle! + // TODO: stretch bead widths and locations of the higher bead count beading to fit in the left over space + coord_t next_inset_idx; + for (next_inset_idx = left.toolpath_locations.size() - 1; next_inset_idx >= 0; next_inset_idx--) + { + if (switching_radius > left.toolpath_locations[next_inset_idx]) + { + break; + } + } + if (next_inset_idx < 0) + { // There is no next inset, because there is only one + assert(left.toolpath_locations.empty() || left.toolpath_locations.front() >= switching_radius); + return ret; + } + if (next_inset_idx + 1 == coord_t(left.toolpath_locations.size())) + { // We cant adjust to fit the next edge because there is no previous one?! + return ret; + } + assert(next_inset_idx < coord_t(left.toolpath_locations.size())); + assert(left.toolpath_locations[next_inset_idx] <= switching_radius); + assert(left.toolpath_locations[next_inset_idx + 1] >= switching_radius); + if (ret.toolpath_locations[next_inset_idx] > switching_radius) + { // One inset disappeared between left and the merged one + // solve for ratio f: + // f*l + (1-f)*r = s + // f*l + r - f*r = s + // f*(l-r) + r = s + // f*(l-r) = s - r + // f = (s-r) / (l-r) + float new_ratio = static_cast(switching_radius - right.toolpath_locations[next_inset_idx]) / static_cast(left.toolpath_locations[next_inset_idx] - right.toolpath_locations[next_inset_idx]); + new_ratio = std::min(1.0, new_ratio + 0.1); + return interpolate(left, new_ratio, right); + } + return ret; +} + + +SkeletalTrapezoidation::Beading SkeletalTrapezoidation::interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right) const +{ + assert(ratio_left_to_whole >= 0.0 && ratio_left_to_whole <= 1.0); + float ratio_right_to_whole = 1.0 - ratio_left_to_whole; + + Beading ret = (left.total_thickness > right.total_thickness)? left : right; + for (size_t inset_idx = 0; inset_idx < std::min(left.bead_widths.size(), right.bead_widths.size()); inset_idx++) + { + if(left.bead_widths[inset_idx] == 0 || right.bead_widths[inset_idx] == 0) + { + ret.bead_widths[inset_idx] = 0; //0-width wall markers stay 0-width. + } + else + { + ret.bead_widths[inset_idx] = ratio_left_to_whole * left.bead_widths[inset_idx] + ratio_right_to_whole * right.bead_widths[inset_idx]; + } + ret.toolpath_locations[inset_idx] = ratio_left_to_whole * left.toolpath_locations[inset_idx] + ratio_right_to_whole * right.toolpath_locations[inset_idx]; + } + return ret; +} + +void SkeletalTrapezoidation::generateJunctions(ptr_vector_t& node_beadings, ptr_vector_t& edge_junctions) +{ + for (edge_t& edge_ : graph.edges) + { + edge_t* edge = &edge_; + if (edge->from->data.distance_to_boundary > edge->to->data.distance_to_boundary) + { // Only consider the upward half-edges + continue; + } + + coord_t start_R = edge->to->data.distance_to_boundary; // higher R + coord_t end_R = edge->from->data.distance_to_boundary; // lower R + + if ((edge->from->data.bead_count == edge->to->data.bead_count && edge->from->data.bead_count >= 0) + || end_R >= start_R) + { // No beads to generate + continue; + } + + Beading* beading = &getOrCreateBeading(edge->to, node_beadings)->beading; + edge_junctions.emplace_back(std::make_shared()); + edge_.data.setExtrusionJunctions(edge_junctions.back()); // initialization + LineJunctions& ret = *edge_junctions.back(); + + assert(beading->total_thickness >= edge->to->data.distance_to_boundary * 2); + if(beading->total_thickness < edge->to->data.distance_to_boundary * 2) + { + BOOST_LOG_TRIVIAL(warning) << "Generated junction is beyond the center of total width."; + } + + Point a = edge->to->p; + Point b = edge->from->p; + Point ab = b - a; + + const size_t num_junctions = beading->toolpath_locations.size(); + size_t junction_idx; + // Compute starting junction_idx for this segment + for (junction_idx = (std::max(size_t(1), beading->toolpath_locations.size()) - 1) / 2; junction_idx < num_junctions; junction_idx--) + { + coord_t bead_R = beading->toolpath_locations[junction_idx]; + if (bead_R <= start_R) + { // Junction coinciding with start node is used in this function call + break; + } + } + + // Robustness against odd segments which might lie just slightly outside of the range due to rounding errors + // not sure if this is really needed (TODO) + if (junction_idx + 1 < num_junctions + && beading->toolpath_locations[junction_idx + 1] <= start_R + scaled(0.005) + && beading->total_thickness < start_R + scaled(0.005) + ) + { + junction_idx++; + } + + for (; junction_idx < num_junctions; junction_idx--) //When junction_idx underflows, it'll be more than num_junctions too. + { + coord_t bead_R = beading->toolpath_locations[junction_idx]; + assert(bead_R >= 0); + if (bead_R < end_R) + { // Junction coinciding with a node is handled by the next segment + break; + } + Point junction(a + (ab.cast() * int64_t(bead_R - start_R) / int64_t(end_R - start_R)).cast()); + if (bead_R > start_R - scaled(0.005)) + { // Snap to start node if it is really close, in order to be able to see 3-way intersection later on more robustly + junction = a; + } + ret.emplace_back(junction, beading->bead_widths[junction_idx], junction_idx); + } + } +} + +std::shared_ptr SkeletalTrapezoidation::getOrCreateBeading(node_t* node, ptr_vector_t& node_beadings) +{ + if (! node->data.hasBeading()) + { + if (node->data.bead_count == -1) + { // This bug is due to too small central edges + constexpr coord_t nearby_dist = scaled(0.1); + auto nearest_beading = getNearestBeading(node, nearby_dist); + if (nearest_beading) + { + return nearest_beading; + } + + // Else make a new beading: + bool has_central_edge = false; + bool first = true; + coord_t dist = std::numeric_limits::max(); + for (edge_t* edge = node->incident_edge; edge && (first || edge != node->incident_edge); edge = edge->twin->next) + { + if (edge->data.isCentral()) + { + has_central_edge = true; + } + assert(edge->to->data.distance_to_boundary >= 0); + dist = std::min(dist, edge->to->data.distance_to_boundary + coord_t((edge->to->p - edge->from->p).cast().norm())); + first = false; + } + if (!has_central_edge) + { + BOOST_LOG_TRIVIAL(error) << "Unknown beading for non-central node!"; + } + assert(dist != std::numeric_limits::max()); + node->data.bead_count = beading_strategy.getOptimalBeadCount(dist * 2); + } + assert(node->data.bead_count != -1); + node_beadings.emplace_back(new BeadingPropagation(beading_strategy.compute(node->data.distance_to_boundary * 2, node->data.bead_count))); + node->data.setBeading(node_beadings.back()); + } + assert(node->data.hasBeading()); + return node->data.getBeading(); +} + +std::shared_ptr SkeletalTrapezoidation::getNearestBeading(node_t* node, coord_t max_dist) +{ + struct DistEdge + { + edge_t* edge_to; + coord_t dist; + DistEdge(edge_t* edge_to, coord_t dist) + : edge_to(edge_to), dist(dist) + {} + }; + + auto compare = [](const DistEdge& l, const DistEdge& r) -> bool { return l.dist > r.dist; }; + std::priority_queue, decltype(compare)> further_edges(compare); + bool first = true; + for (edge_t* outgoing = node->incident_edge; outgoing && (first || outgoing != node->incident_edge); outgoing = outgoing->twin->next) + { + further_edges.emplace(outgoing, (outgoing->to->p - outgoing->from->p).cast().norm()); + first = false; + } + + for (coord_t counter = 0; counter < SKELETAL_TRAPEZOIDATION_BEAD_SEARCH_MAX; counter++) + { // Prevent endless recursion + if (further_edges.empty()) return nullptr; + DistEdge here = further_edges.top(); + further_edges.pop(); + if (here.dist > max_dist) return nullptr; + if (here.edge_to->to->data.hasBeading()) + { + return here.edge_to->to->data.getBeading(); + } + else + { // recurse + for (edge_t* further_edge = here.edge_to->next; further_edge && further_edge != here.edge_to->twin; further_edge = further_edge->twin->next) + { + further_edges.emplace(further_edge, here.dist + (further_edge->to->p - further_edge->from->p).cast().norm()); + } + } + } + return nullptr; +} + +void SkeletalTrapezoidation::addToolpathSegment(const ExtrusionJunction& from, const ExtrusionJunction& to, bool is_odd, bool force_new_path, bool from_is_3way, bool to_is_3way) +{ + if (from == to) return; + + std::vector &generated_toolpaths = *p_generated_toolpaths; + + size_t inset_idx = from.perimeter_index; + if (inset_idx >= generated_toolpaths.size()) + { + generated_toolpaths.resize(inset_idx + 1); + } + assert((generated_toolpaths[inset_idx].empty() || !generated_toolpaths[inset_idx].back().junctions.empty()) && "empty extrusion lines should never have been generated"); + if (generated_toolpaths[inset_idx].empty() + || generated_toolpaths[inset_idx].back().is_odd != is_odd + || generated_toolpaths[inset_idx].back().junctions.back().perimeter_index != inset_idx // inset_idx should always be consistent + ) + { + force_new_path = true; + } + if (!force_new_path + && shorter_then(generated_toolpaths[inset_idx].back().junctions.back().p - from.p, scaled(0.010)) + && std::abs(generated_toolpaths[inset_idx].back().junctions.back().w - from.w) < scaled(0.010) + && ! from_is_3way // force new path at 3way intersection + ) + { + generated_toolpaths[inset_idx].back().junctions.push_back(to); + } + else if (!force_new_path + && shorter_then(generated_toolpaths[inset_idx].back().junctions.back().p - to.p, scaled(0.010)) + && std::abs(generated_toolpaths[inset_idx].back().junctions.back().w - to.w) < scaled(0.010) + && ! to_is_3way // force new path at 3way intersection + ) + { + if ( ! is_odd) + { + BOOST_LOG_TRIVIAL(error) << "Reversing even wall line causes it to be printed CCW instead of CW!"; + } + generated_toolpaths[inset_idx].back().junctions.push_back(from); + } + else + { + generated_toolpaths[inset_idx].emplace_back(inset_idx, is_odd); + generated_toolpaths[inset_idx].back().junctions.push_back(from); + generated_toolpaths[inset_idx].back().junctions.push_back(to); + } +}; + +void SkeletalTrapezoidation::connectJunctions(ptr_vector_t& edge_junctions) +{ + std::unordered_set unprocessed_quad_starts(graph.edges.size() * 5 / 2); + for (edge_t& edge : graph.edges) + { + if (!edge.prev) + { + unprocessed_quad_starts.insert(&edge); + } + } + + std::unordered_set passed_odd_edges; + + while (!unprocessed_quad_starts.empty()) + { + edge_t* poly_domain_start = *unprocessed_quad_starts.begin(); + edge_t* quad_start = poly_domain_start; + bool new_domain_start = true; + do + { + edge_t* quad_end = quad_start; + while (quad_end->next) + { + quad_end = quad_end->next; + } + + edge_t* edge_to_peak = getQuadMaxRedgeTo(quad_start); + // walk down on both sides and connect junctions + edge_t* edge_from_peak = edge_to_peak->next; assert(edge_from_peak); + + unprocessed_quad_starts.erase(quad_start); + + if (! edge_to_peak->data.hasExtrusionJunctions()) + { + edge_junctions.emplace_back(std::make_shared()); + edge_to_peak->data.setExtrusionJunctions(edge_junctions.back()); + } + // The junctions on the edge(s) from the start of the quad to the node with highest R + LineJunctions from_junctions = *edge_to_peak->data.getExtrusionJunctions(); + if (! edge_from_peak->twin->data.hasExtrusionJunctions()) + { + edge_junctions.emplace_back(std::make_shared()); + edge_from_peak->twin->data.setExtrusionJunctions(edge_junctions.back()); + } + // The junctions on the edge(s) from the end of the quad to the node with highest R + LineJunctions to_junctions = *edge_from_peak->twin->data.getExtrusionJunctions(); + if (edge_to_peak->prev) + { + LineJunctions from_prev_junctions = *edge_to_peak->prev->data.getExtrusionJunctions(); + while (!from_junctions.empty() && !from_prev_junctions.empty() && from_junctions.back().perimeter_index <= from_prev_junctions.front().perimeter_index) + { + from_junctions.pop_back(); + } + from_junctions.reserve(from_junctions.size() + from_prev_junctions.size()); + from_junctions.insert(from_junctions.end(), from_prev_junctions.begin(), from_prev_junctions.end()); + assert(!edge_to_peak->prev->prev); + if(edge_to_peak->prev->prev) + { + BOOST_LOG_TRIVIAL(warning) << "The edge we're about to connect is already connected."; + } + } + if (edge_from_peak->next) + { + LineJunctions to_next_junctions = *edge_from_peak->next->twin->data.getExtrusionJunctions(); + while (!to_junctions.empty() && !to_next_junctions.empty() && to_junctions.back().perimeter_index <= to_next_junctions.front().perimeter_index) + { + to_junctions.pop_back(); + } + to_junctions.reserve(to_junctions.size() + to_next_junctions.size()); + to_junctions.insert(to_junctions.end(), to_next_junctions.begin(), to_next_junctions.end()); + assert(!edge_from_peak->next->next); + if(edge_from_peak->next->next) + { + BOOST_LOG_TRIVIAL(warning) << "The edge we're about to connect is already connected!"; + } + } + assert(std::abs(int(from_junctions.size()) - int(to_junctions.size())) <= 1); // at transitions one end has more beads + if(std::abs(int(from_junctions.size()) - int(to_junctions.size())) > 1) + { + BOOST_LOG_TRIVIAL(warning) << "Can't create a transition when connecting two perimeters where the number of beads differs too much! " << from_junctions.size() << " vs. " << to_junctions.size(); + } + + size_t segment_count = std::min(from_junctions.size(), to_junctions.size()); + for (size_t junction_rev_idx = 0; junction_rev_idx < segment_count; junction_rev_idx++) + { + ExtrusionJunction& from = from_junctions[from_junctions.size() - 1 - junction_rev_idx]; + ExtrusionJunction& to = to_junctions[to_junctions.size() - 1 - junction_rev_idx]; + assert(from.perimeter_index == to.perimeter_index); + if(from.perimeter_index != to.perimeter_index) + { + BOOST_LOG_TRIVIAL(warning) << "Connecting two perimeters with different indices! Perimeter " << from.perimeter_index << " and " << to.perimeter_index; + } + const bool from_is_odd = + quad_start->to->data.bead_count > 0 && quad_start->to->data.bead_count % 2 == 1 // quad contains single bead segment + && quad_start->to->data.transition_ratio == 0 // We're not in a transition + && junction_rev_idx == segment_count - 1 // Is single bead segment + && shorter_then(from.p - quad_start->to->p, scaled(0.005)); + const bool to_is_odd = + quad_end->from->data.bead_count > 0 && quad_end->from->data.bead_count % 2 == 1 // quad contains single bead segment + && quad_end->from->data.transition_ratio == 0 // We're not in a transition + && junction_rev_idx == segment_count - 1 // Is single bead segment + && shorter_then(to.p - quad_end->from->p, scaled(0.005)); + const bool is_odd_segment = from_is_odd && to_is_odd; + if (is_odd_segment + && passed_odd_edges.count(quad_start->next->twin) > 0) // Only generate toolpath for odd segments once + { + continue; // Prevent duplication of single bead segments + } + bool from_is_3way = from_is_odd && quad_start->to->isMultiIntersection(); + bool to_is_3way = to_is_odd && quad_end->from->isMultiIntersection(); + passed_odd_edges.emplace(quad_start->next); + + addToolpathSegment(from, to, is_odd_segment, new_domain_start, from_is_3way, to_is_3way); + } + new_domain_start = false; + } + while(quad_start = quad_start->getNextUnconnected(), quad_start != poly_domain_start); + } +} + +void SkeletalTrapezoidation::generateLocalMaximaSingleBeads() +{ + std::vector &generated_toolpaths = *p_generated_toolpaths; + + for (auto& node : graph.nodes) + { + if (! node.data.hasBeading()) + { + continue; + } + Beading& beading = node.data.getBeading()->beading; + if (beading.bead_widths.size() % 2 == 1 && node.isLocalMaximum(true) && !node.isCentral()) + { + const size_t inset_index = beading.bead_widths.size() / 2; + constexpr bool is_odd = true; + if (inset_index >= generated_toolpaths.size()) + { + generated_toolpaths.resize(inset_index + 1); + } + generated_toolpaths[inset_index].emplace_back(inset_index, is_odd); + ExtrusionLine& line = generated_toolpaths[inset_index].back(); + const coord_t width = beading.bead_widths[inset_index]; + // total area to be extruded is pi*(w/2)^2 = pi*w*w/4 + // Width a constant extrusion width w, that would be a length of pi*w/4 + // If we make a small circle to fill up the hole, then that circle would have a circumference of 2*pi*r + // So our circle needs to be such that r=w/8 + const coord_t r = width / 8; + constexpr coord_t n_segments = 6; + for (coord_t segment = 0; segment < n_segments; segment++) { + float a = 2.0 * M_PI / n_segments * segment; + line.junctions.emplace_back(node.p + Point(r * cos(a), r * sin(a)), width, inset_index); + } + } + } +} + +// +// ^^^^^^^^^^^^^^^^^^^^^ +// TOOLPATH GENERATION +// ===================== +// + +} // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp new file mode 100644 index 0000000000..51b24bbcdf --- /dev/null +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp @@ -0,0 +1,595 @@ +//Copyright (c) 2020 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef SKELETAL_TRAPEZOIDATION_H +#define SKELETAL_TRAPEZOIDATION_H + +#include + +#include // smart pointers +#include +#include // pair + +#include "utils/HalfEdgeGraph.hpp" +#include "utils/PolygonsSegmentIndex.hpp" +#include "utils/ExtrusionJunction.hpp" +#include "utils/ExtrusionLine.hpp" +#include "SkeletalTrapezoidationEdge.hpp" +#include "SkeletalTrapezoidationJoint.hpp" +#include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp" +#include "SkeletalTrapezoidationGraph.hpp" + +namespace Slic3r::Arachne +{ + +/*! + * Main class of the dynamic beading strategies. + * + * The input polygon region is decomposed into trapezoids and represented as a half-edge data-structure. + * + * We determine which edges are 'central' accordinding to the transitioning_angle of the beading strategy, + * and determine the bead count for these central regions and apply them outward when generating toolpaths. [oversimplified] + * + * The method can be visually explained as generating the 3D union of cones surface on the outline polygons, + * and changing the heights along central regions of that surface so that they are flat. + * For more info, please consult the paper "A framework for adaptive width control of dense contour-parallel toolpaths in fused +deposition modeling" by Kuipers et al. + * This visual explanation aid explains the use of "upward", "lower" etc, + * i.e. the radial distance and/or the bead count are used as heights of this visualization, there is no coordinate called 'Z'. + * + * TODO: split this class into two: + * 1. Class for generating the decomposition and aux functions for performing updates + * 2. Class for editing the structure for our purposes. + */ +class SkeletalTrapezoidation +{ + using pos_t = double; + using vd_t = boost::polygon::voronoi_diagram; + using graph_t = SkeletalTrapezoidationGraph; + using edge_t = STHalfEdge; + using node_t = STHalfEdgeNode; + using Beading = BeadingStrategy::Beading; + using BeadingPropagation = SkeletalTrapezoidationJoint::BeadingPropagation; + using TransitionMiddle = SkeletalTrapezoidationEdge::TransitionMiddle; + using TransitionEnd = SkeletalTrapezoidationEdge::TransitionEnd; + + template + using ptr_vector_t = std::vector>; + + double transitioning_angle; //!< How pointy a region should be before we apply the method. Equals 180* - limit_bisector_angle + coord_t discretization_step_size; //!< approximate size of segments when parabolic VD edges get discretized (and vertex-vertex edges) + coord_t transition_filter_dist; //!< Filter transition mids (i.e. anchors) closer together than this + coord_t allowed_filter_deviation; //!< The allowed line width deviation induced by filtering + coord_t beading_propagation_transition_dist; //!< When there are different beadings propagated from below and from above, use this transitioning distance + static constexpr coord_t central_filter_dist = scaled(0.02); //!< Filter areas marked as 'central' smaller than this + static constexpr coord_t snap_dist = scaled(0.02); //!< Generic arithmatic inaccuracy. Only used to determine whether a transition really needs to insert an extra edge. + + /*! + * The strategy to use to fill a certain shape with lines. + * + * Various BeadingStrategies are available that differ in which lines get to + * print at their optimal width, where the play is being compensated, and + * how the joints are handled where we transition to different numbers of + * lines. + */ + const BeadingStrategy& beading_strategy; + +public: + using Segment = PolygonsSegmentIndex; + + /*! + * Construct a new trapezoidation problem to solve. + * \param polys The shapes to fill with walls. + * \param beading_strategy The strategy to use to fill these shapes. + * \param transitioning_angle Where we transition to a different number of + * walls, how steep should this transition be? A lower angle means that the + * transition will be longer. + * \param discretization_step_size Since g-code can't represent smooth + * transitions in line width, the line width must change with discretized + * steps. This indicates how long the line segments between those steps will + * be. + * \param transition_filter_dist The minimum length of transitions. + * Transitions shorter than this will be considered for dissolution. + * \param beading_propagation_transition_dist When there are different + * beadings propagated from below and from above, use this transitioning + * distance. + */ + SkeletalTrapezoidation(const Polygons& polys, + const BeadingStrategy& beading_strategy, + double transitioning_angle + , coord_t discretization_step_size + , coord_t transition_filter_dist + , coord_t allowed_filter_deviation + , coord_t beading_propagation_transition_dist); + + /*! + * A skeletal graph through the polygons that we need to fill with beads. + * + * The skeletal graph represents the medial axes through each part of the + * polygons, and the lines from these medial axes towards each vertex of the + * polygons. The graph can be used to see what the width is of a polygon in + * each place and where the width transitions. + */ + graph_t graph; + + /*! + * Generate the paths that the printer must extrude, to print the outlines + * in the input polygons. + * \param filter_outermost_central_edges Some edges are "central" but still + * touch the outside of the polygon. If enabled, don't treat these as + * "central" but as if it's a obtuse corner. As a result, sharp corners will + * no longer end in a single line but will just loop. + */ + void generateToolpaths(std::vector &generated_toolpaths, bool filter_outermost_central_edges = false); + +protected: + /*! + * Auxiliary for referencing one transition along an edge which may contain multiple transitions + */ + struct TransitionMidRef + { + edge_t* edge; + std::list::iterator transition_it; + TransitionMidRef(edge_t* edge, std::list::iterator transition_it) + : edge(edge) + , transition_it(transition_it) + {} + }; + + /*! + * Compute the skeletal trapezoidation decomposition of the input shape. + * + * Compute the Voronoi Diagram (VD) and transfer all inside edges into our half-edge (HE) datastructure. + * + * The algorithm is currently a bit overcomplicated, because the discretization of parabolic edges is performed at the same time as all edges are being transfered, + * which means that there is no one-to-one mapping from VD edges to HE edges. + * Instead we map from a VD edge to the last HE edge. + * This could be cimplified by recording the edges which should be discretized and discretizing the mafterwards. + * + * Another complication arises because the VD uses floating logic, which can result in zero-length segments after rounding to integers. + * We therefore collapse edges and their whole cells afterwards. + */ + void constructFromPolygons(const Polygons& polys); + + /*! + * mapping each voronoi VD edge to the corresponding halfedge HE edge + * In case the result segment is discretized, we map the VD edge to the *last* HE edge + */ + std::unordered_map vd_edge_to_he_edge; + std::unordered_map vd_node_to_he_node; + node_t& makeNode(vd_t::vertex_type& vd_node, Point p); //!< Get the node which the VD node maps to, or create a new mapping if there wasn't any yet. + + /*! + * (Eventual) returned 'polylines per index' result (from generateToolpaths): + */ + std::vector *p_generated_toolpaths; + + /*! + * Transfer an edge from the VD to the HE and perform discretization of parabolic edges (and vertex-vertex edges) + * \p prev_edge serves as input and output. May be null as input. + */ + void transferEdge(Point from, Point to, vd_t::edge_type& vd_edge, edge_t*& prev_edge, Point& start_source_point, Point& end_source_point, const std::vector& segments); + + /*! + * Discretize a Voronoi edge that represents the medial axis of a vertex- + * line region or vertex-vertex region into small segments that can be + * considered to have a straight medial axis and a linear line width + * transition. + * + * The medial axis between a point and a line is a parabola. The rest of the + * algorithm doesn't want to have to deal with parabola, so this discretises + * the parabola into straight line segments. This is necessary if there is a + * sharp inner corner (acts as a point) that comes close to a straight edge. + * + * The medial axis between a point and a point is a straight line segment. + * However the distance from the medial axis to either of those points draws + * a parabola as you go along the medial axis. That means that the resulting + * line width along the medial axis would not be linearly increasing or + * linearly decreasing, but needs to take the shape of a parabola. Instead, + * we'll break this edge up into tiny line segments that can approximate the + * parabola with tiny linear increases or decreases in line width. + * \param segment The variable-width Voronoi edge to discretize. + * \param points All vertices of the original Polygons to fill with beads. + * \param segments All line segments of the original Polygons to fill with + * beads. + * \return A number of coordinates along the edge where the edge is broken + * up into discrete pieces. + */ + std::vector discretize(const vd_t::edge_type& segment, const std::vector& segments); + + /*! + * Compute the range of line segments that surround a cell of the skeletal + * graph that belongs to a point on the medial axis. + * + * This should only be used on cells that belong to a corner in the skeletal + * graph, e.g. triangular cells, not trapezoid cells. + * + * The resulting line segments is just the first and the last segment. They + * are linked to the neighboring segments, so you can iterate over the + * segments until you reach the last segment. + * \param cell The cell to compute the range of line segments for. + * \param[out] start_source_point The start point of the source segment of + * this cell. + * \param[out] end_source_point The end point of the source segment of this + * cell. + * \param[out] starting_vd_edge The edge of the Voronoi diagram where the + * loop around the cell starts. + * \param[out] ending_vd_edge The edge of the Voronoi diagram where the loop + * around the cell ends. + * \param points All vertices of the input Polygons. + * \param segments All edges of the input Polygons. + * /return Whether the cell is inside of the polygon. If it's outside of the + * polygon we should skip processing it altogether. + */ + bool computePointCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector& segments); + + /*! + * Compute the range of line segments that surround a cell of the skeletal + * graph that belongs to a line segment of the medial axis. + * + * This should only be used on cells that belong to a central line segment + * of the skeletal graph, e.g. trapezoid cells, not triangular cells. + * + * The resulting line segments is just the first and the last segment. They + * are linked to the neighboring segments, so you can iterate over the + * segments until you reach the last segment. + * \param cell The cell to compute the range of line segments for. + * \param[out] start_source_point The start point of the source segment of + * this cell. + * \param[out] end_source_point The end point of the source segment of this + * cell. + * \param[out] starting_vd_edge The edge of the Voronoi diagram where the + * loop around the cell starts. + * \param[out] ending_vd_edge The edge of the Voronoi diagram where the loop + * around the cell ends. + * \param points All vertices of the input Polygons. + * \param segments All edges of the input Polygons. + * /return Whether the cell is inside of the polygon. If it's outside of the + * polygon we should skip processing it altogether. + */ + void computeSegmentCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector& segments); + + /*! + * For VD cells associated with an input polygon vertex, we need to separate the node at the end and start of the cell into two + * That way we can reach both the quad_start and the quad_end from the [incident_edge] of the two new nodes + * Otherwise if node.incident_edge = quad_start you couldnt reach quad_end.twin by normal iteration (i.e. it = it.twin.next) + */ + void separatePointyQuadEndNodes(); + + + // ^ init | v transitioning + + void updateIsCentral(); // Update the "is_central" flag for each edge based on the transitioning_angle + + /*! + * Filter out small central areas. + * + * Only used to get rid of small edges which get marked as central because + * of rounding errors because the region is so small. + */ + void filterCentral(coord_t max_length); + + /*! + * Filter central areas connected to starting_edge recursively. + * \return Whether we should unmark this section marked as central, on the + * way back out of the recursion. + */ + bool filterCentral(edge_t* starting_edge, coord_t traveled_dist, coord_t max_length); + + /*! + * Unmark the outermost edges directly connected to the outline, as not + * being central. + * + * Only used to emulate some related literature. + * + * The paper shows that this function is bad for the stability of the framework. + */ + void filterOuterCentral(); + + /*! + * Set bead count in central regions based on the optimal_bead_count of the + * beading strategy. + */ + void updateBeadCount(); + + /*! + * Add central regions and set bead counts where there is an end of the + * central area and when traveling upward we get to another region with the + * same bead count. + */ + void filterNoncentralRegions(); + + /*! + * Add central regions and set bead counts for a particular edge and all of + * its adjacent edges. + * + * Recursive subroutine for \ref filterNoncentralRegions(). + * \return Whether to set the bead count on the way back + */ + bool filterNoncentralRegions(edge_t* to_edge, coord_t bead_count, coord_t traveled_dist, coord_t max_dist); + + /*! + * Generate middle points of all transitions on edges. + * + * The transition middle points are saved in the graph itself. They are also + * returned via the output parameter. + * \param[out] edge_transitions A list of transitions that were generated. + */ + void generateTransitionMids(ptr_vector_t>& edge_transitions); + + /*! + * Removes some transition middle points. + * + * Transitions can be removed if there are multiple intersecting transitions + * that are too close together. If transitions have opposite effects, both + * are removed. + */ + void filterTransitionMids(); + + /*! + * Merge transitions that are too close together. + * \param edge_to_start Edge pointing to the node from which to start + * traveling in all directions except along \p edge_to_start . + * \param origin_transition The transition for which we are checking nearby + * transitions. + * \param traveled_dist The distance traveled before we came to + * \p edge_to_start.to . + * \param going_up Whether we are traveling in the upward direction as seen + * from the \p origin_transition. If this doesn't align with the direction + * according to the R diff on a consecutive edge we know there was a local + * optimum. + * \return Whether the origin transition should be dissolved. + */ + std::list dissolveNearbyTransitions(edge_t* edge_to_start, TransitionMiddle& origin_transition, coord_t traveled_dist, coord_t max_dist, bool going_up); + + /*! + * Spread a certain bead count over a region in the graph. + * \param edge_to_start One edge of the region to spread the bead count in. + * \param from_bead_count All edges with this bead count will be changed. + * \param to_bead_count The new bead count for those edges. + */ + void dissolveBeadCountRegion(edge_t* edge_to_start, coord_t from_bead_count, coord_t to_bead_count); + + /*! + * Change the bead count if the given edge is at the end of a central + * region. + * + * This is necessary to provide a transitioning bead count to the edges of a + * central region to transition more smoothly from a high bead count in the + * central region to a lower bead count at the edge. + * \param edge_to_start One edge from a zone that needs to be filtered. + * \param traveled_dist The distance along the edges we've traveled so far. + * \param max_distance Don't filter beyond this range. + * \param replacing_bead_count The new bead count for this region. + * \return ``true`` if the bead count of this edge was changed. + */ + bool filterEndOfCentralTransition(edge_t* edge_to_start, coord_t traveled_dist, coord_t max_dist, coord_t replacing_bead_count); + + /*! + * Generate the endpoints of all transitions for all edges in the graph. + * \param[out] edge_transition_ends The resulting transition endpoints. + */ + void generateAllTransitionEnds(ptr_vector_t>& edge_transition_ends); + + /*! + * Also set the rest values at nodes in between the transition ends + */ + void applyTransitions(ptr_vector_t>& edge_transition_ends); + + /*! + * Create extra edges along all edges, where it needs to transition from one + * bead count to another. + * + * For example, if an edge of the graph goes from a bead count of 6 to a + * bead count of 1, it needs to generate 5 places where the beads around + * this line transition to a lower bead count. These are the "ribs". They + * reach from the edge to the border of the polygon. Where the beads hit + * those ribs the beads know to make a transition. + */ + void generateTransitioningRibs(); + + /*! + * Generate the endpoints of a specific transition midpoint. + * \param edge The edge to create transitions on. + * \param mid_R The radius of the transition middle point. + * \param transition_lower_bead_count The bead count at the lower end of the + * transition. + * \param[out] edge_transition_ends A list of endpoints to add the new + * endpoints to. + */ + void generateTransitionEnds(edge_t& edge, coord_t mid_R, coord_t transition_lower_bead_count, ptr_vector_t>& edge_transition_ends); + + /*! + * Compute a single endpoint of a transition. + * \param edge The edge to generate the endpoint for. + * \param start_pos The position where the transition starts. + * \param end_pos The position where the transition ends on the other side. + * \param transition_half_length The distance to the transition middle + * point. + * \param start_rest The gap between the start of the transition and the + * starting endpoint, as ratio of the inner bead width at the high end of + * the transition. + * \param end_rest The gap between the end of the transition and the ending + * endpoint, as ratio of the inner bead width at the high end of the + * transition. + * \param transition_lower_bead_count The bead count at the lower end of the + * transition. + * \param[out] edge_transition_ends The list to put the resulting endpoints + * in. + * \return Whether the given edge is going downward (i.e. towards a thinner + * region of the polygon). + */ + bool generateTransitionEnd(edge_t& edge, coord_t start_pos, coord_t end_pos, coord_t transition_half_length, double start_rest, double end_rest, coord_t transition_lower_bead_count, ptr_vector_t>& edge_transition_ends); + + /*! + * Determines whether an edge is going downwards or upwards in the graph. + * + * An edge is said to go "downwards" if it's going towards a narrower part + * of the polygon. The notion of "downwards" comes from the conical + * representation of the graph, where the polygon is filled with a cone of + * maximum radius. + * + * This function works by recursively checking adjacent edges until the edge + * is reached. + * \param outgoing The edge to check. + * \param traveled_dist The distance traversed so far. + * \param transition_half_length The radius of the transition width. + * \param lower_bead_count The bead count at the lower end of the edge. + * \return ``true`` if this edge is going down, or ``false`` if it's going + * up. + */ + bool isGoingDown(edge_t* outgoing, coord_t traveled_dist, coord_t transition_half_length, coord_t lower_bead_count) const; + + /*! + * Determines whether this edge marks the end of the central region. + * \param edge The edge to check. + * \return ``true`` if this edge goes from a central region to a non-central + * region, or ``false`` in every other case (central to central, non-central + * to non-central, non-central to central, or end-of-the-line). + */ + bool isEndOfCentral(const edge_t& edge) const; + + /*! + * Create extra ribs in the graph where the graph contains a parabolic arc + * or a straight between two inner corners. + * + * There might be transitions there as the beads go through a narrow + * bottleneck in the polygon. + */ + void generateExtraRibs(); + + // ^ transitioning ^ + + // v toolpath generation v + + /*! + * \param[out] segments the generated segments + */ + void generateSegments(); + + /*! + * From a quad (a group of linked edges in one cell of the Voronoi), find + * the edge pointing to the node that is furthest away from the border of the polygon. + * \param quad_start_edge The first edge of the quad. + * \return The edge of the quad that is furthest away from the border. + */ + edge_t* getQuadMaxRedgeTo(edge_t* quad_start_edge); + + /*! + * Propagate beading information from nodes that are closer to the edge + * (low radius R) to nodes that are farther from the edge (high R). + * + * only propagate from nodes with beading info upward to nodes without beading info + * + * Edges are sorted by their radius, so that we can do a depth-first walk + * without employing a recursive algorithm. + * + * In upward propagated beadings we store the distance traveled, so that we can merge these beadings with the downward propagated beadings in \ref propagateBeadingsDownward(.) + * + * \param upward_quad_mids all upward halfedges of the inner skeletal edges (not directly connected to the outline) sorted on their highest [distance_to_boundary]. Higher dist first. + */ + void propagateBeadingsUpward(std::vector& upward_quad_mids, ptr_vector_t& node_beadings); + + /*! + * propagate beading info from higher R nodes to lower R nodes + * + * merge with upward propagated beadings if they are encountered + * + * don't transfer to nodes which lie on the outline polygon + * + * edges are sorted so that we can do a depth-first walk without employing a recursive algorithm + * + * \param upward_quad_mids all upward halfedges of the inner skeletal edges (not directly connected to the outline) sorted on their highest [distance_to_boundary]. Higher dist first. + */ + void propagateBeadingsDownward(std::vector& upward_quad_mids, ptr_vector_t& node_beadings); + + /*! + * Subroutine of \ref propagateBeadingsDownward(std::vector&, ptr_vector_t&) + */ + void propagateBeadingsDownward(edge_t* edge_to_peak, ptr_vector_t& node_beadings); + + /*! + * Find a beading in between two other beadings. + * + * This creates a new beading. With this we can find the coordinates of the + * endpoints of the actual line segments to draw. + * + * The parameters \p left and \p right are not actually always left or right + * but just arbitrary directions to visually indicate the difference. + * \param left One of the beadings to interpolate between. + * \param ratio_left_to_whole The position within the two beadings to sample + * an interpolation. Should be a ratio between 0 and 1. + * \param right One of the beadings to interpolate between. + * \param switching_radius The bead radius at which we switch from the left + * beading to the merged beading, if the beadings have a different number of + * beads. + * \return The beading at the interpolated location. + */ + Beading interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right, coord_t switching_radius) const; + + /*! + * Subroutine of \ref interpolate(const Beading&, Ratio, const Beading&, coord_t) + * + * This creates a new Beading between two beadings, assuming that both have + * the same number of beads. + * \param left One of the beadings to interpolate between. + * \param ratio_left_to_whole The position within the two beadings to sample + * an interpolation. Should be a ratio between 0 and 1. + * \param right One of the beadings to interpolate between. + * \return The beading at the interpolated location. + */ + Beading interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right) const; + + /*! + * Get the beading at a certain node of the skeletal graph, or create one if + * it doesn't have one yet. + * + * This is a lazy get. + * \param node The node to get the beading from. + * \param node_beadings A list of all beadings for nodes. + * \return The beading of that node. + */ + std::shared_ptr getOrCreateBeading(node_t* node, ptr_vector_t& node_beadings); + + /*! + * In case we cannot find the beading of a node, get a beading from the + * nearest node. + * \param node The node to attempt to get a beading from. The actual node + * that the returned beading is from may be a different, nearby node. + * \param max_dist The maximum distance to search for. + * \return A beading for the node, or ``nullptr`` if there is no node nearby + * with a beading. + */ + std::shared_ptr getNearestBeading(node_t* node, coord_t max_dist); + + /*! + * generate junctions for each bone + * \param edge_to_junctions junctions ordered high R to low R + */ + void generateJunctions(ptr_vector_t& node_beadings, ptr_vector_t& edge_junctions); + + /*! + * Add a new toolpath segment, defined between two extrusion-juntions. + * + * \param from The junction from which to add a segment. + * \param to The junction to which to add a segment. + * \param is_odd Whether this segment is an odd gap filler along the middle of the skeleton. + * \param force_new_path Whether to prevent adding this path to an existing path which ends in \p from + * \param from_is_3way Whether the \p from junction is a splitting junction where two normal wall lines and a gap filler line come together. + * \param to_is_3way Whether the \p to junction is a splitting junction where two normal wall lines and a gap filler line come together. + */ + void addToolpathSegment(const ExtrusionJunction& from, const ExtrusionJunction& to, bool is_odd, bool force_new_path, bool from_is_3way, bool to_is_3way); + + /*! + * connect junctions in each quad + */ + void connectJunctions(ptr_vector_t& edge_junctions); + + /*! + * Genrate small segments for local maxima where the beading would only result in a single bead + */ + void generateLocalMaximaSingleBeads(); +}; + +} // namespace Slic3r::Arachne +#endif // VORONOI_QUADRILATERALIZATION_H diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidationEdge.hpp b/src/libslic3r/Arachne/SkeletalTrapezoidationEdge.hpp new file mode 100644 index 0000000000..e0d3fe81d2 --- /dev/null +++ b/src/libslic3r/Arachne/SkeletalTrapezoidationEdge.hpp @@ -0,0 +1,122 @@ +//Copyright (c) 2021 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef SKELETAL_TRAPEZOIDATION_EDGE_H +#define SKELETAL_TRAPEZOIDATION_EDGE_H + +#include // smart pointers +#include +#include + +#include "utils/ExtrusionJunction.hpp" + +namespace Slic3r::Arachne +{ + +class SkeletalTrapezoidationEdge +{ +private: + enum class Central { UNKNOWN = -1, NO, YES }; + +public: + /*! + * Representing the location along an edge where the anchor position of a transition should be placed. + */ + struct TransitionMiddle + { + coord_t pos; // Position along edge as measure from edge.from.p + int lower_bead_count; + coord_t feature_radius; // The feature radius at which this transition is placed + TransitionMiddle(coord_t pos, int lower_bead_count, coord_t feature_radius) + : pos(pos), lower_bead_count(lower_bead_count) + , feature_radius(feature_radius) + {} + }; + + /*! + * Represents the location along an edge where the lower or upper end of a transition should be placed. + */ + struct TransitionEnd + { + coord_t pos; // Position along edge as measure from edge.from.p, where the edge is always the half edge oriented from lower to higher R + int lower_bead_count; + bool is_lower_end; // Whether this is the ed of the transition with lower bead count + TransitionEnd(coord_t pos, int lower_bead_count, bool is_lower_end) + : pos(pos), lower_bead_count(lower_bead_count), is_lower_end(is_lower_end) + {} + }; + + enum class EdgeType + { + NORMAL = 0, // from voronoi diagram + EXTRA_VD = 1, // introduced to voronoi diagram in order to make the gMAT + TRANSITION_END = 2 // introduced to voronoi diagram in order to make the gMAT + }; + EdgeType type; + + SkeletalTrapezoidationEdge() : SkeletalTrapezoidationEdge(EdgeType::NORMAL) {} + SkeletalTrapezoidationEdge(const EdgeType &type) : type(type), is_central(Central::UNKNOWN) {} + + bool isCentral() const + { + assert(is_central != Central::UNKNOWN); + return is_central == Central::YES; + } + void setIsCentral(bool b) + { + is_central = b ? Central::YES : Central::NO; + } + bool centralIsSet() const + { + return is_central != Central::UNKNOWN; + } + + bool hasTransitions(bool ignore_empty = false) const + { + return transitions.use_count() > 0 && (ignore_empty || ! transitions.lock()->empty()); + } + void setTransitions(std::shared_ptr> storage) + { + transitions = storage; + } + std::shared_ptr> getTransitions() + { + return transitions.lock(); + } + + bool hasTransitionEnds(bool ignore_empty = false) const + { + return transition_ends.use_count() > 0 && (ignore_empty || ! transition_ends.lock()->empty()); + } + void setTransitionEnds(std::shared_ptr> storage) + { + transition_ends = storage; + } + std::shared_ptr> getTransitionEnds() + { + return transition_ends.lock(); + } + + bool hasExtrusionJunctions(bool ignore_empty = false) const + { + return extrusion_junctions.use_count() > 0 && (ignore_empty || ! extrusion_junctions.lock()->empty()); + } + void setExtrusionJunctions(std::shared_ptr storage) + { + extrusion_junctions = storage; + } + std::shared_ptr getExtrusionJunctions() + { + return extrusion_junctions.lock(); + } + +private: + Central is_central; //! whether the edge is significant; whether the source segments have a sharp angle; -1 is unknown + + std::weak_ptr> transitions; + std::weak_ptr> transition_ends; + std::weak_ptr extrusion_junctions; +}; + +} // namespace Slic3r::Arachne +#endif // SKELETAL_TRAPEZOIDATION_EDGE_H diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.cpp b/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.cpp new file mode 100644 index 0000000000..4ef96eda1a --- /dev/null +++ b/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.cpp @@ -0,0 +1,467 @@ +//Copyright (c) 2020 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "SkeletalTrapezoidationGraph.hpp" +#include + +#include + +#include "utils/linearAlg2D.hpp" +#include "../Line.hpp" + +namespace Slic3r::Arachne +{ + +STHalfEdge::STHalfEdge(SkeletalTrapezoidationEdge data) : HalfEdge(data) {} + +bool STHalfEdge::canGoUp(bool strict) const +{ + if (to->data.distance_to_boundary > from->data.distance_to_boundary) + { + return true; + } + if (to->data.distance_to_boundary < from->data.distance_to_boundary || strict) + { + return false; + } + + // Edge is between equidistqant verts; recurse! + for (edge_t* outgoing = next; outgoing != twin; outgoing = outgoing->twin->next) + { + if (outgoing->canGoUp()) + { + return true; + } + assert(outgoing->twin); if (!outgoing->twin) return false; + assert(outgoing->twin->next); if (!outgoing->twin->next) return true; // This point is on the boundary?! Should never occur + } + return false; +} + +bool STHalfEdge::isUpward() const +{ + if (to->data.distance_to_boundary > from->data.distance_to_boundary) + { + return true; + } + if (to->data.distance_to_boundary < from->data.distance_to_boundary) + { + return false; + } + + // Equidistant edge case: + std::optional forward_up_dist = this->distToGoUp(); + std::optional backward_up_dist = twin->distToGoUp(); + if (forward_up_dist && backward_up_dist) + { + return forward_up_dist < backward_up_dist; + } + + if (forward_up_dist) + { + return true; + } + + if (backward_up_dist) + { + return false; + } + return to->p < from->p; // Arbitrary ordering, which returns the opposite for the twin edge +} + +std::optional STHalfEdge::distToGoUp() const +{ + if (to->data.distance_to_boundary > from->data.distance_to_boundary) + { + return 0; + } + if (to->data.distance_to_boundary < from->data.distance_to_boundary) + { + return std::optional(); + } + + // Edge is between equidistqant verts; recurse! + std::optional ret; + for (edge_t* outgoing = next; outgoing != twin; outgoing = outgoing->twin->next) + { + std::optional dist_to_up = outgoing->distToGoUp(); + if (dist_to_up) + { + if (ret) + { + ret = std::min(*ret, *dist_to_up); + } + else + { + ret = dist_to_up; + } + } + assert(outgoing->twin); if (!outgoing->twin) return std::optional(); + assert(outgoing->twin->next); if (!outgoing->twin->next) return 0; // This point is on the boundary?! Should never occur + } + if (ret) + { + ret = *ret + (to->p - from->p).cast().norm(); + } + return ret; +} + +STHalfEdge* STHalfEdge::getNextUnconnected() +{ + edge_t* result = static_cast(this); + while (result->next) + { + result = result->next; + if (result == this) + { + return nullptr; + } + } + return result->twin; +} + +STHalfEdgeNode::STHalfEdgeNode(SkeletalTrapezoidationJoint data, Point p) : HalfEdgeNode(data, p) {} + +bool STHalfEdgeNode::isMultiIntersection() +{ + int odd_path_count = 0; + edge_t* outgoing = this->incident_edge; + do + { + if ( ! outgoing) + { // This is a node on the outside + return false; + } + if (outgoing->data.isCentral()) + { + odd_path_count++; + } + } while (outgoing = outgoing->twin->next, outgoing != this->incident_edge); + return odd_path_count > 2; +} + +bool STHalfEdgeNode::isCentral() const +{ + edge_t* edge = incident_edge; + do + { + if (edge->data.isCentral()) + { + return true; + } + assert(edge->twin); if (!edge->twin) return false; + } while (edge = edge->twin->next, edge != incident_edge); + return false; +} + +bool STHalfEdgeNode::isLocalMaximum(bool strict) const +{ + if (data.distance_to_boundary == 0) + { + return false; + } + + edge_t* edge = incident_edge; + do + { + if (edge->canGoUp(strict)) + { + return false; + } + assert(edge->twin); if (!edge->twin) return false; + + if (!edge->twin->next) + { // This point is on the boundary + return false; + } + } while (edge = edge->twin->next, edge != incident_edge); + return true; +} + +void SkeletalTrapezoidationGraph::collapseSmallEdges(coord_t snap_dist) +{ + std::unordered_map::iterator> edge_locator; + std::unordered_map::iterator> node_locator; + + for (auto edge_it = edges.begin(); edge_it != edges.end(); ++edge_it) + { + edge_locator.emplace(&*edge_it, edge_it); + } + + for (auto node_it = nodes.begin(); node_it != nodes.end(); ++node_it) + { + node_locator.emplace(&*node_it, node_it); + } + + auto safelyRemoveEdge = [this, &edge_locator](edge_t* to_be_removed, std::list::iterator& current_edge_it, bool& edge_it_is_updated) + { + if (current_edge_it != edges.end() + && to_be_removed == &*current_edge_it) + { + current_edge_it = edges.erase(current_edge_it); + edge_it_is_updated = true; + } + else + { + edges.erase(edge_locator[to_be_removed]); + } + }; + + auto should_collapse = [snap_dist](node_t* a, node_t* b) + { + return shorter_then(a->p - b->p, snap_dist); + }; + + for (auto edge_it = edges.begin(); edge_it != edges.end();) + { + if (edge_it->prev) + { + edge_it++; + continue; + } + + edge_t* quad_start = &*edge_it; + edge_t* quad_end = quad_start; while (quad_end->next) quad_end = quad_end->next; + edge_t* quad_mid = (quad_start->next == quad_end)? nullptr : quad_start->next; + + bool edge_it_is_updated = false; + if (quad_mid && should_collapse(quad_mid->from, quad_mid->to)) + { + assert(quad_mid->twin); + if(!quad_mid->twin) + { + BOOST_LOG_TRIVIAL(warning) << "Encountered quad edge without a twin."; + continue; //Prevent accessing unallocated memory. + } + int count = 0; + for (edge_t* edge_from_3 = quad_end; edge_from_3 && edge_from_3 != quad_mid->twin; edge_from_3 = edge_from_3->twin->next) + { + edge_from_3->from = quad_mid->from; + edge_from_3->twin->to = quad_mid->from; + if (count > 50) + { + std::cerr << edge_from_3->from->p << " - " << edge_from_3->to->p << '\n'; + } + if (++count > 1000) + { + break; + } + } + + // o-o > collapse top + // | | + // | | + // | | + // o o + if (quad_mid->from->incident_edge == quad_mid) + { + if (quad_mid->twin->next) + { + quad_mid->from->incident_edge = quad_mid->twin->next; + } + else + { + quad_mid->from->incident_edge = quad_mid->prev->twin; + } + } + + nodes.erase(node_locator[quad_mid->to]); + + quad_mid->prev->next = quad_mid->next; + quad_mid->next->prev = quad_mid->prev; + quad_mid->twin->next->prev = quad_mid->twin->prev; + quad_mid->twin->prev->next = quad_mid->twin->next; + + safelyRemoveEdge(quad_mid->twin, edge_it, edge_it_is_updated); + safelyRemoveEdge(quad_mid, edge_it, edge_it_is_updated); + } + + // o-o + // | | > collapse sides + // o o + if ( should_collapse(quad_start->from, quad_end->to) && should_collapse(quad_start->to, quad_end->from)) + { // Collapse start and end edges and remove whole cell + + quad_start->twin->to = quad_end->to; + quad_end->to->incident_edge = quad_end->twin; + if (quad_end->from->incident_edge == quad_end) + { + if (quad_end->twin->next) + { + quad_end->from->incident_edge = quad_end->twin->next; + } + else + { + quad_end->from->incident_edge = quad_end->prev->twin; + } + } + nodes.erase(node_locator[quad_start->from]); + + quad_start->twin->twin = quad_end->twin; + quad_end->twin->twin = quad_start->twin; + safelyRemoveEdge(quad_start, edge_it, edge_it_is_updated); + safelyRemoveEdge(quad_end, edge_it, edge_it_is_updated); + } + // If only one side had collapsable length then the cell on the other side of that edge has to collapse + // if we would collapse that one edge then that would change the quad_start and/or quad_end of neighboring cells + // this is to do with the constraint that !prev == !twin.next + + if (!edge_it_is_updated) + { + edge_it++; + } + } +} + +void SkeletalTrapezoidationGraph::makeRib(edge_t*& prev_edge, Point start_source_point, Point end_source_point, bool is_next_to_start_or_end) +{ + Point p; + Line(start_source_point, end_source_point).distance_to_infinite_squared(prev_edge->to->p, &p); + coord_t dist = (prev_edge->to->p - p).cast().norm(); + prev_edge->to->data.distance_to_boundary = dist; + assert(dist >= 0); + + nodes.emplace_front(SkeletalTrapezoidationJoint(), p); + node_t* node = &nodes.front(); + node->data.distance_to_boundary = 0; + + edges.emplace_front(SkeletalTrapezoidationEdge(SkeletalTrapezoidationEdge::EdgeType::EXTRA_VD)); + edge_t* forth_edge = &edges.front(); + edges.emplace_front(SkeletalTrapezoidationEdge(SkeletalTrapezoidationEdge::EdgeType::EXTRA_VD)); + edge_t* back_edge = &edges.front(); + + prev_edge->next = forth_edge; + forth_edge->prev = prev_edge; + forth_edge->from = prev_edge->to; + forth_edge->to = node; + forth_edge->twin = back_edge; + back_edge->twin = forth_edge; + back_edge->from = node; + back_edge->to = prev_edge->to; + node->incident_edge = back_edge; + + prev_edge = back_edge; +} + +std::pair SkeletalTrapezoidationGraph::insertRib(edge_t& edge, node_t* mid_node) +{ + edge_t* edge_before = edge.prev; + edge_t* edge_after = edge.next; + node_t* node_before = edge.from; + node_t* node_after = edge.to; + + Point p = mid_node->p; + + const Line source_segment = getSource(edge); + Point px; + source_segment.distance_to_squared(p, &px); + coord_t dist = (p - px).cast().norm(); + assert(dist > 0); + mid_node->data.distance_to_boundary = dist; + mid_node->data.transition_ratio = 0; // Both transition end should have rest = 0, because at the ends a whole number of beads fits without rest + + nodes.emplace_back(SkeletalTrapezoidationJoint(), px); + node_t* source_node = &nodes.back(); + source_node->data.distance_to_boundary = 0; + + edge_t* first = &edge; + edges.emplace_back(SkeletalTrapezoidationEdge()); + edge_t* second = &edges.back(); + edges.emplace_back(SkeletalTrapezoidationEdge(SkeletalTrapezoidationEdge::EdgeType::TRANSITION_END)); + edge_t* outward_edge = &edges.back(); + edges.emplace_back(SkeletalTrapezoidationEdge(SkeletalTrapezoidationEdge::EdgeType::TRANSITION_END)); + edge_t* inward_edge = &edges.back(); + + if (edge_before) + { + edge_before->next = first; + } + first->next = outward_edge; + outward_edge->next = nullptr; + inward_edge->next = second; + second->next = edge_after; + + if (edge_after) + { + edge_after->prev = second; + } + second->prev = inward_edge; + inward_edge->prev = nullptr; + outward_edge->prev = first; + first->prev = edge_before; + + first->to = mid_node; + outward_edge->to = source_node; + inward_edge->to = mid_node; + second->to = node_after; + + first->from = node_before; + outward_edge->from = mid_node; + inward_edge->from = source_node; + second->from = mid_node; + + node_before->incident_edge = first; + mid_node->incident_edge = outward_edge; + source_node->incident_edge = inward_edge; + if (edge_after) + { + node_after->incident_edge = edge_after; + } + + first->data.setIsCentral(true); + outward_edge->data.setIsCentral(false); // TODO verify this is always the case. + inward_edge->data.setIsCentral(false); + second->data.setIsCentral(true); + + outward_edge->twin = inward_edge; + inward_edge->twin = outward_edge; + + first->twin = nullptr; // we don't know these yet! + second->twin = nullptr; + + assert(second->prev->from->data.distance_to_boundary == 0); + + return std::make_pair(first, second); +} + +SkeletalTrapezoidationGraph::edge_t* SkeletalTrapezoidationGraph::insertNode(edge_t* edge, Point mid, coord_t mide_node_bead_count) +{ + edge_t* last_edge_replacing_input = edge; + + nodes.emplace_back(SkeletalTrapezoidationJoint(), mid); + node_t* mid_node = &nodes.back(); + + edge_t* twin = last_edge_replacing_input->twin; + last_edge_replacing_input->twin = nullptr; + twin->twin = nullptr; + std::pair left_pair = insertRib(*last_edge_replacing_input, mid_node); + std::pair right_pair = insertRib(*twin, mid_node); + edge_t* first_edge_replacing_input = left_pair.first; + last_edge_replacing_input = left_pair.second; + edge_t* first_edge_replacing_twin = right_pair.first; + edge_t* last_edge_replacing_twin = right_pair.second; + + first_edge_replacing_input->twin = last_edge_replacing_twin; + last_edge_replacing_twin->twin = first_edge_replacing_input; + last_edge_replacing_input->twin = first_edge_replacing_twin; + first_edge_replacing_twin->twin = last_edge_replacing_input; + + mid_node->data.bead_count = mide_node_bead_count; + + return last_edge_replacing_input; +} + +Line SkeletalTrapezoidationGraph::getSource(const edge_t &edge) const +{ + const edge_t *from_edge = &edge; + while (from_edge->prev) + from_edge = from_edge->prev; + + const edge_t *to_edge = &edge; + while (to_edge->next) + to_edge = to_edge->next; + + return Line(from_edge->from->p, to_edge->to->p); +} + +} diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp b/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp new file mode 100644 index 0000000000..cfdbfecdaf --- /dev/null +++ b/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp @@ -0,0 +1,105 @@ +//Copyright (c) 2020 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef SKELETAL_TRAPEZOIDATION_GRAPH_H +#define SKELETAL_TRAPEZOIDATION_GRAPH_H + +#include + +#include "utils/HalfEdgeGraph.hpp" +#include "SkeletalTrapezoidationEdge.hpp" +#include "SkeletalTrapezoidationJoint.hpp" + +namespace Slic3r::Arachne +{ + +class STHalfEdgeNode; + +class STHalfEdge : public HalfEdge +{ + using edge_t = STHalfEdge; + using node_t = STHalfEdgeNode; +public: + STHalfEdge(SkeletalTrapezoidationEdge data); + + /*! + * Check (recursively) whether there is any upward edge from the distance_to_boundary of the from of the \param edge + * + * \param strict Whether equidistant edges can count as a local maximum + */ + bool canGoUp(bool strict = false) const; + + /*! + * Check whether the edge goes from a lower to a higher distance_to_boundary. + * Effectively deals with equidistant edges by looking beyond this edge. + */ + bool isUpward() const; + + /*! + * Calculate the traversed distance until we meet an upward edge. + * Useful for calling on edges between equidistant points. + * + * If we can go up then the distance includes the length of the \param edge + */ + std::optional distToGoUp() const; + + STHalfEdge* getNextUnconnected(); +}; + +class STHalfEdgeNode : public HalfEdgeNode +{ + using edge_t = STHalfEdge; + using node_t = STHalfEdgeNode; +public: + STHalfEdgeNode(SkeletalTrapezoidationJoint data, Point p); + + bool isMultiIntersection(); + + bool isCentral() const; + + /*! + * Check whether this node has a locally maximal distance_to_boundary + * + * \param strict Whether equidistant edges can count as a local maximum + */ + bool isLocalMaximum(bool strict = false) const; +}; + +class SkeletalTrapezoidationGraph: public HalfEdgeGraph +{ + using edge_t = STHalfEdge; + using node_t = STHalfEdgeNode; +public: + + /*! + * If an edge is too small, collapse it and its twin and fix the surrounding edges to ensure a consistent graph. + * + * Don't collapse support edges, unless we can collapse the whole quad. + * + * o-, + * | "-o + * | | > Don't collapse this edge only. + * o o + */ + void collapseSmallEdges(coord_t snap_dist = 5); + + void makeRib(edge_t*& prev_edge, Point start_source_point, Point end_source_point, bool is_next_to_start_or_end); + + /*! + * Insert a node into the graph and connect it to the input polygon using ribs + * + * \return the last edge which replaced [edge], which points to the same [to] node + */ + edge_t* insertNode(edge_t* edge, Point mid, coord_t mide_node_bead_count); + + /*! + * Return the first and last edge of the edges replacing \p edge pointing to the same node + */ + std::pair insertRib(edge_t& edge, node_t* mid_node); + +protected: + Line getSource(const edge_t& edge) const; +}; + +} +#endif diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidationJoint.hpp b/src/libslic3r/Arachne/SkeletalTrapezoidationJoint.hpp new file mode 100644 index 0000000000..346d511165 --- /dev/null +++ b/src/libslic3r/Arachne/SkeletalTrapezoidationJoint.hpp @@ -0,0 +1,60 @@ +//Copyright (c) 2020 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef SKELETAL_TRAPEZOIDATION_JOINT_H +#define SKELETAL_TRAPEZOIDATION_JOINT_H + +#include // smart pointers + +#include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp" + +namespace Slic3r::Arachne +{ + +class SkeletalTrapezoidationJoint +{ + using Beading = BeadingStrategy::Beading; +public: + struct BeadingPropagation + { + Beading beading; + coord_t dist_to_bottom_source; + coord_t dist_from_top_source; + bool is_upward_propagated_only; + BeadingPropagation(const Beading& beading) + : beading(beading) + , dist_to_bottom_source(0) + , dist_from_top_source(0) + , is_upward_propagated_only(false) + {} + }; + + coord_t distance_to_boundary; + coord_t bead_count; + float transition_ratio; //! The distance near the skeleton to leave free because this joint is in the middle of a transition, as a fraction of the inner bead width of the bead at the higher transition. + SkeletalTrapezoidationJoint() + : distance_to_boundary(-1) + , bead_count(-1) + , transition_ratio(0) + {} + + bool hasBeading() const + { + return beading.use_count() > 0; + } + void setBeading(std::shared_ptr storage) + { + beading = storage; + } + std::shared_ptr getBeading() + { + return beading.lock(); + } + +private: + + std::weak_ptr beading; +}; + +} // namespace Slic3r::Arachne +#endif // SKELETAL_TRAPEZOIDATION_JOINT_H diff --git a/src/libslic3r/Arachne/WallToolPaths.cpp b/src/libslic3r/Arachne/WallToolPaths.cpp new file mode 100644 index 0000000000..d40b6c4882 --- /dev/null +++ b/src/libslic3r/Arachne/WallToolPaths.cpp @@ -0,0 +1,843 @@ +// Copyright (c) 2022 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include //For std::partition_copy and std::min_element. +#include + +#include "WallToolPaths.hpp" + +#include "SkeletalTrapezoidation.hpp" +#include "../ClipperUtils.hpp" +#include "utils/linearAlg2D.hpp" +#include "EdgeGrid.hpp" +#include "utils/SparseLineGrid.hpp" +#include "Geometry.hpp" +#include "utils/PolylineStitcher.hpp" +#include "SVG.hpp" +#include "Utils.hpp" + +#include + +//#define ARACHNE_STITCH_PATCH_DEBUG + +namespace Slic3r::Arachne +{ + +WallToolPaths::WallToolPaths(const Polygons& outline, const coord_t bead_width_0, const coord_t bead_width_x, + const size_t inset_count, const coord_t wall_0_inset, const WallToolPathsParams ¶ms) + : outline(outline) + , bead_width_0(bead_width_0) + , bead_width_x(bead_width_x) + , inset_count(inset_count) + , wall_0_inset(wall_0_inset) + , print_thin_walls(Slic3r::Arachne::fill_outline_gaps) + , min_feature_size(scaled(params.min_feature_size)) + , min_bead_width(scaled(params.min_bead_width)) + , small_area_length(static_cast(bead_width_0) / 2.) + , toolpaths_generated(false) + , wall_transition_filter_deviation(scaled(params.wall_transition_filter_deviation)) + , m_params(params) +{ +} + +void simplify(Polygon &thiss, const int64_t smallest_line_segment_squared, const int64_t allowed_error_distance_squared) +{ + if (thiss.size() < 3) { + thiss.points.clear(); + return; + } + if (thiss.size() == 3) + return; + + Polygon new_path; + Point previous = thiss.points.back(); + Point previous_previous = thiss.points.at(thiss.points.size() - 2); + Point current = thiss.points.at(0); + + /* When removing a vertex, we check the height of the triangle of the area + being removed from the original polygon by the simplification. However, + when consecutively removing multiple vertices the height of the previously + removed vertices w.r.t. the shortcut path changes. + In order to not recompute the new height value of previously removed + vertices we compute the height of a representative triangle, which covers + the same amount of area as the area being cut off. We use the Shoelace + formula to accumulate the area under the removed segments. This works by + computing the area in a 'fan' where each of the blades of the fan go from + the origin to one of the segments. While removing vertices the area in + this fan accumulates. By subtracting the area of the blade connected to + the short-cutting segment we obtain the total area of the cutoff region. + From this area we compute the height of the representative triangle using + the standard formula for a triangle area: A = .5*b*h + */ + int64_t accumulated_area_removed = int64_t(previous.x()) * int64_t(current.y()) - int64_t(previous.y()) * int64_t(current.x()); // Twice the Shoelace formula for area of polygon per line segment. + + for (size_t point_idx = 0; point_idx < thiss.points.size(); point_idx++) { + current = thiss.points.at(point_idx % thiss.points.size()); + + //Check if the accumulated area doesn't exceed the maximum. + Point next; + if (point_idx + 1 < thiss.points.size()) { + next = thiss.points.at(point_idx + 1); + } else if (point_idx + 1 == thiss.points.size() && new_path.size() > 1) { // don't spill over if the [next] vertex will then be equal to [previous] + next = new_path[0]; //Spill over to new polygon for checking removed area. + } else { + next = thiss.points.at((point_idx + 1) % thiss.points.size()); + } + const int64_t removed_area_next = int64_t(current.x()) * int64_t(next.y()) - int64_t(current.y()) * int64_t(next.x()); // Twice the Shoelace formula for area of polygon per line segment. + const int64_t negative_area_closing = int64_t(next.x()) * int64_t(previous.y()) - int64_t(next.y()) * int64_t(previous.x()); // area between the origin and the short-cutting segment + accumulated_area_removed += removed_area_next; + + const int64_t length2 = (current - previous).cast().squaredNorm(); + if (length2 < scaled(25.)) { + // We're allowed to always delete segments of less than 5 micron. + continue; + } + + const int64_t area_removed_so_far = accumulated_area_removed + negative_area_closing; // close the shortcut area polygon + const int64_t base_length_2 = (next - previous).cast().squaredNorm(); + + if (base_length_2 == 0) //Two line segments form a line back and forth with no area. + continue; //Remove the vertex. + //We want to check if the height of the triangle formed by previous, current and next vertices is less than allowed_error_distance_squared. + //1/2 L = A [actual area is half of the computed shoelace value] // Shoelace formula is .5*(...) , but we simplify the computation and take out the .5 + //A = 1/2 * b * h [triangle area formula] + //L = b * h [apply above two and take out the 1/2] + //h = L / b [divide by b] + //h^2 = (L / b)^2 [square it] + //h^2 = L^2 / b^2 [factor the divisor] + const int64_t height_2 = double(area_removed_so_far) * double(area_removed_so_far) / double(base_length_2); + if ((height_2 <= Slic3r::sqr(scaled(0.005)) //Almost exactly colinear (barring rounding errors). + && Line::distance_to_infinite(current, previous, next) <= scaled(0.005))) // make sure that height_2 is not small because of cancellation of positive and negative areas + continue; + + if (length2 < smallest_line_segment_squared + && height_2 <= allowed_error_distance_squared) // removing the vertex doesn't introduce too much error.) + { + const int64_t next_length2 = (current - next).cast().squaredNorm(); + if (next_length2 > 4 * smallest_line_segment_squared) { + // Special case; The next line is long. If we were to remove this, it could happen that we get quite noticeable artifacts. + // We should instead move this point to a location where both edges are kept and then remove the previous point that we wanted to keep. + // By taking the intersection of these two lines, we get a point that preserves the direction (so it makes the corner a bit more pointy). + // We just need to be sure that the intersection point does not introduce an artifact itself. + Point intersection_point; + bool has_intersection = Line(previous_previous, previous).intersection_infinite(Line(current, next), &intersection_point); + if (!has_intersection + || Line::distance_to_infinite_squared(intersection_point, previous, current) > double(allowed_error_distance_squared) + || (intersection_point - previous).cast().squaredNorm() > smallest_line_segment_squared // The intersection point is way too far from the 'previous' + || (intersection_point - next).cast().squaredNorm() > smallest_line_segment_squared) // and 'next' points, so it shouldn't replace 'current' + { + // We can't find a better spot for it, but the size of the line is more than 5 micron. + // So the only thing we can do here is leave it in... + } + else { + // New point seems like a valid one. + current = intersection_point; + // If there was a previous point added, remove it. + if(!new_path.empty()) { + new_path.points.pop_back(); + previous = previous_previous; + } + } + } else { + continue; //Remove the vertex. + } + } + //Don't remove the vertex. + accumulated_area_removed = removed_area_next; // so that in the next iteration it's the area between the origin, [previous] and [current] + previous_previous = previous; + previous = current; //Note that "previous" is only updated if we don't remove the vertex. + new_path.points.push_back(current); + } + + thiss = new_path; +} + +/*! + * Removes vertices of the polygons to make sure that they are not too high + * resolution. + * + * This removes points which are connected to line segments that are shorter + * than the `smallest_line_segment`, unless that would introduce a deviation + * in the contour of more than `allowed_error_distance`. + * + * Criteria: + * 1. Never remove a vertex if either of the connceted segments is larger than \p smallest_line_segment + * 2. Never remove a vertex if the distance between that vertex and the final resulting polygon would be higher than \p allowed_error_distance + * 3. The direction of segments longer than \p smallest_line_segment always + * remains unaltered (but their end points may change if it is connected to + * a small segment) + * + * Simplify uses a heuristic and doesn't neccesarily remove all removable + * vertices under the above criteria, but simplify may never violate these + * criteria. Unless the segments or the distance is smaller than the + * rounding error of 5 micron. + * + * Vertices which introduce an error of less than 5 microns are removed + * anyway, even if the segments are longer than the smallest line segment. + * This makes sure that (practically) colinear line segments are joined into + * a single line segment. + * \param smallest_line_segment Maximal length of removed line segments. + * \param allowed_error_distance If removing a vertex introduces a deviation + * from the original path that is more than this distance, the vertex may + * not be removed. + */ +void simplify(Polygons &thiss, const int64_t smallest_line_segment = scaled(0.01), const int64_t allowed_error_distance = scaled(0.005)) +{ + const int64_t allowed_error_distance_squared = int64_t(allowed_error_distance) * int64_t(allowed_error_distance); + const int64_t smallest_line_segment_squared = int64_t(smallest_line_segment) * int64_t(smallest_line_segment); + for (size_t p = 0; p < thiss.size(); p++) + { + simplify(thiss[p], smallest_line_segment_squared, allowed_error_distance_squared); + if (thiss[p].size() < 3) + { + thiss.erase(thiss.begin() + p); + p--; + } + } +} + +typedef SparseLineGrid LocToLineGrid; +std::unique_ptr createLocToLineGrid(const Polygons &polygons, int square_size) +{ + unsigned int n_points = 0; + for (const auto &poly : polygons) + n_points += poly.size(); + + auto ret = std::make_unique(square_size, n_points); + + for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++) + for (unsigned int point_idx = 0; point_idx < polygons[poly_idx].size(); point_idx++) + ret->insert(PolygonsPointIndex(&polygons, poly_idx, point_idx)); + return ret; +} + +/* Note: Also tries to solve for near-self intersections, when epsilon >= 1 + */ +void fixSelfIntersections(const coord_t epsilon, Polygons &thiss) +{ + if (epsilon < 1) { + ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(thiss)); + return; + } + + const int64_t half_epsilon = (epsilon + 1) / 2; + + // Points too close to line segments should be moved a little away from those line segments, but less than epsilon, + // so at least half-epsilon distance between points can still be guaranteed. + constexpr coord_t grid_size = scaled(2.); + auto query_grid = createLocToLineGrid(thiss, grid_size); + + const auto move_dist = std::max(2L, half_epsilon - 2); + const int64_t half_epsilon_sqrd = half_epsilon * half_epsilon; + + const size_t n = thiss.size(); + for (size_t poly_idx = 0; poly_idx < n; poly_idx++) { + const size_t pathlen = thiss[poly_idx].size(); + for (size_t point_idx = 0; point_idx < pathlen; ++point_idx) { + Point &pt = thiss[poly_idx][point_idx]; + for (const auto &line : query_grid->getNearby(pt, epsilon)) { + const size_t line_next_idx = (line.point_idx + 1) % thiss[line.poly_idx].size(); + if (poly_idx == line.poly_idx && (point_idx == line.point_idx || point_idx == line_next_idx)) + continue; + + const Line segment(thiss[line.poly_idx][line.point_idx], thiss[line.poly_idx][line_next_idx]); + Point segment_closest_point; + segment.distance_to_squared(pt, &segment_closest_point); + + if (half_epsilon_sqrd >= (pt - segment_closest_point).cast().squaredNorm()) { + const Point &other = thiss[poly_idx][(point_idx + 1) % pathlen]; + const Vec2i64 vec = (LinearAlg2D::pointIsLeftOfLine(other, segment.a, segment.b) > 0 ? segment.b - segment.a : segment.a - segment.b).cast(); + assert(Slic3r::sqr(double(vec.x())) < double(std::numeric_limits::max())); + assert(Slic3r::sqr(double(vec.y())) < double(std::numeric_limits::max())); + const int64_t len = vec.norm(); + pt.x() += (-vec.y() * move_dist) / len; + pt.y() += (vec.x() * move_dist) / len; + } + } + } + } + + ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(thiss)); +} + +/*! + * Removes overlapping consecutive line segments which don't delimit a positive area. + */ +void removeDegenerateVerts(Polygons &thiss) +{ + for (size_t poly_idx = 0; poly_idx < thiss.size(); poly_idx++) { + Polygon &poly = thiss[poly_idx]; + Polygon result; + + auto isDegenerate = [](const Point &last, const Point &now, const Point &next) { + Vec2i64 last_line = (now - last).cast(); + Vec2i64 next_line = (next - now).cast(); + return last_line.dot(next_line) == -1 * last_line.norm() * next_line.norm(); + }; + bool isChanged = false; + for (size_t idx = 0; idx < poly.size(); idx++) { + const Point &last = (result.size() == 0) ? poly.back() : result.back(); + if (idx + 1 == poly.size() && result.size() == 0) + break; + + const Point &next = (idx + 1 == poly.size()) ? result[0] : poly[idx + 1]; + if (isDegenerate(last, poly[idx], next)) { // lines are in the opposite direction + // don't add vert to the result + isChanged = true; + while (result.size() > 1 && isDegenerate(result[result.size() - 2], result.back(), next)) + result.points.pop_back(); + } else { + result.points.emplace_back(poly[idx]); + } + } + + if (isChanged) { + if (result.size() > 2) { + poly = result; + } else { + thiss.erase(thiss.begin() + poly_idx); + poly_idx--; // effectively the next iteration has the same poly_idx (referring to a new poly which is not yet processed) + } + } + } +} + +void removeSmallAreas(Polygons &thiss, const double min_area_size, const bool remove_holes) +{ + auto to_path = [](const Polygon &poly) -> ClipperLib::Path { + ClipperLib::Path out; + for (const Point &pt : poly.points) + out.emplace_back(ClipperLib::cInt(pt.x()), ClipperLib::cInt(pt.y())); + return out; + }; + + auto new_end = thiss.end(); + if(remove_holes) + { + for(auto it = thiss.begin(); it < new_end; it++) + { + // All polygons smaller than target are removed by replacing them with a polygon from the back of the vector + if(fabs(ClipperLib::Area(to_path(*it))) < min_area_size) + { + new_end--; + *it = std::move(*new_end); + it--; // wind back the iterator such that the polygon just swaped in is checked next + } + } + } + else + { + // For each polygon, computes the signed area, move small outlines at the end of the vector and keep pointer on small holes + std::vector small_holes; + for(auto it = thiss.begin(); it < new_end; it++) { + double area = ClipperLib::Area(to_path(*it)); + if (fabs(area) < min_area_size) + { + if(area >= 0) + { + new_end--; + if(it < new_end) { + std::swap(*new_end, *it); + it--; + } + else + { // Don't self-swap the last Path + break; + } + } + else + { + small_holes.push_back(*it); + } + } + } + + // Removes small holes that have their first point inside one of the removed outlines + // Iterating in reverse ensures that unprocessed small holes won't be moved + const auto removed_outlines_start = new_end; + for(auto hole_it = small_holes.rbegin(); hole_it < small_holes.rend(); hole_it++) + { + for(auto outline_it = removed_outlines_start; outline_it < thiss.end() ; outline_it++) + { + if(Polygon(*outline_it).contains(*hole_it->begin())) { + new_end--; + *hole_it = std::move(*new_end); + break; + } + } + } + } + thiss.resize(new_end-thiss.begin()); +} + +void removeColinearEdges(Polygon &poly, const double max_deviation_angle) +{ + // TODO: Can be made more efficient (for example, use pointer-types for process-/skip-indices, so we can swap them without copy). + size_t num_removed_in_iteration = 0; + do { + num_removed_in_iteration = 0; + std::vector process_indices(poly.points.size(), true); + + bool go = true; + while (go) { + go = false; + + const auto &rpath = poly; + const size_t pathlen = rpath.size(); + if (pathlen <= 3) + return; + + std::vector skip_indices(poly.points.size(), false); + + Polygon new_path; + for (size_t point_idx = 0; point_idx < pathlen; ++point_idx) { + // Don't iterate directly over process-indices, but do it this way, because there are points _in_ process-indices that should nonetheless + // be skipped: + if (!process_indices[point_idx]) { + new_path.points.push_back(rpath[point_idx]); + continue; + } + + // Should skip the last point for this iteration if the old first was removed (which can be seen from the fact that the new first was skipped): + if (point_idx == (pathlen - 1) && skip_indices[0]) { + skip_indices[new_path.size()] = true; + go = true; + new_path.points.push_back(rpath[point_idx]); + break; + } + + const Point &prev = rpath[(point_idx - 1 + pathlen) % pathlen]; + const Point &pt = rpath[point_idx]; + const Point &next = rpath[(point_idx + 1) % pathlen]; + + float angle = LinearAlg2D::getAngleLeft(prev, pt, next); // [0 : 2 * pi] + if (angle >= float(M_PI)) { angle -= float(M_PI); } // map [pi : 2 * pi] to [0 : pi] + + // Check if the angle is within limits for the point to 'make sense', given the maximum deviation. + // If the angle indicates near-parallel segments ignore the point 'pt' + if (angle > max_deviation_angle && angle < M_PI - max_deviation_angle) { + new_path.points.push_back(pt); + } else if (point_idx != (pathlen - 1)) { + // Skip the next point, since the current one was removed: + skip_indices[new_path.size()] = true; + go = true; + new_path.points.push_back(next); + ++point_idx; + } + } + poly = new_path; + num_removed_in_iteration += pathlen - poly.points.size(); + + process_indices.clear(); + process_indices.insert(process_indices.end(), skip_indices.begin(), skip_indices.end()); + } + } while (num_removed_in_iteration > 0); +} + +void removeColinearEdges(Polygons &thiss, const double max_deviation_angle = 0.0005) +{ + for (int p = 0; p < int(thiss.size()); p++) { + removeColinearEdges(thiss[p], max_deviation_angle); + if (thiss[p].size() < 3) { + thiss.erase(thiss.begin() + p); + p--; + } + } +} + +const std::vector &WallToolPaths::generate() +{ + if (this->inset_count < 1) + return toolpaths; + + const coord_t smallest_segment = Slic3r::Arachne::meshfix_maximum_resolution; + const coord_t allowed_distance = Slic3r::Arachne::meshfix_maximum_deviation; + const coord_t epsilon_offset = (allowed_distance / 2) - 1; + const double transitioning_angle = Geometry::deg2rad(m_params.wall_transition_angle); + constexpr coord_t discretization_step_size = scaled(0.8); + + // Simplify outline for boost::voronoi consumption. Absolutely no self intersections or near-self intersections allowed: + // TODO: Open question: Does this indeed fix all (or all-but-one-in-a-million) cases for manifold but otherwise possibly complex polygons? + Polygons prepared_outline = offset(offset(offset(outline, -epsilon_offset), epsilon_offset * 2), -epsilon_offset); + simplify(prepared_outline, smallest_segment, allowed_distance); + fixSelfIntersections(epsilon_offset, prepared_outline); + removeDegenerateVerts(prepared_outline); + removeColinearEdges(prepared_outline, 0.005); + // Removing collinear edges may introduce self intersections, so we need to fix them again + fixSelfIntersections(epsilon_offset, prepared_outline); + removeDegenerateVerts(prepared_outline); + removeSmallAreas(prepared_outline, small_area_length * small_area_length, false); + + // The functions above could produce intersecting polygons that could cause a crash inside Arachne. + // Applying Clipper union should be enough to get rid of this issue. + // Clipper union also fixed an issue in Arachne that in post-processing Voronoi diagram, some edges + // didn't have twin edges (this probably isn't an issue in Boost Voronoi generator). + prepared_outline = union_(prepared_outline); + + if (area(prepared_outline) <= 0) { + assert(toolpaths.empty()); + return toolpaths; + } + + const coord_t wall_transition_length = scaled(this->m_params.wall_transition_length); + const double wall_split_middle_threshold = this->m_params.wall_split_middle_threshold; // For an uneven nr. of lines: When to split the middle wall into two. + const double wall_add_middle_threshold = this->m_params.wall_add_middle_threshold; // For an even nr. of lines: When to add a new middle in between the innermost two walls. + const int wall_distribution_count = this->m_params.wall_distribution_count; + const size_t max_bead_count = (inset_count < std::numeric_limits::max() / 2) ? 2 * inset_count : std::numeric_limits::max(); + const auto beading_strat = BeadingStrategyFactory::makeStrategy + ( + bead_width_0, + bead_width_x, + wall_transition_length, + transitioning_angle, + print_thin_walls, + min_bead_width, + min_feature_size, + wall_split_middle_threshold, + wall_add_middle_threshold, + max_bead_count, + wall_0_inset, + wall_distribution_count + ); + const coord_t transition_filter_dist = scaled(100.f); + const coord_t allowed_filter_deviation = wall_transition_filter_deviation; + SkeletalTrapezoidation wall_maker + ( + prepared_outline, + *beading_strat, + beading_strat->getTransitioningAngle(), + discretization_step_size, + transition_filter_dist, + allowed_filter_deviation, + wall_transition_length + ); + wall_maker.generateToolpaths(toolpaths); + + stitchToolPaths(toolpaths, this->bead_width_x); + + removeSmallLines(toolpaths); + + separateOutInnerContour(); + + simplifyToolPaths(toolpaths); + + removeEmptyToolPaths(toolpaths); + assert(std::is_sorted(toolpaths.cbegin(), toolpaths.cend(), + [](const VariableWidthLines& l, const VariableWidthLines& r) + { + return l.front().inset_idx < r.front().inset_idx; + }) && "WallToolPaths should be sorted from the outer 0th to inner_walls"); + toolpaths_generated = true; + return toolpaths; +} + +void WallToolPaths::stitchToolPaths(std::vector &toolpaths, const coord_t bead_width_x) +{ + const coord_t stitch_distance = bead_width_x - 1; //In 0-width contours, junctions can cause up to 1-line-width gaps. Don't stitch more than 1 line width. + + for (unsigned int wall_idx = 0; wall_idx < toolpaths.size(); wall_idx++) { + VariableWidthLines& wall_lines = toolpaths[wall_idx]; + + VariableWidthLines stitched_polylines; + VariableWidthLines closed_polygons; + PolylineStitcher::stitch(wall_lines, stitched_polylines, closed_polygons, stitch_distance); +#ifdef ARACHNE_STITCH_PATCH_DEBUG + for (const ExtrusionLine& line : stitched_polylines) { + if ( ! line.is_odd && line.polylineLength() > 3 * stitch_distance && line.size() > 3) { + BOOST_LOG_TRIVIAL(error) << "Some even contour lines could not be closed into polygons!"; + assert(false && "Some even contour lines could not be closed into polygons!"); + BoundingBox aabb; + for (auto line2 : wall_lines) + for (auto j : line2) + aabb.merge(j.p); + { + static int iRun = 0; + SVG svg(debug_out_path("contours_before.svg-%d.png", iRun), aabb); + std::array colors = {"gray", "black", "blue", "green", "lime", "purple", "red", "yellow"}; + size_t color_idx = 0; + for (auto& inset : toolpaths) + for (auto& line2 : inset) { + // svg.writePolyline(line2.toPolygon(), col); + + Polygon poly = line2.toPolygon(); + Point last = poly.front(); + for (size_t idx = 1 ; idx < poly.size(); idx++) { + Point here = poly[idx]; + svg.draw(Line(last, here), colors[color_idx]); +// svg.draw_text((last + here) / 2, std::to_string(line2.junctions[idx].region_id).c_str(), "black"); + last = here; + } + svg.draw(poly[0], colors[color_idx]); + // svg.nextLayer(); + // svg.writePoints(poly, true, 0.1); + // svg.nextLayer(); + color_idx = (color_idx + 1) % colors.size(); + } + } + { + static int iRun = 0; + SVG svg(debug_out_path("contours-%d.svg", iRun), aabb); + for (auto& inset : toolpaths) + for (auto& line2 : inset) + svg.draw_outline(line2.toPolygon(), "gray"); + for (auto& line2 : stitched_polylines) { + const char *col = line2.is_odd ? "gray" : "red"; + if ( ! line2.is_odd) + std::cerr << "Non-closed even wall of size: " << line2.size() << " at " << line2.front().p << "\n"; + if ( ! line2.is_odd) + svg.draw(line2.front().p); + Polygon poly = line2.toPolygon(); + Point last = poly.front(); + for (size_t idx = 1 ; idx < poly.size(); idx++) + { + Point here = poly[idx]; + svg.draw(Line(last, here), col); + last = here; + } + } + for (auto line2 : closed_polygons) + svg.draw(line2.toPolygon()); + } + } + } +#endif // ARACHNE_STITCH_PATCH_DEBUG + wall_lines = stitched_polylines; // replace input toolpaths with stitched polylines + + for (ExtrusionLine& wall_polygon : closed_polygons) + { + if (wall_polygon.junctions.empty()) + { + continue; + } + wall_polygon.is_closed = true; + wall_lines.emplace_back(std::move(wall_polygon)); // add stitched polygons to result + } +#ifdef DEBUG + for (ExtrusionLine& line : wall_lines) + { + assert(line.inset_idx == wall_idx); + } +#endif // DEBUG + } +} + +template bool shorterThan(const T &shape, const coord_t check_length) +{ + const auto *p0 = &shape.back(); + int64_t length = 0; + for (const auto &p1 : shape) { + length += (*p0 - p1).template cast().norm(); + if (length >= check_length) + return false; + p0 = &p1; + } + return true; +} + +void WallToolPaths::removeSmallLines(std::vector &toolpaths) +{ + for (VariableWidthLines &inset : toolpaths) { + for (size_t line_idx = 0; line_idx < inset.size(); line_idx++) { + ExtrusionLine &line = inset[line_idx]; + coord_t min_width = std::numeric_limits::max(); + for (const ExtrusionJunction &j : line) + min_width = std::min(min_width, j.w); + if (line.is_odd && !line.is_closed && shorterThan(line, min_width / 2)) { // remove line + line = std::move(inset.back()); + inset.erase(--inset.end()); + line_idx--; // reconsider the current position + } + } + } +} + +void WallToolPaths::simplifyToolPaths(std::vector &toolpaths) +{ + for (size_t toolpaths_idx = 0; toolpaths_idx < toolpaths.size(); ++toolpaths_idx) + { + const int64_t maximum_resolution = Slic3r::Arachne::meshfix_maximum_resolution; + const int64_t maximum_deviation = Slic3r::Arachne::meshfix_maximum_deviation; + const int64_t maximum_extrusion_area_deviation = Slic3r::Arachne::meshfix_maximum_extrusion_area_deviation; // unit: μm² + for (auto& line : toolpaths[toolpaths_idx]) + { + line.simplify(maximum_resolution * maximum_resolution, maximum_deviation * maximum_deviation, maximum_extrusion_area_deviation); + } + } +} + +const std::vector &WallToolPaths::getToolPaths() +{ + if (!toolpaths_generated) + return generate(); + return toolpaths; +} + +void WallToolPaths::separateOutInnerContour() +{ + //We'll remove all 0-width paths from the original toolpaths and store them separately as polygons. + std::vector actual_toolpaths; + actual_toolpaths.reserve(toolpaths.size()); //A bit too much, but the correct order of magnitude. + std::vector contour_paths; + contour_paths.reserve(toolpaths.size() / inset_count); + inner_contour.clear(); + for (const VariableWidthLines &inset : toolpaths) { + if (inset.empty()) + continue; + bool is_contour = false; + for (const ExtrusionLine &line : inset) { + for (const ExtrusionJunction &j : line) { + if (j.w == 0) + is_contour = true; + else + is_contour = false; + break; + } + } + + if (is_contour) { +#ifdef DEBUG + for (const ExtrusionLine &line : inset) + for (const ExtrusionJunction &j : line) + assert(j.w == 0); +#endif // DEBUG + for (const ExtrusionLine &line : inset) { + if (line.is_odd) + continue; // odd lines don't contribute to the contour + else if (line.is_closed) // sometimes an very small even polygonal wall is not stitched into a polygon + inner_contour.emplace_back(line.toPolygon()); + } + } else { + actual_toolpaths.emplace_back(inset); + } + } + if (!actual_toolpaths.empty()) + toolpaths = std::move(actual_toolpaths); // Filtered out the 0-width paths. + else + toolpaths.clear(); + + //The output walls from the skeletal trapezoidation have no known winding order, especially if they are joined together from polylines. + //They can be in any direction, clockwise or counter-clockwise, regardless of whether the shapes are positive or negative. + //To get a correct shape, we need to make the outside contour positive and any holes inside negative. + //This can be done by applying the even-odd rule to the shape. This rule is not sensitive to the winding order of the polygon. + //The even-odd rule would be incorrect if the polygon self-intersects, but that should never be generated by the skeletal trapezoidation. + inner_contour = union_(inner_contour, ClipperLib::PolyFillType::pftEvenOdd); +} + +const Polygons& WallToolPaths::getInnerContour() +{ + if (!toolpaths_generated && inset_count > 0) + { + generate(); + } + else if(inset_count == 0) + { + return outline; + } + return inner_contour; +} + +bool WallToolPaths::removeEmptyToolPaths(std::vector &toolpaths) +{ + toolpaths.erase(std::remove_if(toolpaths.begin(), toolpaths.end(), [](const VariableWidthLines& lines) + { + return lines.empty(); + }), toolpaths.end()); + return toolpaths.empty(); +} + +/*! + * Get the order constraints of the insets when printing walls per region / hole. + * Each returned pair consists of adjacent wall lines where the left has an inset_idx one lower than the right. + * + * Odd walls should always go after their enclosing wall polygons. + * + * \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one. + */ +std::unordered_set, boost::hash>> WallToolPaths::getRegionOrder(const std::vector &input, const bool outer_to_inner) +{ + std::unordered_set, boost::hash>> order_requirements; + + // We build a grid where we map toolpath vertex locations to toolpaths, + // so that we can easily find which two toolpaths are next to each other, + // which is the requirement for there to be an order constraint. + // + // We use a PointGrid rather than a LineGrid to save on computation time. + // In very rare cases two insets might lie next to each other without having neighboring vertices, e.g. + // \ . + // | / . + // | / . + // || . + // | \ . + // | \ . + // / . + // However, because of how Arachne works this will likely never be the case for two consecutive insets. + // On the other hand one could imagine that two consecutive insets of a very large circle + // could be simplify()ed such that the remaining vertices of the two insets don't align. + // In those cases the order requirement is not captured, + // which means that the PathOrderOptimizer *might* result in a violation of the user set path order. + // This problem is expected to be not so severe and happen very sparsely. + + coord_t max_line_w = 0u; + for (const ExtrusionLine *line : input) // compute max_line_w + for (const ExtrusionJunction &junction : *line) + max_line_w = std::max(max_line_w, junction.w); + if (max_line_w == 0u) + return order_requirements; + + struct LineLoc + { + ExtrusionJunction j; + const ExtrusionLine *line; + }; + struct Locator + { + Point operator()(const LineLoc &elem) { return elem.j.p; } + }; + + // How much farther two verts may be apart due to corners. + // This distance must be smaller than 2, because otherwise + // we could create an order requirement between e.g. + // wall 2 of one region and wall 3 of another region, + // while another wall 3 of the first region would lie in between those two walls. + // However, higher values are better against the limitations of using a PointGrid rather than a LineGrid. + constexpr float diagonal_extension = 1.9f; + const auto searching_radius = coord_t(max_line_w * diagonal_extension); + using GridT = SparsePointGrid; + GridT grid(searching_radius); + + for (const ExtrusionLine *line : input) + for (const ExtrusionJunction &junction : *line) grid.insert(LineLoc{junction, line}); + for (const std::pair &pair : grid) { + const LineLoc &lineloc_here = pair.second; + const ExtrusionLine *here = lineloc_here.line; + Point loc_here = pair.second.j.p; + std::vector nearby_verts = grid.getNearby(loc_here, searching_radius); + for (const LineLoc &lineloc_nearby : nearby_verts) { + const ExtrusionLine *nearby = lineloc_nearby.line; + if (nearby == here) + continue; + if (nearby->inset_idx == here->inset_idx) + continue; + if (nearby->inset_idx > here->inset_idx + 1) + continue; // not directly adjacent + if (here->inset_idx > nearby->inset_idx + 1) + continue; // not directly adjacent + if (!shorter_then(loc_here - lineloc_nearby.j.p, (lineloc_here.j.w + lineloc_nearby.j.w) / 2 * diagonal_extension)) + continue; // points are too far away from each other + if (here->is_odd || nearby->is_odd) { + if (here->is_odd && !nearby->is_odd && nearby->inset_idx < here->inset_idx) + order_requirements.emplace(std::make_pair(nearby, here)); + if (nearby->is_odd && !here->is_odd && here->inset_idx < nearby->inset_idx) + order_requirements.emplace(std::make_pair(here, nearby)); + } else if ((nearby->inset_idx < here->inset_idx) == outer_to_inner) { + order_requirements.emplace(std::make_pair(nearby, here)); + } else { + assert((nearby->inset_idx > here->inset_idx) == outer_to_inner); + order_requirements.emplace(std::make_pair(here, nearby)); + } + } + } + return order_requirements; +} + +} // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/WallToolPaths.hpp b/src/libslic3r/Arachne/WallToolPaths.hpp new file mode 100644 index 0000000000..2f9879f0c0 --- /dev/null +++ b/src/libslic3r/Arachne/WallToolPaths.hpp @@ -0,0 +1,139 @@ +// Copyright (c) 2020 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef CURAENGINE_WALLTOOLPATHS_H +#define CURAENGINE_WALLTOOLPATHS_H + +#include +#include + +#include "BeadingStrategy/BeadingStrategyFactory.hpp" +#include "utils/ExtrusionLine.hpp" +#include "../Polygon.hpp" +#include "../PrintConfig.hpp" + +namespace Slic3r::Arachne +{ + +constexpr bool fill_outline_gaps = true; +constexpr coord_t meshfix_maximum_resolution = scaled(0.5); +constexpr coord_t meshfix_maximum_deviation = scaled(0.025); +constexpr coord_t meshfix_maximum_extrusion_area_deviation = scaled(2.); + +class WallToolPathsParams +{ +public: + float min_bead_width; + float min_feature_size; + float wall_transition_length; + float wall_transition_angle; + float wall_transition_filter_deviation; + int wall_distribution_count; + float wall_add_middle_threshold; + float wall_split_middle_threshold; +}; + +class WallToolPaths +{ +public: + /*! + * A class that creates the toolpaths given an outline, nominal bead width and maximum amount of walls + * \param outline An outline of the area in which the ToolPaths are to be generated + * \param bead_width_0 The bead width of the first wall used in the generation of the toolpaths + * \param bead_width_x The bead width of the inner walls used in the generation of the toolpaths + * \param inset_count The maximum number of parallel extrusion lines that make up the wall + * \param wall_0_inset How far to inset the outer wall, to make it adhere better to other walls. + */ + WallToolPaths(const Polygons& outline, coord_t bead_width_0, coord_t bead_width_x, size_t inset_count, coord_t wall_0_inset, const WallToolPathsParams ¶ms); + + /*! + * Generates the Toolpaths + * \return A reference to the newly create ToolPaths + */ + const std::vector &generate(); + + /*! + * Gets the toolpaths, if this called before \p generate() it will first generate the Toolpaths + * \return a reference to the toolpaths + */ + const std::vector &getToolPaths(); + + /*! + * Compute the inner contour of the walls. This contour indicates where the walled area ends and its infill begins. + * The inside can then be filled, e.g. with skin/infill for the walls of a part, or with a pattern in the case of + * infill with extra infill walls. + */ + void separateOutInnerContour(); + + /*! + * Gets the inner contour of the area which is inside of the generated tool + * paths. + * + * If the walls haven't been generated yet, this will lazily call the + * \p generate() function to generate the walls with variable width. + * The resulting polygon will snugly match the inside of the variable-width + * walls where the walls get limited by the LimitedBeadingStrategy to a + * maximum wall count. + * If there are no walls, the outline will be returned. + * \return The inner contour of the generated walls. + */ + const Polygons& getInnerContour(); + + /*! + * Removes empty paths from the toolpaths + * \param toolpaths the VariableWidthPaths generated with \p generate() + * \return true if there are still paths left. If all toolpaths were removed it returns false + */ + static bool removeEmptyToolPaths(std::vector &toolpaths); + + /*! + * Get the order constraints of the insets when printing walls per region / hole. + * Each returned pair consists of adjacent wall lines where the left has an inset_idx one lower than the right. + * + * Odd walls should always go after their enclosing wall polygons. + * + * \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one. + */ + static std::unordered_set, boost::hash>> getRegionOrder(const std::vector &input, bool outer_to_inner); + +protected: + /*! + * Stitch the polylines together and form closed polygons. + * + * Works on both toolpaths and inner contours simultaneously. + */ + static void stitchToolPaths(std::vector &toolpaths, coord_t bead_width_x); + + /*! + * Remove polylines shorter than half the smallest line width along that polyline. + */ + static void removeSmallLines(std::vector &toolpaths); + + /*! + * Simplifies the variable-width toolpaths by calling the simplify on every line in the toolpath using the provided + * settings. + * \param settings The settings as provided by the user + * \return + */ + static void simplifyToolPaths(std::vector &toolpaths); + +private: + const Polygons& outline; // toolpaths; //; // + +#include "ExtrusionLine.hpp" +#include "linearAlg2D.hpp" +#include "../../VariableWidth.hpp" + +namespace Slic3r::Arachne +{ + +ExtrusionLine::ExtrusionLine(const size_t inset_idx, const bool is_odd) : inset_idx(inset_idx), is_odd(is_odd), is_closed(false) {} + +int64_t ExtrusionLine::getLength() const +{ + if (junctions.empty()) + return 0; + + int64_t len = 0; + ExtrusionJunction prev = junctions.front(); + for (const ExtrusionJunction &next : junctions) { + len += (next.p - prev.p).cast().norm(); + prev = next; + } + if (is_closed) + len += (front().p - back().p).cast().norm(); + + return len; +} + +coord_t ExtrusionLine::getMinimalWidth() const +{ + return std::min_element(junctions.cbegin(), junctions.cend(), + [](const ExtrusionJunction& l, const ExtrusionJunction& r) + { + return l.w < r.w; + })->w; +} + +void ExtrusionLine::simplify(const int64_t smallest_line_segment_squared, const int64_t allowed_error_distance_squared, const int64_t maximum_extrusion_area_deviation) +{ + const size_t min_path_size = is_closed ? 3 : 2; + if (junctions.size() <= min_path_size) + return; + + // TODO: allow for the first point to be removed in case of simplifying closed Extrusionlines. + + /* ExtrusionLines are treated as (open) polylines, so in case an ExtrusionLine is actually a closed polygon, its + * starting and ending points will be equal (or almost equal). Therefore, the simplification of the ExtrusionLine + * should not touch the first and last points. As a result, start simplifying from point at index 1. + * */ + std::vector new_junctions; + // Starting junction should always exist in the simplified path + new_junctions.emplace_back(junctions.front()); + + /* Initially, previous_previous is always the same as previous because, for open ExtrusionLines the last junction + * cannot be taken into consideration when checking the points at index 1. For closed ExtrusionLines, the first and + * last junctions are anyway the same. + * */ + ExtrusionJunction previous_previous = junctions.front(); + ExtrusionJunction previous = junctions.front(); + + /* When removing a vertex, we check the height of the triangle of the area + being removed from the original polygon by the simplification. However, + when consecutively removing multiple vertices the height of the previously + removed vertices w.r.t. the shortcut path changes. + In order to not recompute the new height value of previously removed + vertices we compute the height of a representative triangle, which covers + the same amount of area as the area being cut off. We use the Shoelace + formula to accumulate the area under the removed segments. This works by + computing the area in a 'fan' where each of the blades of the fan go from + the origin to one of the segments. While removing vertices the area in + this fan accumulates. By subtracting the area of the blade connected to + the short-cutting segment we obtain the total area of the cutoff region. + From this area we compute the height of the representative triangle using + the standard formula for a triangle area: A = .5*b*h + */ + const ExtrusionJunction& initial = junctions.at(1); + int64_t accumulated_area_removed = int64_t(previous.p.x()) * int64_t(initial.p.y()) - int64_t(previous.p.y()) * int64_t(initial.p.x()); // Twice the Shoelace formula for area of polygon per line segment. + + for (size_t point_idx = 1; point_idx < junctions.size() - 1; point_idx++) + { + const ExtrusionJunction& current = junctions[point_idx]; + + // Spill over in case of overflow, unless the [next] vertex will then be equal to [previous]. + const bool spill_over = point_idx + 1 == junctions.size() && new_junctions.size() > 1; + ExtrusionJunction& next = spill_over ? new_junctions[0] : junctions[point_idx + 1]; + + const int64_t removed_area_next = int64_t(current.p.x()) * int64_t(next.p.y()) - int64_t(current.p.y()) * int64_t(next.p.x()); // Twice the Shoelace formula for area of polygon per line segment. + const int64_t negative_area_closing = int64_t(next.p.x()) * int64_t(previous.p.y()) - int64_t(next.p.y()) * int64_t(previous.p.x()); // Area between the origin and the short-cutting segment + accumulated_area_removed += removed_area_next; + + const int64_t length2 = (current - previous).cast().squaredNorm(); + if (length2 < scaled(0.025)) + { + // We're allowed to always delete segments of less than 5 micron. The width in this case doesn't matter that much. + continue; + } + + const int64_t area_removed_so_far = accumulated_area_removed + negative_area_closing; // Close the shortcut area polygon + const int64_t base_length_2 = (next - previous).cast().squaredNorm(); + + if (base_length_2 == 0) // Two line segments form a line back and forth with no area. + { + continue; // Remove the junction (vertex). + } + //We want to check if the height of the triangle formed by previous, current and next vertices is less than allowed_error_distance_squared. + //1/2 L = A [actual area is half of the computed shoelace value] // Shoelace formula is .5*(...) , but we simplify the computation and take out the .5 + //A = 1/2 * b * h [triangle area formula] + //L = b * h [apply above two and take out the 1/2] + //h = L / b [divide by b] + //h^2 = (L / b)^2 [square it] + //h^2 = L^2 / b^2 [factor the divisor] + const auto height_2 = int64_t(double(area_removed_so_far) * double(area_removed_so_far) / double(base_length_2)); + coord_t weighted_average_width; + const int64_t extrusion_area_error = calculateExtrusionAreaDeviationError(previous, current, next, weighted_average_width); + if ((height_2 <= scaled(0.001) //Almost exactly colinear (barring rounding errors). + && Line::distance_to_infinite(current.p, previous.p, next.p) <= scaled(0.001)) // Make sure that height_2 is not small because of cancellation of positive and negative areas + // We shouldn't remove middle junctions of colinear segments if the area changed for the C-P segment is exceeding the maximum allowed + && extrusion_area_error <= maximum_extrusion_area_deviation) + { + // Remove the current junction (vertex). + continue; + } + + if (length2 < smallest_line_segment_squared + && height_2 <= allowed_error_distance_squared) // Removing the junction (vertex) doesn't introduce too much error. + { + const int64_t next_length2 = (current - next).cast().squaredNorm(); + if (next_length2 > 4 * smallest_line_segment_squared) + { + // Special case; The next line is long. If we were to remove this, it could happen that we get quite noticeable artifacts. + // We should instead move this point to a location where both edges are kept and then remove the previous point that we wanted to keep. + // By taking the intersection of these two lines, we get a point that preserves the direction (so it makes the corner a bit more pointy). + // We just need to be sure that the intersection point does not introduce an artifact itself. + Point intersection_point; + bool has_intersection = Line(previous_previous.p, previous.p).intersection_infinite(Line(current.p, next.p), &intersection_point); + if (!has_intersection + || Line::distance_to_infinite_squared(intersection_point, previous.p, current.p) > double(allowed_error_distance_squared) + || (intersection_point - previous.p).cast().squaredNorm() > smallest_line_segment_squared // The intersection point is way too far from the 'previous' + || (intersection_point - next.p).cast().squaredNorm() > smallest_line_segment_squared) // and 'next' points, so it shouldn't replace 'current' + { + // We can't find a better spot for it, but the size of the line is more than 5 micron. + // So the only thing we can do here is leave it in... + } + else + { + // New point seems like a valid one. + const ExtrusionJunction new_to_add = ExtrusionJunction(intersection_point, current.w, current.perimeter_index); + // If there was a previous point added, remove it. + if(!new_junctions.empty()) + { + new_junctions.pop_back(); + previous = previous_previous; + } + + // The junction (vertex) is replaced by the new one. + accumulated_area_removed = removed_area_next; // So that in the next iteration it's the area between the origin, [previous] and [current] + previous_previous = previous; + previous = new_to_add; // Note that "previous" is only updated if we don't remove the junction (vertex). + new_junctions.push_back(new_to_add); + continue; + } + } + else + { + continue; // Remove the junction (vertex). + } + } + // The junction (vertex) isn't removed. + accumulated_area_removed = removed_area_next; // So that in the next iteration it's the area between the origin, [previous] and [current] + previous_previous = previous; + previous = current; // Note that "previous" is only updated if we don't remove the junction (vertex). + new_junctions.push_back(current); + } + + // Ending junction (vertex) should always exist in the simplified path + new_junctions.emplace_back(junctions.back()); + + /* In case this is a closed polygon (instead of a poly-line-segments), the invariant that the first and last points are the same should be enforced. + * Since one of them didn't move, and the other can't have been moved further than the constraints, if originally equal, they can simply be equated. + */ + if ((junctions.front().p - junctions.back().p).cast().squaredNorm() == 0) + { + new_junctions.back().p = junctions.front().p; + } + + junctions = new_junctions; +} + +int64_t ExtrusionLine::calculateExtrusionAreaDeviationError(ExtrusionJunction A, ExtrusionJunction B, ExtrusionJunction C, coord_t& weighted_average_width) +{ + /* + * A B C A C + * --------------- ************** + * | | ------------------------------------------ + * | |--------------------------| B removed | |***************************| + * | | | ---------> | | | + * | |--------------------------| | |***************************| + * | | ------------------------------------------ + * --------------- ^ ************** + * ^ B.w + C.w / 2 ^ + * A.w + B.w / 2 new_width = weighted_average_width + * + * + * ******** denote the total extrusion area deviation error in the consecutive segments as a result of using the + * weighted-average width for the entire extrusion line. + * + * */ + const int64_t ab_length = (B - A).cast().norm(); + const int64_t bc_length = (C - B).cast().norm(); + const coord_t width_diff = std::max(std::abs(B.w - A.w), std::abs(C.w - B.w)); + if (width_diff > 1) + { + // Adjust the width only if there is a difference, or else the rounding errors may produce the wrong + // weighted average value. + const int64_t ab_weight = (A.w + B.w) / 2; + const int64_t bc_weight = (B.w + C.w) / 2; + assert(((ab_length * ab_weight + bc_length * bc_weight) / (C - A).cast().norm()) <= std::numeric_limits::max()); + weighted_average_width = (ab_length * ab_weight + bc_length * bc_weight) / (C - A).cast().norm(); + assert((int64_t(std::abs(ab_weight - weighted_average_width)) * ab_length + int64_t(std::abs(bc_weight - weighted_average_width)) * bc_length) <= double(std::numeric_limits::max())); + return std::abs(ab_weight - weighted_average_width) * ab_length + std::abs(bc_weight - weighted_average_width) * bc_length; + } + else + { + // If the width difference is very small, then select the width of the segment that is longer + weighted_average_width = ab_length > bc_length ? A.w : B.w; + assert((int64_t(width_diff) * int64_t(bc_length)) <= std::numeric_limits::max()); + assert((int64_t(width_diff) * int64_t(ab_length)) <= std::numeric_limits::max()); + return ab_length > bc_length ? width_diff * bc_length : width_diff * ab_length; + } +} + +bool ExtrusionLine::is_contour() const +{ + if (!this->is_closed) + return false; + + Polygon poly; + poly.points.reserve(this->junctions.size()); + for (const ExtrusionJunction &junction : this->junctions) + poly.points.emplace_back(junction.p); + + // Arachne produces contour with clockwise orientation and holes with counterclockwise orientation. + return poly.is_clockwise(); +} + +double ExtrusionLine::area() const +{ + assert(this->is_closed); + double a = 0.; + if (this->junctions.size() >= 3) { + Vec2d p1 = this->junctions.back().p.cast(); + for (const ExtrusionJunction &junction : this->junctions) { + Vec2d p2 = junction.p.cast(); + a += cross2(p1, p2); + p1 = p2; + } + } + return 0.5 * a; +} + +} // namespace Slic3r::Arachne + +namespace Slic3r { +void extrusion_paths_append(ExtrusionPaths &dst, const ClipperLib_Z::Paths &extrusion_paths, const ExtrusionRole role, const Flow &flow) +{ + for (const ClipperLib_Z::Path &extrusion_path : extrusion_paths) { + ThickPolyline thick_polyline = Arachne::to_thick_polyline(extrusion_path); + Slic3r::append(dst, thick_polyline_to_extrusion_paths(thick_polyline, role, flow, scaled(0.05), SCALED_EPSILON)); + } +} + +void extrusion_paths_append(ExtrusionPaths &dst, const Arachne::ExtrusionLine &extrusion, const ExtrusionRole role, const Flow &flow) +{ + ThickPolyline thick_polyline = Arachne::to_thick_polyline(extrusion); + Slic3r::append(dst, thick_polyline_to_extrusion_paths(thick_polyline, role, flow, scaled(0.05), SCALED_EPSILON)); +} +} // namespace Slic3r \ No newline at end of file diff --git a/src/libslic3r/Arachne/utils/ExtrusionLine.hpp b/src/libslic3r/Arachne/utils/ExtrusionLine.hpp new file mode 100644 index 0000000000..7ac1a3c4c8 --- /dev/null +++ b/src/libslic3r/Arachne/utils/ExtrusionLine.hpp @@ -0,0 +1,307 @@ +//Copyright (c) 2020 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + + +#ifndef UTILS_EXTRUSION_LINE_H +#define UTILS_EXTRUSION_LINE_H + +#include "ExtrusionJunction.hpp" +#include "../../Polyline.hpp" +#include "../../Polygon.hpp" +#include "../../BoundingBox.hpp" +#include "../../ExtrusionEntity.hpp" +#include "../../Flow.hpp" +#include "../../../clipper/clipper_z.hpp" + +namespace Slic3r { +class ThickPolyline; +} + +namespace Slic3r::Arachne +{ + +/*! + * Represents a polyline (not just a line) that is to be extruded with variable + * line width. + * + * This polyline is a sequence of \ref ExtrusionJunction, with a bit of metadata + * about which inset it represents. + */ +struct ExtrusionLine +{ + /*! + * Which inset this path represents, counted from the outside inwards. + * + * The outer wall has index 0. + */ + size_t inset_idx; + + /*! + * If a thin piece needs to be printed with an odd number of walls (e.g. 5 + * walls) then there will be one wall in the middle that is not a loop. This + * field indicates whether this path is such a line through the middle, that + * has no companion line going back on the other side and is not a closed + * loop. + */ + bool is_odd; + + /*! + * Whether this is a closed polygonal path + */ + bool is_closed; + + /*! + * Gets the number of vertices in this polygon. + * \return The number of vertices in this polygon. + */ + size_t size() const { return junctions.size(); } + + /*! + * Whether there are no junctions. + */ + bool empty() const { return junctions.empty(); } + + /*! + * The list of vertices along which this path runs. + * + * Each junction has a width, making this path a variable-width path. + */ + std::vector junctions; + + ExtrusionLine(const size_t inset_idx, const bool is_odd); + ExtrusionLine() : inset_idx(-1), is_odd(true), is_closed(false) {} + ExtrusionLine(const ExtrusionLine &other) : inset_idx(other.inset_idx), is_odd(other.is_odd), is_closed(other.is_closed), junctions(other.junctions) {} + + ExtrusionLine &operator=(ExtrusionLine &&other) + { + junctions = std::move(other.junctions); + inset_idx = other.inset_idx; + is_odd = other.is_odd; + is_closed = other.is_closed; + return *this; + } + + ExtrusionLine &operator=(const ExtrusionLine &other) + { + junctions = other.junctions; + inset_idx = other.inset_idx; + is_odd = other.is_odd; + is_closed = other.is_closed; + return *this; + } + + std::vector::const_iterator begin() const { return junctions.begin(); } + std::vector::const_iterator end() const { return junctions.end(); } + std::vector::const_reverse_iterator rbegin() const { return junctions.rbegin(); } + std::vector::const_reverse_iterator rend() const { return junctions.rend(); } + std::vector::const_reference front() const { return junctions.front(); } + std::vector::const_reference back() const { return junctions.back(); } + const ExtrusionJunction &operator[](unsigned int index) const { return junctions[index]; } + ExtrusionJunction &operator[](unsigned int index) { return junctions[index]; } + std::vector::iterator begin() { return junctions.begin(); } + std::vector::iterator end() { return junctions.end(); } + std::vector::reference front() { return junctions.front(); } + std::vector::reference back() { return junctions.back(); } + + template void emplace_back(Args &&...args) { junctions.emplace_back(args...); } + void remove(unsigned int index) { junctions.erase(junctions.begin() + index); } + void insert(size_t index, const ExtrusionJunction &p) { junctions.insert(junctions.begin() + index, p); } + + template + std::vector::iterator insert(std::vector::const_iterator pos, iterator first, iterator last) + { + return junctions.insert(pos, first, last); + } + + void clear() { junctions.clear(); } + void reverse() { std::reverse(junctions.begin(), junctions.end()); } + + /*! + * Sum the total length of this path. + */ + int64_t getLength() const; + int64_t polylineLength() const { return getLength(); } + + /*! + * Put all junction locations into a polygon object. + * + * When this path is not closed the returned Polygon should be handled as a polyline, rather than a polygon. + */ + Polygon toPolygon() const + { + Polygon ret; + for (const ExtrusionJunction &j : junctions) + ret.points.emplace_back(j.p); + + return ret; + } + + /*! + * Get the minimal width of this path + */ + coord_t getMinimalWidth() const; + + /*! + * Removes vertices of the ExtrusionLines to make sure that they are not too high + * resolution. + * + * This removes junctions which are connected to line segments that are shorter + * than the `smallest_line_segment`, unless that would introduce a deviation + * in the contour of more than `allowed_error_distance`. + * + * Criteria: + * 1. Never remove a junction if either of the connected segments is larger than \p smallest_line_segment + * 2. Never remove a junction if the distance between that junction and the final resulting polygon would be higher + * than \p allowed_error_distance + * 3. The direction of segments longer than \p smallest_line_segment always + * remains unaltered (but their end points may change if it is connected to + * a small segment) + * 4. Never remove a junction if it has a distinctively different width than the next junction, as this can + * introduce unwanted irregularities on the wall widths. + * + * Simplify uses a heuristic and doesn't necessarily remove all removable + * vertices under the above criteria, but simplify may never violate these + * criteria. Unless the segments or the distance is smaller than the + * rounding error of 5 micron. + * + * Vertices which introduce an error of less than 5 microns are removed + * anyway, even if the segments are longer than the smallest line segment. + * This makes sure that (practically) co-linear line segments are joined into + * a single line segment. + * \param smallest_line_segment Maximal length of removed line segments. + * \param allowed_error_distance If removing a vertex introduces a deviation + * from the original path that is more than this distance, the vertex may + * not be removed. + * \param maximum_extrusion_area_deviation The maximum extrusion area deviation allowed when removing intermediate + * junctions from a straight ExtrusionLine + */ + void simplify(int64_t smallest_line_segment_squared, int64_t allowed_error_distance_squared, int64_t maximum_extrusion_area_deviation); + + /*! + * Computes and returns the total area error (in μm²) of the AB and BC segments of an ABC straight ExtrusionLine + * when the junction B with a width B.w is removed from the ExtrusionLine. The area changes due to the fact that the + * new simplified line AC has a uniform width which equals to the weighted average of the width of the subsegments + * (based on their length). + * + * \param A Start point of the 3-point-straight line + * \param B Intermediate point of the 3-point-straight line + * \param C End point of the 3-point-straight line + * \param weighted_average_width The weighted average of the widths of the two colinear extrusion segments + * */ + static int64_t calculateExtrusionAreaDeviationError(ExtrusionJunction A, ExtrusionJunction B, ExtrusionJunction C, coord_t& weighted_average_width); + + bool is_contour() const; + + double area() const; +}; + +static inline Slic3r::ThickPolyline to_thick_polyline(const Arachne::ExtrusionLine &line_junctions) +{ + assert(line_junctions.size() >= 2); + Slic3r::ThickPolyline out; + out.points.emplace_back(line_junctions.front().p); + out.width.emplace_back(line_junctions.front().w); + out.points.emplace_back(line_junctions[1].p); + out.width.emplace_back(line_junctions[1].w); + + auto it_prev = line_junctions.begin() + 1; + for (auto it = line_junctions.begin() + 2; it != line_junctions.end(); ++it) { + out.points.emplace_back(it->p); + out.width.emplace_back(it_prev->w); + out.width.emplace_back(it->w); + it_prev = it; + } + + return out; +} + +static inline Slic3r::ThickPolyline to_thick_polyline(const ClipperLib_Z::Path &path) +{ + assert(path.size() >= 2); + Slic3r::ThickPolyline out; + out.points.emplace_back(path.front().x(), path.front().y()); + out.width.emplace_back(path.front().z()); + out.points.emplace_back(path[1].x(), path[1].y()); + out.width.emplace_back(path[1].z()); + + auto it_prev = path.begin() + 1; + for (auto it = path.begin() + 2; it != path.end(); ++it) { + out.points.emplace_back(it->x(), it->y()); + out.width.emplace_back(it_prev->z()); + out.width.emplace_back(it->z()); + it_prev = it; + } + + return out; +} + +static inline Polygon to_polygon(const ExtrusionLine &line) +{ + Polygon out; + assert(line.junctions.size() >= 3); + assert(line.junctions.front().p == line.junctions.back().p); + out.points.reserve(line.junctions.size() - 1); + for (auto it = line.junctions.begin(); it != line.junctions.end() - 1; ++it) + out.points.emplace_back(it->p); + return out; +} + +#if 0 +static BoundingBox get_extents(const ExtrusionLine &extrusion_line) +{ + BoundingBox bbox; + for (const ExtrusionJunction &junction : extrusion_line.junctions) + bbox.merge(junction.p); + return bbox; +} + +static BoundingBox get_extents(const std::vector &extrusion_lines) +{ + BoundingBox bbox; + for (const ExtrusionLine &extrusion_line : extrusion_lines) + bbox.merge(get_extents(extrusion_line)); + return bbox; +} + +static BoundingBox get_extents(const std::vector &extrusion_lines) +{ + BoundingBox bbox; + for (const ExtrusionLine *extrusion_line : extrusion_lines) { + assert(extrusion_line != nullptr); + bbox.merge(get_extents(*extrusion_line)); + } + return bbox; +} + +static Points to_points(const ExtrusionLine &extrusion_line) +{ + Points points; + points.reserve(extrusion_line.junctions.size()); + for (const ExtrusionJunction &junction : extrusion_line.junctions) + points.emplace_back(junction.p); + return points; +} + +static std::vector to_points(const std::vector &extrusion_lines) +{ + std::vector points; + for (const ExtrusionLine *extrusion_line : extrusion_lines) { + assert(extrusion_line != nullptr); + points.emplace_back(to_points(*extrusion_line)); + } + return points; +} +#endif + +using VariableWidthLines = std::vector; // +#include + +namespace Slic3r::Arachne +{ + +template +class HalfEdgeNode; + + +template +class HalfEdge +{ + using edge_t = derived_edge_t; + using node_t = derived_node_t; +public: + edge_data_t data; + edge_t* twin = nullptr; + edge_t* next = nullptr; + edge_t* prev = nullptr; + node_t* from = nullptr; + node_t* to = nullptr; + HalfEdge(edge_data_t data) + : data(data) + {} + bool operator==(const edge_t& other) + { + return this == &other; + } +}; + +} // namespace Slic3r::Arachne +#endif // UTILS_HALF_EDGE_H diff --git a/src/libslic3r/Arachne/utils/HalfEdgeGraph.hpp b/src/libslic3r/Arachne/utils/HalfEdgeGraph.hpp new file mode 100644 index 0000000000..99efff6a07 --- /dev/null +++ b/src/libslic3r/Arachne/utils/HalfEdgeGraph.hpp @@ -0,0 +1,29 @@ +//Copyright (c) 2020 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef UTILS_HALF_EDGE_GRAPH_H +#define UTILS_HALF_EDGE_GRAPH_H + + +#include +#include + + + +#include "HalfEdge.hpp" +#include "HalfEdgeNode.hpp" + +namespace Slic3r::Arachne +{ +template // types of data contained in nodes and edges +class HalfEdgeGraph +{ +public: + using edge_t = derived_edge_t; + using node_t = derived_node_t; + std::list edges; + std::list nodes; +}; + +} // namespace Slic3r::Arachne +#endif // UTILS_HALF_EDGE_GRAPH_H diff --git a/src/libslic3r/Arachne/utils/HalfEdgeNode.hpp b/src/libslic3r/Arachne/utils/HalfEdgeNode.hpp new file mode 100644 index 0000000000..ad474489ce --- /dev/null +++ b/src/libslic3r/Arachne/utils/HalfEdgeNode.hpp @@ -0,0 +1,38 @@ +//Copyright (c) 2020 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef UTILS_HALF_EDGE_NODE_H +#define UTILS_HALF_EDGE_NODE_H + +#include + +#include "../../Point.hpp" + +namespace Slic3r::Arachne +{ + +template +class HalfEdge; + +template +class HalfEdgeNode +{ + using edge_t = derived_edge_t; + using node_t = derived_node_t; +public: + node_data_t data; + Point p; + edge_t* incident_edge = nullptr; + HalfEdgeNode(node_data_t data, Point p) + : data(data) + , p(p) + {} + + bool operator==(const node_t& other) + { + return this == &other; + } +}; + +} // namespace Slic3r::Arachne +#endif // UTILS_HALF_EDGE_NODE_H diff --git a/src/libslic3r/Arachne/utils/PolygonsPointIndex.hpp b/src/libslic3r/Arachne/utils/PolygonsPointIndex.hpp new file mode 100644 index 0000000000..125b3ef926 --- /dev/null +++ b/src/libslic3r/Arachne/utils/PolygonsPointIndex.hpp @@ -0,0 +1,180 @@ +//Copyright (c) 2018 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef UTILS_POLYGONS_POINT_INDEX_H +#define UTILS_POLYGONS_POINT_INDEX_H + +#include + +#include "../../Point.hpp" +#include "../../Polygon.hpp" + + +namespace Slic3r::Arachne +{ + +// Identity function, used to be able to make templated algorithms where the input is sometimes points, sometimes things that contain or can be converted to points. +inline const Point &make_point(const Point &p) { return p; } + +/*! + * A class for iterating over the points in one of the polygons in a \ref Polygons object + */ +template +class PathsPointIndex +{ +public: + /*! + * The polygons into which this index is indexing. + */ + const Paths* polygons; // (pointer to const polygons) + + unsigned int poly_idx; //!< The index of the polygon in \ref PolygonsPointIndex::polygons + + unsigned int point_idx; //!< The index of the point in the polygon in \ref PolygonsPointIndex::polygons + + /*! + * Constructs an empty point index to no polygon. + * + * This is used as a placeholder for when there is a zero-construction + * needed. Since the `polygons` field is const you can't ever make this + * initialisation useful. + */ + PathsPointIndex() : polygons(nullptr), poly_idx(0), point_idx(0) {} + + /*! + * Constructs a new point index to a vertex of a polygon. + * \param polygons The Polygons instance to which this index points. + * \param poly_idx The index of the sub-polygon to point to. + * \param point_idx The index of the vertex in the sub-polygon. + */ + PathsPointIndex(const Paths *polygons, unsigned int poly_idx, unsigned int point_idx) : polygons(polygons), poly_idx(poly_idx), point_idx(point_idx) {} + + /*! + * Copy constructor to copy these indices. + */ + PathsPointIndex(const PathsPointIndex& original) = default; + + Point p() const + { + if (!polygons) + return {0, 0}; + + return make_point((*polygons)[poly_idx][point_idx]); + } + + /*! + * \brief Returns whether this point is initialised. + */ + bool initialized() const { return polygons; } + + /*! + * Get the polygon to which this PolygonsPointIndex refers + */ + const Polygon &getPolygon() const { return (*polygons)[poly_idx]; } + + /*! + * Test whether two iterators refer to the same polygon in the same polygon list. + * + * \param other The PolygonsPointIndex to test for equality + * \return Wether the right argument refers to the same polygon in the same ListPolygon as the left argument. + */ + bool operator==(const PathsPointIndex &other) const + { + return polygons == other.polygons && poly_idx == other.poly_idx && point_idx == other.point_idx; + } + bool operator!=(const PathsPointIndex &other) const + { + return !(*this == other); + } + bool operator<(const PathsPointIndex &other) const + { + return this->p() < other.p(); + } + PathsPointIndex &operator=(const PathsPointIndex &other) + { + polygons = other.polygons; + poly_idx = other.poly_idx; + point_idx = other.point_idx; + return *this; + } + //! move the iterator forward (and wrap around at the end) + PathsPointIndex &operator++() + { + point_idx = (point_idx + 1) % (*polygons)[poly_idx].size(); + return *this; + } + //! move the iterator backward (and wrap around at the beginning) + PathsPointIndex &operator--() + { + if (point_idx == 0) + point_idx = (*polygons)[poly_idx].size(); + point_idx--; + return *this; + } + //! move the iterator forward (and wrap around at the end) + PathsPointIndex next() const + { + PathsPointIndex ret(*this); + ++ret; + return ret; + } + //! move the iterator backward (and wrap around at the beginning) + PathsPointIndex prev() const + { + PathsPointIndex ret(*this); + --ret; + return ret; + } +}; + +using PolygonsPointIndex = PathsPointIndex; + +/*! + * Locator to extract a line segment out of a \ref PolygonsPointIndex + */ +struct PolygonsPointIndexSegmentLocator +{ + std::pair operator()(const PolygonsPointIndex &val) const + { + const Polygon &poly = (*val.polygons)[val.poly_idx]; + Point start = poly[val.point_idx]; + unsigned int next_point_idx = (val.point_idx + 1) % poly.size(); + Point end = poly[next_point_idx]; + return std::pair(start, end); + } +}; + +/*! + * Locator of a \ref PolygonsPointIndex + */ +template +struct PathsPointIndexLocator +{ + Point operator()(const PathsPointIndex& val) const + { + return make_point(val.p()); + } +}; + +using PolygonsPointIndexLocator = PathsPointIndexLocator; + +}//namespace Slic3r::Arachne + +namespace std +{ +/*! + * Hash function for \ref PolygonsPointIndex + */ +template <> +struct hash +{ + size_t operator()(const Slic3r::Arachne::PolygonsPointIndex& lpi) const + { + return Slic3r::PointHash{}(lpi.p()); + } +}; +}//namespace std + + + +#endif//UTILS_POLYGONS_POINT_INDEX_H diff --git a/src/libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp b/src/libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp new file mode 100644 index 0000000000..6eff3d62ee --- /dev/null +++ b/src/libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp @@ -0,0 +1,31 @@ +//Copyright (c) 2020 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef UTILS_POLYGONS_SEGMENT_INDEX_H +#define UTILS_POLYGONS_SEGMENT_INDEX_H + +#include + +#include "PolygonsPointIndex.hpp" + +namespace Slic3r::Arachne +{ + +/*! + * A class for iterating over the points in one of the polygons in a \ref Polygons object + */ +class PolygonsSegmentIndex : public PolygonsPointIndex +{ +public: + PolygonsSegmentIndex() : PolygonsPointIndex(){}; + PolygonsSegmentIndex(const Polygons *polygons, unsigned int poly_idx, unsigned int point_idx) : PolygonsPointIndex(polygons, poly_idx, point_idx){}; + + Point from() const { return PolygonsPointIndex::p(); } + + Point to() const { return PolygonsSegmentIndex::next().p(); } +}; + +} // namespace Slic3r::Arachne + + +#endif//UTILS_POLYGONS_SEGMENT_INDEX_H diff --git a/src/libslic3r/Arachne/utils/PolylineStitcher.cpp b/src/libslic3r/Arachne/utils/PolylineStitcher.cpp new file mode 100644 index 0000000000..89ec929540 --- /dev/null +++ b/src/libslic3r/Arachne/utils/PolylineStitcher.cpp @@ -0,0 +1,42 @@ +//Copyright (c) 2022 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "PolylineStitcher.hpp" +#include "ExtrusionLine.hpp" + +namespace Slic3r::Arachne { + +template<> bool PolylineStitcher::canReverse(const PathsPointIndex &ppi) +{ + if ((*ppi.polygons)[ppi.poly_idx].is_odd) + return true; + else + return false; +} + +template<> bool PolylineStitcher::canReverse(const PathsPointIndex &) +{ + return true; +} + +template<> bool PolylineStitcher::canConnect(const ExtrusionLine &a, const ExtrusionLine &b) +{ + return a.is_odd == b.is_odd; +} + +template<> bool PolylineStitcher::canConnect(const Polygon &, const Polygon &) +{ + return true; +} + +template<> bool PolylineStitcher::isOdd(const ExtrusionLine &line) +{ + return line.is_odd; +} + +template<> bool PolylineStitcher::isOdd(const Polygon &) +{ + return false; +} + +} // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/utils/PolylineStitcher.hpp b/src/libslic3r/Arachne/utils/PolylineStitcher.hpp new file mode 100644 index 0000000000..2ab770a3ec --- /dev/null +++ b/src/libslic3r/Arachne/utils/PolylineStitcher.hpp @@ -0,0 +1,234 @@ +//Copyright (c) 2022 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef UTILS_POLYLINE_STITCHER_H +#define UTILS_POLYLINE_STITCHER_H + +#include "SparsePointGrid.hpp" +#include "PolygonsPointIndex.hpp" +#include "../../Polygon.hpp" +#include +#include + +namespace Slic3r::Arachne +{ + +/*! + * Class for stitching polylines into longer polylines or into polygons + */ +template +class PolylineStitcher +{ +public: + /*! + * Stitch together the separate \p lines into \p result_lines and if they + * can be closed into \p result_polygons. + * + * Only introduce new segments shorter than \p max_stitch_distance, and + * larger than \p snap_distance but always try to take the shortest + * connection possible. + * + * Only stitch polylines into closed polygons if they are larger than 3 * + * \p max_stitch_distance, in order to prevent small segments to + * accidentally get closed into a polygon. + * + * \warning Tiny polylines (smaller than 3 * max_stitch_distance) will not + * be closed into polygons. + * + * \note Resulting polylines and polygons are added onto the existing + * containers, so you can directly output onto a polygons container with + * existing polygons in it. However, you shouldn't call this function with + * the same parameter in \p lines as \p result_lines, because that would + * duplicate (some of) the polylines. + * \param lines The lines to stitch together. + * \param result_lines[out] The stitched parts that are not closed polygons + * will be stored in here. + * \param result_polygons[out] The stitched parts that were closed as + * polygons will be stored in here. + * \param max_stitch_distance The maximum distance that will be bridged to + * connect two lines. + * \param snap_distance Points closer than this distance are considered to + * be the same point. + */ + static void stitch(const Paths& lines, Paths& result_lines, Paths& result_polygons, coord_t max_stitch_distance = scaled(0.1), coord_t snap_distance = scaled(0.01)) + { + if (lines.empty()) + return; + + SparsePointGrid, PathsPointIndexLocator> grid(max_stitch_distance, lines.size() * 2); + + // populate grid + for (size_t line_idx = 0; line_idx < lines.size(); line_idx++) + { + const auto line = lines[line_idx]; + grid.insert(PathsPointIndex(&lines, line_idx, 0)); + grid.insert(PathsPointIndex(&lines, line_idx, line.size() - 1)); + } + + std::vector processed(lines.size(), false); + + for (size_t line_idx = 0; line_idx < lines.size(); line_idx++) + { + if (processed[line_idx]) + { + continue; + } + processed[line_idx] = true; + const auto line = lines[line_idx]; + bool should_close = isOdd(line); + + Path chain = line; + bool closest_is_closing_polygon = false; + for (bool go_in_reverse_direction : { false, true }) // first go in the unreversed direction, to try to prevent the chain.reverse() operation. + { // NOTE: Implementation only works for this order; we currently only re-reverse the chain when it's closed. + if (go_in_reverse_direction) + { // try extending chain in the other direction + chain.reverse(); + } + int64_t chain_length = chain.polylineLength(); + + while (true) + { + Point from = make_point(chain.back()); + + PathsPointIndex closest; + coord_t closest_distance = std::numeric_limits::max(); + grid.processNearby(from, max_stitch_distance, + std::function&)> ( + [from, &chain, &closest, &closest_is_closing_polygon, &closest_distance, &processed, &chain_length, go_in_reverse_direction, max_stitch_distance, snap_distance, should_close] + (const PathsPointIndex& nearby)->bool + { + bool is_closing_segment = false; + coord_t dist = (nearby.p().template cast() - from.template cast()).norm(); + if (dist > max_stitch_distance) + { + return true; // keep looking + } + if ((nearby.p().template cast() - make_point(chain.front()).template cast()).squaredNorm() < snap_distance * snap_distance) + { + if (chain_length + dist < 3 * max_stitch_distance // prevent closing of small poly, cause it might be able to continue making a larger polyline + || chain.size() <= 2) // don't make 2 vert polygons + { + return true; // look for a better next line + } + is_closing_segment = true; + if (!should_close) + { + dist += scaled(0.01); // prefer continuing polyline over closing a polygon; avoids closed zigzags from being printed separately + // continue to see if closing segment is also the closest + // there might be a segment smaller than [max_stitch_distance] which closes the polygon better + } + else + { + dist -= scaled(0.01); //Prefer closing the polygon if it's 100% even lines. Used to create closed contours. + //Continue to see if closing segment is also the closest. + } + } + else if (processed[nearby.poly_idx]) + { // it was already moved to output + return true; // keep looking for a connection + } + bool nearby_would_be_reversed = nearby.point_idx != 0; + nearby_would_be_reversed = nearby_would_be_reversed != go_in_reverse_direction; // flip nearby_would_be_reversed when searching in the reverse direction + if (!canReverse(nearby) && nearby_would_be_reversed) + { // connecting the segment would reverse the polygon direction + return true; // keep looking for a connection + } + if (!canConnect(chain, (*nearby.polygons)[nearby.poly_idx])) + { + return true; // keep looking for a connection + } + if (dist < closest_distance) + { + closest_distance = dist; + closest = nearby; + closest_is_closing_polygon = is_closing_segment; + } + if (dist < snap_distance) + { // we have found a good enough next line + return false; // stop looking for alternatives + } + return true; // keep processing elements + }) + ); + + if (!closest.initialized() // we couldn't find any next line + || closest_is_closing_polygon // we closed the polygon + ) + { + break; + } + + coord_t segment_dist = (make_point(chain.back()).template cast() - closest.p().template cast()).norm(); + assert(segment_dist <= max_stitch_distance + scaled(0.01)); + const size_t old_size = chain.size(); + if (closest.point_idx == 0) + { + auto start_pos = (*closest.polygons)[closest.poly_idx].begin(); + if (segment_dist < snap_distance) + { + ++start_pos; + } + chain.insert(chain.end(), start_pos, (*closest.polygons)[closest.poly_idx].end()); + } + else + { + auto start_pos = (*closest.polygons)[closest.poly_idx].rbegin(); + if (segment_dist < snap_distance) + { + ++start_pos; + } + chain.insert(chain.end(), start_pos, (*closest.polygons)[closest.poly_idx].rend()); + } + for(size_t i = old_size; i < chain.size(); ++i) //Update chain length. + { + chain_length += (make_point(chain[i]).template cast() - make_point(chain[i - 1]).template cast()).norm(); + } + should_close = should_close & !isOdd((*closest.polygons)[closest.poly_idx]); //If we connect an even to an odd line, we should no longer try to close it. + assert( ! processed[closest.poly_idx]); + processed[closest.poly_idx] = true; + } + if (closest_is_closing_polygon) + { + if (go_in_reverse_direction) + { // re-reverse chain to retain original direction + // NOTE: not sure if this code could ever be reached, since if a polygon can be closed that should be already possible in the forward direction + chain.reverse(); + } + + break; // don't consider reverse direction + } + } + if (closest_is_closing_polygon) + { + result_polygons.emplace_back(chain); + } + else + { + PathsPointIndex ppi_here(&lines, line_idx, 0); + if ( ! canReverse(ppi_here)) + { // Since closest_is_closing_polygon is false we went through the second iterations of the for-loop, where go_in_reverse_direction is true + // the polyline isn't allowed to be reversed, so we re-reverse it. + chain.reverse(); + } + result_lines.emplace_back(chain); + } + } + } + + /*! + * Whether a polyline is allowed to be reversed. (Not true for wall polylines which are not odd) + */ + static bool canReverse(const PathsPointIndex &polyline); + + /*! + * Whether two paths are allowed to be connected. + * (Not true for an odd and an even wall.) + */ + static bool canConnect(const Path &a, const Path &b); + + static bool isOdd(const Path &line); +}; + +} // namespace Slic3r::Arachne +#endif // UTILS_POLYLINE_STITCHER_H \ No newline at end of file diff --git a/src/libslic3r/Arachne/utils/SparseGrid.hpp b/src/libslic3r/Arachne/utils/SparseGrid.hpp new file mode 100644 index 0000000000..be461d4241 --- /dev/null +++ b/src/libslic3r/Arachne/utils/SparseGrid.hpp @@ -0,0 +1,133 @@ +//Copyright (c) 2016 Scott Lenser +//Copyright (c) 2018 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef UTILS_SPARSE_GRID_H +#define UTILS_SPARSE_GRID_H + +#include +#include +#include +#include + +#include "../../Point.hpp" +#include "SquareGrid.hpp" + +namespace Slic3r::Arachne { + +/*! \brief Sparse grid which can locate spatially nearby elements efficiently. + * + * \note This is an abstract template class which doesn't have any functions to insert elements. + * \see SparsePointGrid + * + * \tparam ElemT The element type to store. + */ +template class SparseGrid : public SquareGrid +{ +public: + using Elem = ElemT; + + using GridPoint = SquareGrid::GridPoint; + using grid_coord_t = SquareGrid::grid_coord_t; + using GridMap = std::unordered_multimap; + + using iterator = typename GridMap::iterator; + using const_iterator = typename GridMap::const_iterator; + + /*! \brief Constructs a sparse grid with the specified cell size. + * + * \param[in] cell_size The size to use for a cell (square) in the grid. + * Typical values would be around 0.5-2x of expected query radius. + * \param[in] elem_reserve Number of elements to research space for. + * \param[in] max_load_factor Maximum average load factor before rehashing. + */ + SparseGrid(coord_t cell_size, size_t elem_reserve=0U, float max_load_factor=1.0f); + + iterator begin() { return m_grid.begin(); } + iterator end() { return m_grid.end(); } + const_iterator begin() const { return m_grid.begin(); } + const_iterator end() const { return m_grid.end(); } + + /*! \brief Returns all data within radius of query_pt. + * + * Finds all elements with location within radius of \p query_pt. May + * return additional elements that are beyond radius. + * + * Average running time is a*(1 + 2 * radius / cell_size)**2 + + * b*cnt where a and b are proportionality constance and cnt is + * the number of returned items. The search will return items in + * an area of (2*radius + cell_size)**2 on average. The max range + * of an item from the query_point is radius + cell_size. + * + * \param[in] query_pt The point to search around. + * \param[in] radius The search radius. + * \return Vector of elements found + */ + std::vector getNearby(const Point &query_pt, coord_t radius) const; + + /*! \brief Process elements from cells that might contain sought after points. + * + * Processes elements from cell that might have elements within \p + * radius of \p query_pt. Processes all elements that are within + * radius of query_pt. May process elements that are up to radius + + * cell_size from query_pt. + * + * \param[in] query_pt The point to search around. + * \param[in] radius The search radius. + * \param[in] process_func Processes each element. process_func(elem) is + * called for each element in the cell. Processing stops if function returns false. + * \return Whether we need to continue processing after this function + */ + bool processNearby(const Point &query_pt, coord_t radius, const std::function &process_func) const; + +protected: + /*! \brief Process elements from the cell indicated by \p grid_pt. + * + * \param[in] grid_pt The grid coordinates of the cell. + * \param[in] process_func Processes each element. process_func(elem) is + * called for each element in the cell. Processing stops if function returns false. + * \return Whether we need to continue processing a next cell. + */ + bool processFromCell(const GridPoint &grid_pt, const std::function &process_func) const; + + /*! \brief Map from grid locations (GridPoint) to elements (Elem). */ + GridMap m_grid; +}; + +template SparseGrid::SparseGrid(coord_t cell_size, size_t elem_reserve, float max_load_factor) : SquareGrid(cell_size) +{ + // Must be before the reserve call. + m_grid.max_load_factor(max_load_factor); + if (elem_reserve != 0U) + m_grid.reserve(elem_reserve); +} + +template bool SparseGrid::processFromCell(const GridPoint &grid_pt, const std::function &process_func) const +{ + auto grid_range = m_grid.equal_range(grid_pt); + for (auto iter = grid_range.first; iter != grid_range.second; ++iter) + if (!process_func(iter->second)) + return false; + return true; +} + +template +bool SparseGrid::processNearby(const Point &query_pt, coord_t radius, const std::function &process_func) const +{ + return SquareGrid::processNearby(query_pt, radius, [&process_func, this](const GridPoint &grid_pt) { return processFromCell(grid_pt, process_func); }); +} + +template std::vector::Elem> SparseGrid::getNearby(const Point &query_pt, coord_t radius) const +{ + std::vector ret; + const std::function process_func = [&ret](const Elem &elem) { + ret.push_back(elem); + return true; + }; + processNearby(query_pt, radius, process_func); + return ret; +} + +} // namespace Slic3r::Arachne + +#endif // UTILS_SPARSE_GRID_H diff --git a/src/libslic3r/Arachne/utils/SparseLineGrid.hpp b/src/libslic3r/Arachne/utils/SparseLineGrid.hpp new file mode 100644 index 0000000000..a9b5368697 --- /dev/null +++ b/src/libslic3r/Arachne/utils/SparseLineGrid.hpp @@ -0,0 +1,77 @@ +//Copyright (c) 2018 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + + +#ifndef UTILS_SPARSE_LINE_GRID_H +#define UTILS_SPARSE_LINE_GRID_H + +#include +#include +#include +#include + +#include "SparseGrid.hpp" + +namespace Slic3r::Arachne { + +/*! \brief Sparse grid which can locate spatially nearby elements efficiently. + * + * \tparam ElemT The element type to store. + * \tparam Locator The functor to get the start and end locations from ElemT. + * must have: std::pair operator()(const ElemT &elem) const + * which returns the location associated with val. + */ +template class SparseLineGrid : public SparseGrid +{ +public: + using Elem = ElemT; + + /*! \brief Constructs a sparse grid with the specified cell size. + * + * \param[in] cell_size The size to use for a cell (square) in the grid. + * Typical values would be around 0.5-2x of expected query radius. + * \param[in] elem_reserve Number of elements to research space for. + * \param[in] max_load_factor Maximum average load factor before rehashing. + */ + SparseLineGrid(coord_t cell_size, size_t elem_reserve = 0U, float max_load_factor = 1.0f); + + /*! \brief Inserts elem into the sparse grid. + * + * \param[in] elem The element to be inserted. + */ + void insert(const Elem &elem); + +protected: + using GridPoint = typename SparseGrid::GridPoint; + + /*! \brief Accessor for getting locations from elements. */ + Locator m_locator; +}; + +template +SparseLineGrid::SparseLineGrid(coord_t cell_size, size_t elem_reserve, float max_load_factor) + : SparseGrid(cell_size, elem_reserve, max_load_factor) {} + +template void SparseLineGrid::insert(const Elem &elem) +{ + const std::pair line = m_locator(elem); + using GridMap = std::unordered_multimap; + // below is a workaround for the fact that lambda functions cannot access private or protected members + // first we define a lambda which works on any GridMap and then we bind it to the actual protected GridMap of the parent class + std::function process_cell_func_ = [&elem](GridMap *m_grid, const GridPoint grid_loc) { + m_grid->emplace(grid_loc, elem); + return true; + }; + using namespace std::placeholders; // for _1, _2, _3... + GridMap *m_grid = &(this->m_grid); + std::function process_cell_func(std::bind(process_cell_func_, m_grid, _1)); + + SparseGrid::processLineCells(line, process_cell_func); +} + +#undef SGI_TEMPLATE +#undef SGI_THIS + +} // namespace Slic3r::Arachne + +#endif // UTILS_SPARSE_LINE_GRID_H diff --git a/src/libslic3r/Arachne/utils/SparsePointGrid.hpp b/src/libslic3r/Arachne/utils/SparsePointGrid.hpp new file mode 100644 index 0000000000..31c1965357 --- /dev/null +++ b/src/libslic3r/Arachne/utils/SparsePointGrid.hpp @@ -0,0 +1,90 @@ +// Copyright (c) 2016 Scott Lenser +// Copyright (c) 2020 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef UTILS_SPARSE_POINT_GRID_H +#define UTILS_SPARSE_POINT_GRID_H + +#include +#include +#include + +#include "SparseGrid.hpp" + +namespace Slic3r::Arachne { + +/*! \brief Sparse grid which can locate spatially nearby elements efficiently. + * + * \tparam ElemT The element type to store. + * \tparam Locator The functor to get the location from ElemT. Locator + * must have: Point operator()(const ElemT &elem) const + * which returns the location associated with val. + */ +template class SparsePointGrid : public SparseGrid +{ +public: + using Elem = ElemT; + + /*! \brief Constructs a sparse grid with the specified cell size. + * + * \param[in] cell_size The size to use for a cell (square) in the grid. + * Typical values would be around 0.5-2x of expected query radius. + * \param[in] elem_reserve Number of elements to research space for. + * \param[in] max_load_factor Maximum average load factor before rehashing. + */ + SparsePointGrid(coord_t cell_size, size_t elem_reserve = 0U, float max_load_factor = 1.0f); + + /*! \brief Inserts elem into the sparse grid. + * + * \param[in] elem The element to be inserted. + */ + void insert(const Elem &elem); + + /*! + * Get just any element that's within a certain radius of a point. + * + * Rather than giving a vector of nearby elements, this function just gives + * a single element, any element, in no particular order. + * \param query_pt The point to query for an object nearby. + * \param radius The radius of what is considered "nearby". + */ + const ElemT *getAnyNearby(const Point &query_pt, coord_t radius); + +protected: + using GridPoint = typename SparseGrid::GridPoint; + + /*! \brief Accessor for getting locations from elements. */ + Locator m_locator; +}; + +template +SparsePointGrid::SparsePointGrid(coord_t cell_size, size_t elem_reserve, float max_load_factor) : SparseGrid(cell_size, elem_reserve, max_load_factor) {} + +template +void SparsePointGrid::insert(const Elem &elem) +{ + Point loc = m_locator(elem); + GridPoint grid_loc = SparseGrid::toGridPoint(loc.template cast()); + + SparseGrid::m_grid.emplace(grid_loc, elem); +} + +template +const ElemT *SparsePointGrid::getAnyNearby(const Point &query_pt, coord_t radius) +{ + const ElemT *ret = nullptr; + const std::function &process_func = [&ret, query_pt, radius, this](const ElemT &maybe_nearby) { + if (shorter_then(m_locator(maybe_nearby) - query_pt, radius)) { + ret = &maybe_nearby; + return false; + } + return true; + }; + SparseGrid::processNearby(query_pt, radius, process_func); + + return ret; +} + +} // namespace Slic3r::Arachne + +#endif // UTILS_SPARSE_POINT_GRID_H diff --git a/src/libslic3r/Arachne/utils/SquareGrid.cpp b/src/libslic3r/Arachne/utils/SquareGrid.cpp new file mode 100644 index 0000000000..ae89965795 --- /dev/null +++ b/src/libslic3r/Arachne/utils/SquareGrid.cpp @@ -0,0 +1,147 @@ +//Copyright (c) 2021 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "SquareGrid.hpp" +#include "../../Point.hpp" + +using namespace Slic3r::Arachne; + + +SquareGrid::SquareGrid(coord_t cell_size) : cell_size(cell_size) +{ + assert(cell_size > 0U); +} + + +SquareGrid::GridPoint SquareGrid::toGridPoint(const Vec2i64 &point) const +{ + return Point(toGridCoord(point.x()), toGridCoord(point.y())); +} + + +SquareGrid::grid_coord_t SquareGrid::toGridCoord(const int64_t &coord) const +{ + // This mapping via truncation results in the cells with + // GridPoint.x==0 being twice as large and similarly for + // GridPoint.y==0. This doesn't cause any incorrect behavior, + // just changes the running time slightly. The change in running + // time from this is probably not worth doing a proper floor + // operation. + return coord / cell_size; +} + +coord_t SquareGrid::toLowerCoord(const grid_coord_t& grid_coord) const +{ + // This mapping via truncation results in the cells with + // GridPoint.x==0 being twice as large and similarly for + // GridPoint.y==0. This doesn't cause any incorrect behavior, + // just changes the running time slightly. The change in running + // time from this is probably not worth doing a proper floor + // operation. + return grid_coord * cell_size; +} + + +bool SquareGrid::processLineCells(const std::pair line, const std::function& process_cell_func) +{ + return static_cast(this)->processLineCells(line, process_cell_func); +} + + +bool SquareGrid::processLineCells(const std::pair line, const std::function& process_cell_func) const +{ + Point start = line.first; + Point end = line.second; + if (end.x() < start.x()) + { // make sure X increases between start and end + std::swap(start, end); + } + + const GridPoint start_cell = toGridPoint(start.cast()); + const GridPoint end_cell = toGridPoint(end.cast()); + const int64_t y_diff = int64_t(end.y() - start.y()); + const grid_coord_t y_dir = nonzeroSign(y_diff); + + /* This line drawing algorithm iterates over the range of Y coordinates, and + for each Y coordinate computes the range of X coordinates crossed in one + unit of Y. These ranges are rounded to be inclusive, so effectively this + creates a "fat" line, marking more cells than a strict one-cell-wide path.*/ + grid_coord_t x_cell_start = start_cell.x(); + for (grid_coord_t cell_y = start_cell.y(); cell_y * y_dir <= end_cell.y() * y_dir; cell_y += y_dir) + { // for all Y from start to end + // nearest y coordinate of the cells in the next row + const coord_t nearest_next_y = toLowerCoord(cell_y + ((nonzeroSign(cell_y) == y_dir || cell_y == 0) ? y_dir : coord_t(0))); + grid_coord_t x_cell_end; // the X coord of the last cell to include from this row + if (y_diff == 0) + { + x_cell_end = end_cell.x(); + } + else + { + const int64_t area = int64_t(end.x() - start.x()) * int64_t(nearest_next_y - start.y()); + // corresponding_x: the x coordinate corresponding to nearest_next_y + int64_t corresponding_x = int64_t(start.x()) + area / y_diff; + x_cell_end = toGridCoord(corresponding_x + ((corresponding_x < 0) && ((area % y_diff) != 0))); + if (x_cell_end < start_cell.x()) + { // process at least one cell! + x_cell_end = x_cell_start; + } + } + + for (grid_coord_t cell_x = x_cell_start; cell_x <= x_cell_end; ++cell_x) + { + GridPoint grid_loc(cell_x, cell_y); + if (! process_cell_func(grid_loc)) + { + return false; + } + if (grid_loc == end_cell) + { + return true; + } + } + // TODO: this causes at least a one cell overlap for each row, which + // includes extra cells when crossing precisely on the corners + // where positive slope where x > 0 and negative slope where x < 0 + x_cell_start = x_cell_end; + } + assert(false && "We should have returned already before here!"); + return false; +} + +bool SquareGrid::processNearby +( + const Point &query_pt, + coord_t radius, + const std::function& process_func +) const +{ + const Point min_loc(query_pt.x() - radius, query_pt.y() - radius); + const Point max_loc(query_pt.x() + radius, query_pt.y() + radius); + + GridPoint min_grid = toGridPoint(min_loc.cast()); + GridPoint max_grid = toGridPoint(max_loc.cast()); + + for (coord_t grid_y = min_grid.y(); grid_y <= max_grid.y(); ++grid_y) + { + for (coord_t grid_x = min_grid.x(); grid_x <= max_grid.x(); ++grid_x) + { + GridPoint grid_pt(grid_x,grid_y); + if (!process_func(grid_pt)) + { + return false; + } + } + } + return true; +} + +SquareGrid::grid_coord_t SquareGrid::nonzeroSign(const grid_coord_t z) const +{ + return (z >= 0) - (z < 0); +} + +coord_t SquareGrid::getCellSize() const +{ + return cell_size; +} diff --git a/src/libslic3r/Arachne/utils/SquareGrid.hpp b/src/libslic3r/Arachne/utils/SquareGrid.hpp new file mode 100644 index 0000000000..c59c3ee1b9 --- /dev/null +++ b/src/libslic3r/Arachne/utils/SquareGrid.hpp @@ -0,0 +1,110 @@ +//Copyright (c) 2021 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef UTILS_SQUARE_GRID_H +#define UTILS_SQUARE_GRID_H + +#include "../../Point.hpp" + +#include +#include +#include +#include + +namespace Slic3r::Arachne { + +/*! + * Helper class to calculate coordinates on a square grid, and providing some + * utility functions to process grids. + * + * Doesn't contain any data, except cell size. The purpose is only to + * automatically generate coordinates on a grid, and automatically feed them to + * functions. + * The grid is theoretically infinite (bar integer limits). + */ +class SquareGrid +{ +public: + /*! \brief Constructs a grid with the specified cell size. + * \param[in] cell_size The size to use for a cell (square) in the grid. + */ + SquareGrid(const coord_t cell_size); + + /*! + * Get the cell size this grid was created for. + */ + coord_t getCellSize() const; + + using GridPoint = Point; + using grid_coord_t = coord_t; + + /*! \brief Process cells along a line indicated by \p line. + * + * \param line The line along which to process cells. + * \param process_func Processes each cell. ``process_func(elem)`` is called + * for each cell. Processing stops if function returns false. + * \return Whether we need to continue processing after this function. + */ + bool processLineCells(const std::pair line, const std::function& process_cell_func); + + /*! \brief Process cells along a line indicated by \p line. + * + * \param line The line along which to process cells + * \param process_func Processes each cell. ``process_func(elem)`` is called + * for each cell. Processing stops if function returns false. + * \return Whether we need to continue processing after this function. + */ + bool processLineCells(const std::pair line, const std::function& process_cell_func) const; + + /*! \brief Process cells that might contain sought after points. + * + * Processes cells that might be within a square with twice \p radius as + * width, centered around \p query_pt. + * May process elements that are up to radius + cell_size from query_pt. + * \param query_pt The point to search around. + * \param radius The search radius. + * \param process_func Processes each cell. ``process_func(loc)`` is called + * for each cell coord within range. Processing stops if function returns + * ``false``. + * \return Whether we need to continue processing after this function. + */ + bool processNearby(const Point &query_pt, coord_t radius, const std::function &process_func) const; + + /*! \brief Compute the grid coordinates of a point. + * \param point The actual location. + * \return The grid coordinates that correspond to \p point. + */ + GridPoint toGridPoint(const Vec2i64 &point) const; + + /*! \brief Compute the grid coordinate of a real space coordinate. + * \param coord The actual location. + * \return The grid coordinate that corresponds to \p coord. + */ + grid_coord_t toGridCoord(const int64_t &coord) const; + + /*! \brief Compute the lowest coord in a grid cell. + * The lowest point is the point in the grid cell closest to the origin. + * + * \param grid_coord The grid coordinate. + * \return The print space coordinate that corresponds to \p grid_coord. + */ + coord_t toLowerCoord(const grid_coord_t &grid_coord) const; + +protected: + /*! \brief The cell (square) size. */ + coord_t cell_size; + + /*! + * Compute the sign of a number. + * + * The number 0 will result in a positive sign (1). + * \param z The number to find the sign of. + * \return 1 if the number is positive or 0, or -1 if the number is + * negative. + */ + grid_coord_t nonzeroSign(grid_coord_t z) const; +}; + +} // namespace Slic3r::Arachne + +#endif //UTILS_SQUARE_GRID_H diff --git a/src/libslic3r/Arachne/utils/VoronoiUtils.cpp b/src/libslic3r/Arachne/utils/VoronoiUtils.cpp new file mode 100644 index 0000000000..3da556b470 --- /dev/null +++ b/src/libslic3r/Arachne/utils/VoronoiUtils.cpp @@ -0,0 +1,250 @@ +//Copyright (c) 2021 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#include +#include +#include + +#include "linearAlg2D.hpp" +#include "VoronoiUtils.hpp" + +namespace Slic3r::Arachne +{ + +Vec2i64 VoronoiUtils::p(const vd_t::vertex_type *node) +{ + const double x = node->x(); + const double y = node->y(); + assert(x <= double(std::numeric_limits::max()) && x >= std::numeric_limits::lowest()); + assert(y <= double(std::numeric_limits::max()) && y >= std::numeric_limits::lowest()); + return {int64_t(x + 0.5 - (x < 0)), int64_t(y + 0.5 - (y < 0))}; // Round to the nearest integer coordinates. +} + +Point VoronoiUtils::getSourcePoint(const vd_t::cell_type& cell, const std::vector& segments) +{ + assert(cell.contains_point()); + if(!cell.contains_point()) + BOOST_LOG_TRIVIAL(debug) << "Voronoi cell doesn't contain a source point!"; + + switch (cell.source_category()) { + case boost::polygon::SOURCE_CATEGORY_SINGLE_POINT: + assert(false && "Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!\n"); + BOOST_LOG_TRIVIAL(error) << "Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!"; + break; + case boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT: + assert(cell.source_index() < segments.size()); + return segments[cell.source_index()].to(); + break; + case boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT: + assert(cell.source_index() < segments.size()); + return segments[cell.source_index()].from(); + break; + default: + assert(false && "getSourcePoint should only be called on point cells!\n"); + break; + } + + assert(false && "cell.source_category() is equal to an invalid value!\n"); + BOOST_LOG_TRIVIAL(error) << "cell.source_category() is equal to an invalid value!"; + return {}; +} + +PolygonsPointIndex VoronoiUtils::getSourcePointIndex(const vd_t::cell_type& cell, const std::vector& segments) +{ + assert(cell.contains_point()); + if(!cell.contains_point()) + BOOST_LOG_TRIVIAL(debug) << "Voronoi cell doesn't contain a source point!"; + + assert(cell.source_category() != boost::polygon::SOURCE_CATEGORY_SINGLE_POINT); + switch (cell.source_category()) { + case boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT: { + assert(cell.source_index() < segments.size()); + PolygonsPointIndex ret = segments[cell.source_index()]; + ++ret; + return ret; + break; + } + case boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT: { + assert(cell.source_index() < segments.size()); + return segments[cell.source_index()]; + break; + } + default: + assert(false && "getSourcePoint should only be called on point cells!\n"); + break; + } + PolygonsPointIndex ret = segments[cell.source_index()]; + return ++ret; +} + +const VoronoiUtils::Segment &VoronoiUtils::getSourceSegment(const vd_t::cell_type &cell, const std::vector &segments) +{ + assert(cell.contains_segment()); + if (!cell.contains_segment()) + BOOST_LOG_TRIVIAL(debug) << "Voronoi cell doesn't contain a source segment!"; + + return segments[cell.source_index()]; +} + +class PointMatrix +{ +public: + double matrix[4]; + + PointMatrix() + { + matrix[0] = 1; + matrix[1] = 0; + matrix[2] = 0; + matrix[3] = 1; + } + + PointMatrix(double rotation) + { + rotation = rotation / 180 * M_PI; + matrix[0] = cos(rotation); + matrix[1] = -sin(rotation); + matrix[2] = -matrix[1]; + matrix[3] = matrix[0]; + } + + PointMatrix(const Point p) + { + matrix[0] = p.x(); + matrix[1] = p.y(); + double f = sqrt((matrix[0] * matrix[0]) + (matrix[1] * matrix[1])); + matrix[0] /= f; + matrix[1] /= f; + matrix[2] = -matrix[1]; + matrix[3] = matrix[0]; + } + + static PointMatrix scale(double s) + { + PointMatrix ret; + ret.matrix[0] = s; + ret.matrix[3] = s; + return ret; + } + + Point apply(const Point p) const + { + return Point(coord_t(p.x() * matrix[0] + p.y() * matrix[1]), coord_t(p.x() * matrix[2] + p.y() * matrix[3])); + } + + Point unapply(const Point p) const + { + return Point(coord_t(p.x() * matrix[0] + p.y() * matrix[2]), coord_t(p.x() * matrix[1] + p.y() * matrix[3])); + } +}; +std::vector VoronoiUtils::discretizeParabola(const Point& p, const Segment& segment, Point s, Point e, coord_t approximate_step_size, float transitioning_angle) +{ + std::vector discretized; + // x is distance of point projected on the segment ab + // xx is point projected on the segment ab + const Point a = segment.from(); + const Point b = segment.to(); + const Point ab = b - a; + const Point as = s - a; + const Point ae = e - a; + const coord_t ab_size = ab.cast().norm(); + const coord_t sx = as.cast().dot(ab.cast()) / ab_size; + const coord_t ex = ae.cast().dot(ab.cast()) / ab_size; + const coord_t sxex = ex - sx; + + assert((as.cast().dot(ab.cast()) / int64_t(ab_size)) <= std::numeric_limits::max()); + assert((ae.cast().dot(ab.cast()) / int64_t(ab_size)) <= std::numeric_limits::max()); + + const Point ap = p - a; + const coord_t px = ap.cast().dot(ab.cast()) / ab_size; + + assert((ap.cast().dot(ab.cast()) / int64_t(ab_size)) <= std::numeric_limits::max()); + + Point pxx; + Line(a, b).distance_to_infinite_squared(p, &pxx); + const Point ppxx = pxx - p; + const coord_t d = ppxx.cast().norm(); + const PointMatrix rot = PointMatrix(ppxx.rotate_90_degree_ccw()); + + if (d == 0) + { + discretized.emplace_back(s); + discretized.emplace_back(e); + return discretized; + } + + const float marking_bound = atan(transitioning_angle * 0.5); + int64_t msx = - marking_bound * int64_t(d); // projected marking_start + int64_t mex = marking_bound * int64_t(d); // projected marking_end + + assert(msx <= std::numeric_limits::max()); + assert(double(msx) * double(msx) <= double(std::numeric_limits::max())); + assert(mex <= std::numeric_limits::max()); + assert(double(msx) * double(msx) / double(2 * d) + double(d / 2) <= std::numeric_limits::max()); + + const coord_t marking_start_end_h = msx * msx / (2 * d) + d / 2; + Point marking_start = rot.unapply(Point(coord_t(msx), marking_start_end_h)) + pxx; + Point marking_end = rot.unapply(Point(coord_t(mex), marking_start_end_h)) + pxx; + const int dir = (sx > ex) ? -1 : 1; + if (dir < 0) + { + std::swap(marking_start, marking_end); + std::swap(msx, mex); + } + + bool add_marking_start = msx * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && msx * int64_t(dir) < int64_t(ex - px) * int64_t(dir); + bool add_marking_end = mex * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && mex * int64_t(dir) < int64_t(ex - px) * int64_t(dir); + + const Point apex = rot.unapply(Point(0, d / 2)) + pxx; + bool add_apex = int64_t(sx - px) * int64_t(dir) < 0 && int64_t(ex - px) * int64_t(dir) > 0; + + assert(!(add_marking_start && add_marking_end) || add_apex); + if(add_marking_start && add_marking_end && !add_apex) + { + BOOST_LOG_TRIVIAL(warning) << "Failing to discretize parabola! Must add an apex or one of the endpoints."; + } + + const coord_t step_count = static_cast(static_cast(std::abs(ex - sx)) / approximate_step_size + 0.5); + + discretized.emplace_back(s); + for (coord_t step = 1; step < step_count; step++) + { + assert(double(sxex) * double(step) <= double(std::numeric_limits::max())); + const int64_t x = int64_t(sx) + int64_t(sxex) * int64_t(step) / int64_t(step_count) - int64_t(px); + assert(double(x) * double(x) <= double(std::numeric_limits::max())); + assert(double(x) * double(x) / double(2 * d) + double(d / 2) <= double(std::numeric_limits::max())); + const int64_t y = int64_t(x) * int64_t(x) / int64_t(2 * d) + int64_t(d / 2); + + if (add_marking_start && msx * int64_t(dir) < int64_t(x) * int64_t(dir)) + { + discretized.emplace_back(marking_start); + add_marking_start = false; + } + if (add_apex && int64_t(x) * int64_t(dir) > 0) + { + discretized.emplace_back(apex); + add_apex = false; // only add the apex just before the + } + if (add_marking_end && mex * int64_t(dir) < int64_t(x) * int64_t(dir)) + { + discretized.emplace_back(marking_end); + add_marking_end = false; + } + assert(x <= std::numeric_limits::max() && x >= std::numeric_limits::lowest()); + assert(y <= std::numeric_limits::max() && y >= std::numeric_limits::lowest()); + const Point result = rot.unapply(Point(x, y)) + pxx; + discretized.emplace_back(result); + } + if (add_apex) + { + discretized.emplace_back(apex); + } + if (add_marking_end) + { + discretized.emplace_back(marking_end); + } + discretized.emplace_back(e); + return discretized; +} + +}//namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/utils/VoronoiUtils.hpp b/src/libslic3r/Arachne/utils/VoronoiUtils.hpp new file mode 100644 index 0000000000..e736f98bcf --- /dev/null +++ b/src/libslic3r/Arachne/utils/VoronoiUtils.hpp @@ -0,0 +1,42 @@ +//Copyright (c) 2020 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + + +#ifndef UTILS_VORONOI_UTILS_H +#define UTILS_VORONOI_UTILS_H + +#include + + +#include + +#include "PolygonsSegmentIndex.hpp" + +namespace Slic3r::Arachne +{ + +/*! + */ +class VoronoiUtils +{ +public: + using Segment = PolygonsSegmentIndex; + using voronoi_data_t = double; + using vd_t = boost::polygon::voronoi_diagram; + + static Point getSourcePoint(const vd_t::cell_type &cell, const std::vector &segments); + static const Segment &getSourceSegment(const vd_t::cell_type &cell, const std::vector &segments); + static PolygonsPointIndex getSourcePointIndex(const vd_t::cell_type &cell, const std::vector &segments); + + static Vec2i64 p(const vd_t::vertex_type *node); + + /*! + * Discretize a parabola based on (approximate) step size. + * The \p approximate_step_size is measured parallel to the \p source_segment, not along the parabola. + */ + static std::vector discretizeParabola(const Point &source_point, const Segment &source_segment, Point start, Point end, coord_t approximate_step_size, float transitioning_angle); +}; + +} // namespace Slic3r::Arachne + +#endif // UTILS_VORONOI_UTILS_H diff --git a/src/libslic3r/Arachne/utils/linearAlg2D.hpp b/src/libslic3r/Arachne/utils/linearAlg2D.hpp new file mode 100644 index 0000000000..797bae0b97 --- /dev/null +++ b/src/libslic3r/Arachne/utils/linearAlg2D.hpp @@ -0,0 +1,122 @@ +//Copyright (c) 2020 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef UTILS_LINEAR_ALG_2D_H +#define UTILS_LINEAR_ALG_2D_H + +#include "../../Point.hpp" + +namespace Slic3r::Arachne::LinearAlg2D +{ + +/*! + * Test whether a point is inside a corner. + * Whether point \p query_point is left of the corner abc. + * Whether the \p query_point is in the circle half left of ab and left of bc, rather than to the right. + * + * Test whether the \p query_point is inside of a polygon w.r.t a single corner. + */ +inline static bool isInsideCorner(const Point &a, const Point &b, const Point &c, const Vec2i64 &query_point) +{ + // Visualisation for the algorithm below: + // + // query + // | + // | + // | + // perp-----------b + // / \ (note that the lines + // / \ AB and AC are normalized + // / \ to 10000 units length) + // a c + // + + auto normal = [](const Point &p0, coord_t len) -> Point { + int64_t _len = p0.cast().norm(); + if (_len < 1) + return {len, 0}; + return (p0.cast() * int64_t(len) / _len).cast(); + }; + + auto rotate_90_degree_ccw = [](const Vec2d &p) -> Vec2d { + return {-p.y(), p.x()}; + }; + + constexpr coord_t normal_length = 10000; //Create a normal vector of reasonable length in order to reduce rounding error. + const Point ba = normal(a - b, normal_length); + const Point bc = normal(c - b, normal_length); + const Vec2d bq = query_point.cast() - b.cast(); + const Vec2d perpendicular = rotate_90_degree_ccw(bq); //The query projects to this perpendicular to coordinate 0. + + const double project_a_perpendicular = ba.cast().dot(perpendicular); //Project vertex A on the perpendicular line. + const double project_c_perpendicular = bc.cast().dot(perpendicular); //Project vertex C on the perpendicular line. + if ((project_a_perpendicular > 0.) != (project_c_perpendicular > 0.)) //Query is between A and C on the projection. + { + return project_a_perpendicular > 0.; //Due to the winding order of corner ABC, this means that the query is inside. + } + else //Beyond either A or C, but it could still be inside of the polygon. + { + const double project_a_parallel = ba.cast().dot(bq); //Project not on the perpendicular, but on the original. + const double project_c_parallel = bc.cast().dot(bq); + + //Either: + // * A is to the right of B (project_a_perpendicular > 0) and C is below A (project_c_parallel < project_a_parallel), or + // * A is to the left of B (project_a_perpendicular < 0) and C is above A (project_c_parallel > project_a_parallel). + return (project_c_parallel < project_a_parallel) == (project_a_perpendicular > 0.); + } +} + +/*! + * Returns the determinant of the 2D matrix defined by the the vectors ab and ap as rows. + * + * The returned value is zero for \p p lying (approximately) on the line going through \p a and \p b + * The value is positive for values lying to the left and negative for values lying to the right when looking from \p a to \p b. + * + * \param p the point to check + * \param a the from point of the line + * \param b the to point of the line + * \return a positive value when \p p lies to the left of the line from \p a to \p b + */ +static inline int64_t pointIsLeftOfLine(const Point &p, const Point &a, const Point &b) +{ + return int64_t(b.x() - a.x()) * int64_t(p.y() - a.y()) - int64_t(b.y() - a.y()) * int64_t(p.x() - a.x()); +} + +/*! + * Compute the angle between two consecutive line segments. + * + * The angle is computed from the left side of b when looking from a. + * + * c + * \ . + * \ b + * angle| + * | + * a + * + * \param a start of first line segment + * \param b end of first segment and start of second line segment + * \param c end of second line segment + * \return the angle in radians between 0 and 2 * pi of the corner in \p b + */ +static inline float getAngleLeft(const Point &a, const Point &b, const Point &c) +{ + const Vec2i64 ba = (a - b).cast(); + const Vec2i64 bc = (c - b).cast(); + const int64_t dott = ba.dot(bc); // dot product + const int64_t det = cross2(ba, bc); // determinant + if (det == 0) { + if ((ba.x() != 0 && (ba.x() > 0) == (bc.x() > 0)) || (ba.x() == 0 && (ba.y() > 0) == (bc.y() > 0))) + return 0; // pointy bit + else + return float(M_PI); // straight bit + } + const float angle = -atan2(double(det), double(dott)); // from -pi to pi + if (angle >= 0) + return angle; + else + return M_PI * 2 + angle; +} + +}//namespace Slic3r::Arachne +#endif//UTILS_LINEAR_ALG_2D_H diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index f0f7f351b5..e08a3c2086 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -208,7 +208,7 @@ protected: double dist_corner_x = ibb.minCorner().x() - origin_pack.x(); if (dist_corner_y < 0 || dist_corner_x<0) return LARGE_COST_TO_REJECT; - double bindist = norm(dist_corner_y + 0.1 * dist_corner_x + double bindist = norm(dist_corner_y + 1 * dist_corner_x + 1 * double(ibb.maxCorner().y() - ibb.minCorner().y())); // occupy as few rows as possible return bindist; } @@ -393,7 +393,9 @@ protected: auto py2 = p.boundingBox().maxCorner().y(); auto inter_min = std::max(iy1, py1); // min y of intersection auto inter_max = std::min(iy2, py2); // max y of intersection. length=max_y-min_y>0 means intersection exists - if (inter_max - inter_min > 0) { + // if this item is lower than existing ones, this item will be printed first, so should not exceed height_to_rod + if (iy2 < py1) { hasRowHeightConflict |= (item.height > clearance_height_to_rod); } + else if (inter_max - inter_min > 0) { // if they inter, the one on the left will be printed first double h = ix1 < px1 ? item.height : p.height; hasRowHeightConflict |= (h > clearance_height_to_rod); @@ -434,10 +436,12 @@ protected: if (!params.allow_multi_materials_on_same_plate) score += LARGE_COST_TO_REJECT * (item.extrude_id != p.extrude_id); } + // for layered printing, we want extruder change as few as possible + // this has very weak effect, CAN NOT use a large weight if (!params.is_seq_print) { extruder_ids.insert(item.extrude_id); - score += 0.2 * LARGE_COST_TO_REJECT * std::max(0, ((int)extruder_ids.size() - 1)); + score += 1 * std::max(0, ((int)extruder_ids.size() - 1)); } return std::make_tuple(score, fullbb); diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 8c4410bf23..da327ec978 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -69,6 +69,8 @@ set(lisbslic3r_sources Fill/FillConcentric.hpp Fill/FillConcentricWGapFill.cpp Fill/FillConcentricWGapFill.hpp + Fill/FillConcentricInternal.cpp + Fill/FillConcentricInternal.hpp Fill/FillHoneycomb.cpp Fill/FillHoneycomb.hpp Fill/FillGyroid.cpp @@ -313,6 +315,47 @@ set(lisbslic3r_sources SLA/Clustering.hpp SLA/Clustering.cpp SLA/ReprojectPointsOnMesh.hpp + + Arachne/BeadingStrategy/BeadingStrategy.hpp + Arachne/BeadingStrategy/BeadingStrategy.cpp + Arachne/BeadingStrategy/BeadingStrategyFactory.hpp + Arachne/BeadingStrategy/BeadingStrategyFactory.cpp + Arachne/BeadingStrategy/DistributedBeadingStrategy.hpp + Arachne/BeadingStrategy/DistributedBeadingStrategy.cpp + Arachne/BeadingStrategy/LimitedBeadingStrategy.hpp + Arachne/BeadingStrategy/LimitedBeadingStrategy.cpp + Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.hpp + Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.cpp + Arachne/BeadingStrategy/RedistributeBeadingStrategy.hpp + Arachne/BeadingStrategy/RedistributeBeadingStrategy.cpp + Arachne/BeadingStrategy/WideningBeadingStrategy.hpp + Arachne/BeadingStrategy/WideningBeadingStrategy.cpp + Arachne/utils/ExtrusionJunction.hpp + Arachne/utils/ExtrusionJunction.cpp + Arachne/utils/ExtrusionLine.hpp + Arachne/utils/ExtrusionLine.cpp + Arachne/utils/HalfEdge.hpp + Arachne/utils/HalfEdgeGraph.hpp + Arachne/utils/HalfEdgeNode.hpp + Arachne/utils/SparseGrid.hpp + Arachne/utils/SparsePointGrid.hpp + Arachne/utils/SparseLineGrid.hpp + Arachne/utils/SquareGrid.hpp + Arachne/utils/SquareGrid.cpp + Arachne/utils/PolygonsPointIndex.hpp + Arachne/utils/PolygonsSegmentIndex.hpp + Arachne/utils/PolylineStitcher.hpp + Arachne/utils/PolylineStitcher.cpp + Arachne/utils/VoronoiUtils.hpp + Arachne/utils/VoronoiUtils.cpp + Arachne/SkeletalTrapezoidation.hpp + Arachne/SkeletalTrapezoidation.cpp + Arachne/SkeletalTrapezoidationEdge.hpp + Arachne/SkeletalTrapezoidationGraph.hpp + Arachne/SkeletalTrapezoidationGraph.cpp + Arachne/SkeletalTrapezoidationJoint.hpp + Arachne/WallToolPaths.hpp + Arachne/WallToolPaths.cpp ) if (APPLE) @@ -402,6 +445,11 @@ set(OCCT_LIBS TKernel ) +if(APPLE) + target_link_libraries(libslic3r freetype) +endif() + + target_link_libraries(libslic3r libnest2d admesh diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 02a2cfc93c..b7aeed8a69 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -584,6 +584,8 @@ Slic3r::Polygons union_(const Slic3r::Polygons &subject) { return _clipper(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No); } Slic3r::Polygons union_(const Slic3r::ExPolygons &subject) { return _clipper(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No); } +Slic3r::Polygons union_(const Slic3r::Polygons &subject, const ClipperLib::PolyFillType fillType) + { return to_polygons(clipper_do(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), fillType, ApplySafetyOffset::No)); } Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2) { // BBS diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index a472d1a1c3..d96061b07b 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -498,6 +498,7 @@ inline Slic3r::Lines intersection_ln(const Slic3r::Line &subject, const Slic3r:: Slic3r::Polygons union_(const Slic3r::Polygons &subject); Slic3r::Polygons union_(const Slic3r::ExPolygons &subject); +Slic3r::Polygons union_(const Slic3r::Polygons &subject, const ClipperLib::PolyFillType fillType); Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2); // May be used to "heal" unusual models (3DLabPrints etc.) by providing fill_type (pftEvenOdd, pftNonZero, pftPositive, pftNegative). Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero); diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index a821340c19..7aae8da152 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -11,6 +11,7 @@ #include "FillBase.hpp" #include "FillRectilinear.hpp" +#include "FillConcentricInternal.hpp" #define NARROW_INFILL_AREA_THRESHOLD 3 @@ -303,7 +304,7 @@ std::vector group_fills(const Layer &layer) } } - // BBS: detect narrow internal solid infill area and use ipConcentricGapFill pattern instead + // BBS: detect narrow internal solid infill area and use ipConcentricInternal pattern instead if (layer.object()->config().detect_narrow_internal_solid_infill) { size_t surface_fills_size = surface_fills.size(); for (size_t i = 0; i < surface_fills_size; i++) { @@ -324,12 +325,12 @@ std::vector group_fills(const Layer &layer) } else if (narrow_expolygons_index.size() == expolygons_size) { // BBS: all expolygons are narrow, directly change the fill pattern - surface_fills[i].params.pattern = ipConcentricGapFill; + surface_fills[i].params.pattern = ipConcentricInternal; } else { // BBS: some expolygons are narrow, spilit surface_fills[i] and rearrange the expolygons params = surface_fills[i].params; - params.pattern = ipConcentricGapFill; + params.pattern = ipConcentricInternal; surface_fills.emplace_back(params); surface_fills.back().region_id = surface_fills[i].region_id; surface_fills.back().surface.surface_type = stInternalSolid; @@ -401,6 +402,13 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: f->angle = surface_fill.params.angle; f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree; + if (surface_fill.params.pattern == ipConcentricInternal) { + FillConcentricInternal *fill_concentric = dynamic_cast(f.get()); + assert(fill_concentric != nullptr); + fill_concentric->print_config = &this->object()->print()->config(); + fill_concentric->print_object_config = &this->object()->config(); + } + // calculate flow spacing for infill pattern generation bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.bridge; double link_max_length = 0.; diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index 4255bbe30b..74ffdc372b 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -22,6 +22,7 @@ #include "FillLightning.hpp" // BBS: new infill pattern header #include "FillConcentricWGapFill.hpp" +#include "FillConcentricInternal.hpp" // #define INFILL_DEBUG_OUTPUT @@ -58,6 +59,7 @@ Fill* Fill::new_from_type(const InfillPattern type) #endif // HAS_LIGHTNING_INFILL // BBS: for internal solid infill only case ipConcentricGapFill: return new FillConcentricWGapFill(); + case ipConcentricInternal: return new FillConcentricInternal(); // BBS: for bottom and top surface only case ipMonotonicLine: return new FillMonotonicLineWGapFill(); default: throw Slic3r::InvalidArgument("unknown type"); diff --git a/src/libslic3r/Fill/FillConcentricInternal.cpp b/src/libslic3r/Fill/FillConcentricInternal.cpp new file mode 100644 index 0000000000..33878578c0 --- /dev/null +++ b/src/libslic3r/Fill/FillConcentricInternal.cpp @@ -0,0 +1,101 @@ +#include "../ClipperUtils.hpp" +#include "../ExPolygon.hpp" +#include "../Surface.hpp" +#include "../VariableWidth.hpp" +#include "Arachne/WallToolPaths.hpp" + +#include "FillConcentricInternal.hpp" + +namespace Slic3r { + +void FillConcentricInternal::fill_surface_extrusion(const Surface* surface, const FillParams& params, ExtrusionEntitiesPtr& out) +{ + assert(this->print_config != nullptr && this->print_object_config != nullptr); + + ThickPolylines thick_polylines_out; + + for (size_t i = 0; i < this->no_overlap_expolygons.size(); ++i) { + ExPolygon &expolygon = this->no_overlap_expolygons[i]; + + // no rotation is supported for this infill pattern + Point bbox_size = expolygon.contour.bounding_box().size(); + coord_t min_spacing = params.flow.scaled_spacing(); + + coord_t loops_count = std::max(bbox_size.x(), bbox_size.y()) / min_spacing + 1; + Polygons polygons = offset(expolygon, 0); + + double min_nozzle_diameter = *std::min_element(print_config->nozzle_diameter.values.begin(), print_config->nozzle_diameter.values.end()); + Arachne::WallToolPathsParams input_params; + input_params.min_bead_width = 0.85 * min_nozzle_diameter; + input_params.min_feature_size = 0.1; + input_params.wall_transition_length = 0.4; + input_params.wall_transition_angle = 10; + input_params.wall_transition_filter_deviation = 0.25 * min_nozzle_diameter; + input_params.wall_distribution_count = 1; + input_params.wall_add_middle_threshold = 0.75; + input_params.wall_split_middle_threshold = 0.5; + + Arachne::WallToolPaths wallToolPaths(polygons, min_spacing, min_spacing, loops_count, 0, input_params); + + std::vector loops = wallToolPaths.getToolPaths(); + std::vector all_extrusions; + for (Arachne::VariableWidthLines& loop : loops) { + if (loop.empty()) + continue; + for (const Arachne::ExtrusionLine& wall : loop) + all_extrusions.emplace_back(&wall); + } + + // Split paths using a nearest neighbor search. + size_t firts_poly_idx = thick_polylines_out.size(); + Point last_pos(0, 0); + for (const Arachne::ExtrusionLine* extrusion : all_extrusions) { + if (extrusion->empty()) + continue; + + ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion); + if (extrusion->is_closed && thick_polyline.points.front() == thick_polyline.points.back() && thick_polyline.width.front() == thick_polyline.width.back()) { + thick_polyline.points.pop_back(); + assert(thick_polyline.points.size() * 2 == thick_polyline.width.size()); + int nearest_idx = last_pos.nearest_point_index(thick_polyline.points); + std::rotate(thick_polyline.points.begin(), thick_polyline.points.begin() + nearest_idx, thick_polyline.points.end()); + std::rotate(thick_polyline.width.begin(), thick_polyline.width.begin() + 2 * nearest_idx, thick_polyline.width.end()); + thick_polyline.points.emplace_back(thick_polyline.points.front()); + } + thick_polylines_out.emplace_back(std::move(thick_polyline)); + } + + // clip the paths to prevent the extruder from getting exactly on the first point of the loop + // Keep valid paths only. + size_t j = firts_poly_idx; + for (size_t i = firts_poly_idx; i < thick_polylines_out.size(); ++i) { + thick_polylines_out[i].clip_end(this->loop_clipping); + if (thick_polylines_out[i].is_valid()) { + if (j < i) + thick_polylines_out[j] = std::move(thick_polylines_out[i]); + ++j; + } + } + if (j < thick_polylines_out.size()) + thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end()); + } + + ExtrusionEntityCollection *coll_nosort = new ExtrusionEntityCollection(); + coll_nosort->no_sort = this->no_sort(); //can be sorted inside the pass + + if (!thick_polylines_out.empty()) { + Flow new_flow = params.flow.with_spacing(float(this->spacing)); + ExtrusionEntityCollection gap_fill; + variable_width(thick_polylines_out, params.extrusion_role, new_flow, gap_fill.entities); + coll_nosort->append(std::move(gap_fill.entities)); + } + + if (!coll_nosort->entities.empty()) + out.push_back(coll_nosort); + else + delete coll_nosort; + +} + + +} // namespace Slic3r diff --git a/src/libslic3r/Fill/FillConcentricInternal.hpp b/src/libslic3r/Fill/FillConcentricInternal.hpp new file mode 100644 index 0000000000..9c8442e257 --- /dev/null +++ b/src/libslic3r/Fill/FillConcentricInternal.hpp @@ -0,0 +1,26 @@ +#ifndef slic3r_FillConcentricInternal_hpp_ +#define slic3r_FillConcentricInternal_hpp_ + +#include "FillBase.hpp" + +namespace Slic3r { + +class FillConcentricInternal : public Fill +{ +public: + ~FillConcentricInternal() override = default; + void fill_surface_extrusion(const Surface *surface, const FillParams ¶ms, ExtrusionEntitiesPtr &out) override; + +protected: + Fill* clone() const override { return new FillConcentricInternal(*this); }; + bool no_sort() const override { return true; } + + const PrintConfig *print_config = nullptr; + const PrintObjectConfig *print_object_config = nullptr; + + friend class Layer; +}; + +} // namespace Slic3r + +#endif // slic3r_FillConcentricInternal_hpp_ diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 2c2c7d57fa..503ed28e44 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -553,23 +553,30 @@ bool GCode::gcode_label_objects = false; std::string WipeTowerIntegration::tool_change(GCode& gcodegen, int extruder_id, bool finish_layer) { std::string gcode; + + // 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_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; + } + + if (m_enable_timelapse_print && m_is_first_print) { + gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][0], m_tool_changes[m_layer_idx][0].new_tool, wipe_tower_z); + m_tool_change_idx++; + m_is_first_print = false; + } + assert(m_layer_idx >= 0); 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_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; - } - if (!ignore_sparse) { gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id, wipe_tower_z); m_last_wipe_tower_print_z = wipe_tower_z; @@ -2784,6 +2791,9 @@ GCode::LayerResult GCode::process_layer( } } // for objects + if (m_wipe_tower) + m_wipe_tower->set_is_first_print(true); + // Extrude the skirt, brim, support, perimeters, infill ordered by the extruders. std::vector> lower_layer_edge_grids(layers.size()); for (unsigned int extruder_id : layer_tools.extruders) diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 5963fe484d..702f47fe73 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -82,7 +82,9 @@ public: m_layer_idx(-1), m_tool_change_idx(0), m_plate_origin(plate_origin), - m_single_extruder_multi_material(print_config.single_extruder_multi_material) + m_single_extruder_multi_material(print_config.single_extruder_multi_material), + m_enable_timelapse_print(print_config.timelapse_no_toolhead.value), + m_is_first_print(true) {} std::string prime(GCode &gcodegen); @@ -91,6 +93,11 @@ public: std::string finalize(GCode &gcodegen); std::vector used_filament_length() const; + bool is_first_print() const { return m_is_first_print;} + void set_is_first_print(bool is) { m_is_first_print = is; } + + bool enable_timelapse_print() const { return m_enable_timelapse_print; } + private: WipeTowerIntegration& operator=(const WipeTowerIntegration&); std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const; @@ -117,6 +124,8 @@ private: // BBS Vec3d m_plate_origin; bool m_single_extruder_multi_material; + bool m_enable_timelapse_print; + bool m_is_first_print; }; class ColorPrintColors diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 0da0bca55e..f3ec275291 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -2998,7 +2998,7 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line) block.role = m_extrusion_role; block.distance = delta_xyz; block.g1_line_id = m_g1_line_id; - block.layer_id = m_layer_id; + block.layer_id = std::max(1, m_layer_id); // BBS: calculates block cruise feedrate // For arc move, we need to limite the cruise according to centripetal acceleration which is @@ -3685,7 +3685,7 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type) m_interpolation_points[i] = Vec3f(m_interpolation_points[i].x() + m_x_offset, m_interpolation_points[i].y() + m_y_offset, - m_interpolation_points[i].z()) + + m_processing_start_custom_gcode ? m_first_layer_height : m_interpolation_points[i].z()) + m_extruder_offsets[m_extruder_id]; } diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 208df103bf..f0795ea7bb 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -544,7 +544,8 @@ WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origi m_travel_speed(config.travel_speed), m_current_tool(initial_tool), //wipe_volumes(flush_matrix) - m_wipe_volume(prime_volume) + m_wipe_volume(prime_volume), + m_enable_timelapse_print(config.timelapse_no_toolhead.value) { // Read absolute value of first layer speed, if given as percentage, // it is taken over following default. Speeds from config are not @@ -1304,7 +1305,10 @@ void WipeTower::plan_tower() } } - { + { + if (m_enable_timelapse_print && max_depth < EPSILON) + max_depth = min_wipe_tower_depth; + if (max_depth + EPSILON < min_wipe_tower_depth) m_extra_spacing = min_wipe_tower_depth / max_depth; else @@ -1343,9 +1347,13 @@ void WipeTower::plan_tower() for (auto& layer : m_plan) layer.depth = 0.f; + float max_depth_for_all = 0; for (int layer_index = int(m_plan.size()) - 1; layer_index >= 0; --layer_index) { float this_layer_depth = std::max(m_plan[layer_index].depth, m_plan[layer_index].toolchanges_depth()); + if (m_enable_timelapse_print && this_layer_depth < EPSILON) + this_layer_depth = min_wipe_tower_depth; + m_plan[layer_index].depth = this_layer_depth; if (this_layer_depth > m_wipe_tower_depth - m_perimeter_width) @@ -1356,7 +1364,16 @@ void WipeTower::plan_tower() if (m_plan[i].depth - this_layer_depth < 2*m_perimeter_width ) m_plan[i].depth = this_layer_depth; } - } + + if (m_enable_timelapse_print && layer_index == 0) + max_depth_for_all = m_plan[0].depth; + } + + if (m_enable_timelapse_print) { + for (int i = int(m_plan.size()) - 1; i >= 0; i--) { + m_plan[i].depth = max_depth_for_all; + } + } } void WipeTower::save_on_last_wipe() @@ -1474,17 +1491,25 @@ void WipeTower::generate(std::vector> & // BBS: consider both soluable and support properties int idx = first_toolchange_to_nonsoluble_nonsupport (layer.tool_changes); ToolChangeResult finish_layer_tcr; + ToolChangeResult timelapse_wall; 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(); + if (m_enable_timelapse_print) { + timelapse_wall = only_generate_out_wall(); + } + finish_layer_tcr = finish_layer(m_enable_timelapse_print ? false : true); } for (int i=0; i> & layer_result[idx] = merge_tcr(layer_result[idx], finish_layer_tcr); } + if (m_enable_timelapse_print) { + layer_result.insert(layer_result.begin(), std::move(timelapse_wall)); + } + result.emplace_back(std::move(layer_result)); } } +WipeTower::ToolChangeResult WipeTower::only_generate_out_wall() +{ + size_t old_tool = m_current_tool; + + m_extrusion_flow = 0.038f; // hard code + 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 - (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)); + + // Slow down on the 1st layer. + bool first_layer = is_first_layer(); + // BBS: speed up perimeter speed to 90mm/s for non-first layer + float feedrate = first_layer ? std::min(m_first_layer_speed * 60.f, 5400.f) : 5400.f; + float fill_box_y = m_layer_info->toolchanges_depth() + m_perimeter_width; + box_coordinates fill_box(Vec2f(m_perimeter_width, fill_box_y), m_wipe_tower_width - 2 * m_perimeter_width, m_layer_info->depth - fill_box_y); + + 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; + + // 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()); + + // outer perimeter (always): + // BBS + 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); + wt_box = align_perimeter(wt_box); + writer.rectangle(wt_box, feedrate); + + // Now prepare future wipe. box contains rectangle that was extruded last (ccw). + Vec2f target = (writer.pos() == wt_box.ld ? wt_box.rd : (writer.pos() == wt_box.rd ? wt_box.ru : (writer.pos() == wt_box.ru ? wt_box.lu : wt_box.ld))); + writer.add_wipe_point(writer.pos()).add_wipe_point(target); + + // Ask our writer about how much material was consumed. + // Skip this in case the layer is sparse and config option to not print sparse layers is enabled. + if (!m_no_sparse_layers || toolchanges_on_layer) + 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, true, 0.f); +} + } // namespace Slic3r diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index dd652a4649..271e9180f4 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -152,6 +152,8 @@ public: // Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result" void generate(std::vector> &result); + WipeTower::ToolChangeResult only_generate_out_wall(); + float get_depth() const { return m_wipe_tower_depth; } float get_brim_width() const { return m_wipe_tower_brim_width_real; } @@ -263,7 +265,7 @@ private: return m_filpar[0].filament_area; // all extruders are assumed to have the same filament diameter at this point } - + bool m_enable_timelapse_print = false; bool m_semm = true; // Are we using a single extruder multimaterial printer? Vec2f m_wipe_tower_pos; // Left front corner of the wipe tower in mm. float m_wipe_tower_width; // Width of the wipe tower. diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index 751e594583..781d8cb140 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -82,6 +82,44 @@ double distance_to(const L &line, const Vec, Scalar> &point) return std::sqrt(distance_to_squared(line, point)); } +// Returns a squared distance to the closest point on the infinite. +// Returned nearest_point (and returned squared distance to this point) could be beyond the 'a' and 'b' ends of the segment. +template +double distance_to_infinite_squared(const L &line, const Vec, Scalar> &point, Vec, Scalar> *closest_point) +{ + const Vec, double> v = (get_b(line) - get_a(line)).template cast(); + const Vec, double> va = (point - get_a(line)).template cast(); + const double l2 = v.squaredNorm(); // avoid a sqrt + if (l2 == 0.) { + // a == b case + *closest_point = get_a(line); + return va.squaredNorm(); + } + // Consider the line extending the segment, parameterized as a + t (b - a). + // We find projection of this point onto the line. + // It falls where t = [(this-a) . (b-a)] / |b-a|^2 + const double t = va.dot(v) / l2; + *closest_point = (get_a(line).template cast() + t * v).template cast>(); + return (t * v - va).squaredNorm(); +} + +// Returns a squared distance to the closest point on the infinite. +// Closest point (and returned squared distance to this point) could be beyond the 'a' and 'b' ends of the segment. +template +double distance_to_infinite_squared(const L &line, const Vec, Scalar> &point) +{ + Vec, Scalar> nearest_point; + return distance_to_infinite_squared(line, point, &nearest_point); +} + +// Returns a distance to the closest point on the infinite. +// Closest point (and returned squared distance to this point) could be beyond the 'a' and 'b' ends of the segment. +template +double distance_to_infinite(const L &line, const Vec, Scalar> &point) +{ + return std::sqrt(distance_to_infinite_squared(line, point)); +} + } // namespace line_alg class Line @@ -102,6 +140,7 @@ public: double distance_to_squared(const Point &point) const { return distance_to_squared(point, this->a, this->b); } double distance_to_squared(const Point &point, Point *closest_point) const { return line_alg::distance_to_squared(*this, point, closest_point); } double distance_to(const Point &point) const { return distance_to(point, this->a, this->b); } + double distance_to_infinite_squared(const Point &point, Point *closest_point) const { return line_alg::distance_to_infinite_squared(*this, point, closest_point); } double perp_distance_to(const Point &point) const; bool parallel_to(double angle) const; bool parallel_to(const Line& line) const; @@ -122,6 +161,11 @@ public: static inline double distance_to_squared(const Point &point, const Point &a, const Point &b) { return line_alg::distance_to_squared(Line{a, b}, Vec<2, coord_t>{point}); } static double distance_to(const Point &point, const Point &a, const Point &b) { return sqrt(distance_to_squared(point, a, b)); } + // Returns a distance to the closest point on the infinite. + // Closest point (and returned squared distance to this point) could be beyond the 'a' and 'b' ends of the segment. + static inline double distance_to_infinite_squared(const Point &point, const Point &a, const Point &b) { return line_alg::distance_to_infinite_squared(Line{a, b}, Vec<2, coord_t>{point}); } + static double distance_to_infinite(const Point &point, const Point &a, const Point &b) { return sqrt(distance_to_infinite_squared(point, a, b)); } + Point a; Point b; diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index b9aca56de6..4629998021 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -237,8 +237,7 @@ enum class ModelVolumeType : int { NEGATIVE_VOLUME, PARAMETER_MODIFIER, SUPPORT_BLOCKER, - SUPPORT_ENFORCER, - TIMELAPSE_WIPE_TOWER + SUPPORT_ENFORCER }; enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipLower, CutToParts }; @@ -271,7 +270,6 @@ public: LayerHeightProfile layer_height_profile; // Whether or not this object is printable bool printable; - bool is_timelapse_wipe_tower = false; // This vector holds position of selected support points for SLA. The data are // saved in mesh coordinates to allow using them for several instances. diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index 6501799029..7e280d76b2 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -138,11 +138,9 @@ ArrangePolygon get_instance_arrange_poly(ModelInstance* instance, const Slic3r:: ap.print_temp = config.opt_int("nozzle_temperature", ap.extrude_ids.back() - 1); if (config.has("nozzle_temperature_initial_layer")) //get the nozzle_temperature_initial_layer ap.first_print_temp = config.opt_int("nozzle_temperature_initial_layer", ap.extrude_ids.back() - 1); - // BBS: since first_bed_temp packs all 3 temperatures, vitrify_temp should follow same routine + if (config.has("temperature_vitrification")) { - int tmp = config.opt_int("temperature_vitrification", ap.extrude_ids.back() - 1); - for (int i = 0; i < BedType::btCount; i++) - ap.vitrify_temp += tmp * pow(100, BedType::btCount - i - 1); + ap.vitrify_temp = config.opt_int("temperature_vitrification", ap.extrude_ids.back() - 1); } // get brim width diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index ec60065098..0cdef48a4c 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -396,9 +396,8 @@ void PerimeterGenerator::process() int loop_number = this->config->wall_loops + surface.extra_perimeters - 1; // 0-indexed loops if (this->layer_id == 0 && this->config->only_one_wall_first_layer) loop_number = 0; - - // BBS: force the topmost layer to be one wall - if (loop_number > 0 && this->upper_slices == nullptr) + //BBS: set the topmost layer to be one wall + if (loop_number > 0 && config->only_one_wall_top && this->upper_slices == nullptr) loop_number = 0; ExPolygons last = union_ex(surface.expolygon.simplify_p(surface_simplify_resolution)); diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index d17ee720e0..010692eff7 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -163,6 +163,7 @@ public: Point rotated(double angle) const { Point res(*this); res.rotate(angle); return res; } Point rotated(double cos_a, double sin_a) const { Point res(*this); res.rotate(cos_a, sin_a); return res; } Point rotated(double angle, const Point ¢er) const { Point res(*this); res.rotate(angle, center); return res; } + Point rotate_90_degree_ccw() const { return Point(-this->y(), this->x()); } int nearest_point_index(const Points &points) const; int nearest_point_index(const PointConstPtrs &points) const; int nearest_point_index(const PointPtrs &points) const; @@ -245,6 +246,15 @@ inline bool has_duplicate_successive_points_closed(const std::vector &pts return has_duplicate_successive_points(pts) || (pts.size() >= 2 && pts.front() == pts.back()); } +inline bool shorter_then(const Point& p0, const coord_t len) +{ + if (p0.x() > len || p0.x() < -len) + return false; + if (p0.y() > len || p0.y() < -len) + return false; + return p0.cast().squaredNorm() <= Slic3r::sqr(int64_t(len)); +} + namespace int128 { // Exact orientation predicate, // returns +1: CCW, 0: collinear, -1: CW. diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 3473b83cec..4331192c49 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1577,6 +1577,9 @@ void Print::finalize_first_layer_convex_hull() // Wipe tower support. bool Print::has_wipe_tower() const { + if (enable_timelapse_print()) + return true; + return ! m_config.spiral_mode.value && m_config.enable_prime_tower.value && @@ -1591,18 +1594,25 @@ const WipeTowerData& Print::wipe_tower_data(size_t filaments_cnt) const double width = m_config.prime_tower_width; double layer_height = 0.2; // hard code layer height double wipe_volume = m_config.prime_volume; - const_cast(this)->m_wipe_tower_data.depth = wipe_volume * (filaments_cnt - 1) / (layer_height * width); + if (filaments_cnt == 1 && enable_timelapse_print()) { + const_cast(this)->m_wipe_tower_data.depth = wipe_volume / (layer_height * width); + } else { + const_cast(this)->m_wipe_tower_data.depth = wipe_volume * (filaments_cnt - 1) / (layer_height * width); + } const_cast(this)->m_wipe_tower_data.brim_width = m_config.prime_tower_brim_width; } return m_wipe_tower_data; } +bool Print::enable_timelapse_print() const +{ + return m_config.timelapse_no_toolhead.value; +} + void Print::_make_wipe_tower() { m_wipe_tower_data.clear(); - if (! this->has_wipe_tower()) - return; // Get wiping matrix to get number of extruders and convert vector to vector: std::vector flush_matrix(cast(m_config.flush_volumes_matrix.values)); @@ -1618,7 +1628,18 @@ void Print::_make_wipe_tower() // BBS: priming logic is removed, so don't consider it in tool ordering m_wipe_tower_data.tool_ordering = ToolOrdering(*this, (unsigned int)-1, false); - if (! m_wipe_tower_data.tool_ordering.has_wipe_tower()) + // if enable_timelapse_print(), update all layer_tools parameters(has_wipe_tower, wipe_tower_partitions) + if (enable_timelapse_print()) { + std::vector& layer_tools_array = m_wipe_tower_data.tool_ordering.layer_tools(); + for (LayerTools& layer_tools : layer_tools_array) { + layer_tools.has_wipe_tower = true; + if (layer_tools.wipe_tower_partitions == 0) { + layer_tools.wipe_tower_partitions = 1; + } + } + } + + if (!m_wipe_tower_data.tool_ordering.has_wipe_tower()) // Don't generate any wipe tower. return; @@ -1707,6 +1728,11 @@ void Print::_make_wipe_tower() } } layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this); + + // if enable timelapse, slice all layer + if (enable_timelapse_print()) + continue; + if (&layer_tools == &m_wipe_tower_data.tool_ordering.back() || (&layer_tools + 1)->wipe_tower_partitions == 0) break; } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 15cde3ee69..b07b6719e6 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -416,7 +416,7 @@ private: friend class Print; PrintObject(Print* print, ModelObject* model_object, const Transform3d& trafo, PrintInstances&& instances); - ~PrintObject() { if (m_shared_regions && -- m_shared_regions->m_ref_cnt == 0) delete m_shared_regions; } + ~PrintObject(); void config_apply(const ConfigBase &other, bool ignore_nonexistent = false) { m_config.apply(other, ignore_nonexistent); } void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) { m_config.apply_only(other, keys, ignore_nonexistent); } @@ -684,6 +684,8 @@ public: const WipeTowerData& wipe_tower_data(size_t filaments_cnt = 0) const; const ToolOrdering& tool_ordering() const { return m_tool_ordering; } + bool enable_timelapse_print() const; + std::string output_filename(const std::string &filename_base = std::string()) const override; size_t num_print_regions() const throw() { return m_print_regions.size(); } diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 61297fe567..0e52afba18 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -577,7 +577,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Decrease this value slightly(for example 0.9) to reduce the amount of material for bridge, " "to improve sag"); def->min = 0; - def->max = 1; + def->max = 2.0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(1)); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index c67ae6f334..8c1c94b48d 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -54,7 +54,7 @@ enum AuthorizationType { enum InfillPattern : int { ipConcentric, ipRectilinear, ipGrid, ipLine, ipCubic, ipTriangles, ipStars, ipGyroid, ipHoneycomb, ipAdaptiveCubic, ipMonotonic, ipMonotonicLine, ipAlignedRectilinear, ip3DHoneycomb, - ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipSupportCubic, ipSupportBase, ipConcentricGapFill, + ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipSupportCubic, ipSupportBase, ipConcentricGapFill, ipConcentricInternal, #if HAS_LIGHTNING_INFILL ipLightning, #endif // HAS_LIGHTNING_INFILL diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 823fe34967..849712c475 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -84,6 +84,14 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, const Transfor this->set_instances(std::move(instances)); } +PrintObject::~PrintObject() +{ + if (m_shared_regions && -- m_shared_regions->m_ref_cnt == 0) delete m_shared_regions; + clear_layers(); + clear_support_layers(); + clear_tree_support_layers(); +} + PrintBase::ApplyStatus PrintObject::set_instances(PrintInstances &&instances) { for (PrintInstance &i : instances) @@ -659,8 +667,7 @@ bool PrintObject::invalidate_state_by_config_options( //BBS || opt_key == "adaptive_layer_height" || opt_key == "raft_layers" - || opt_key == "raft_contact_distance" - || opt_key == "timelapse_no_toolhead") { + || opt_key == "raft_contact_distance") { steps.emplace_back(posSlice); } else if ( opt_key == "elefant_foot_compensation" @@ -2376,9 +2383,9 @@ void PrintObject::remove_bridges_from_contacts( int x0 = bbox.min.x(); int x1 = bbox.max.x(); int y0 = bbox.min.y(); - int y1 = bbox.max.y(); + int y1 = bbox.max.y(); const int grid_lw = int(w/2); // grid line width - + #if 1 if (fabs(surface.bridge_angle-0)= SCALED_EPSILON && line.b_width >= SCALED_EPSILON); const coordf_t line_len = line.length(); if (line_len < SCALED_EPSILON) continue; double thickness_delta = fabs(line.a_width - line.b_width); if (thickness_delta > tolerance) { - const unsigned int segments = (unsigned int)ceil(thickness_delta / tolerance); + const auto segments = (unsigned int)ceil(thickness_delta / tolerance); const coordf_t seg_len = line_len / segments; Points pp; std::vector width; @@ -26,7 +27,7 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline& thi for (size_t j = 1; j < segments; ++j) { pp.push_back((line.a.cast() + (line.b - line.a).cast().normalized() * (j * seg_len)).cast()); - coordf_t w = line.a_width + (j * seg_len) * (line.b_width - line.a_width) / line_len; + coordf_t w = line.a_width + (j*seg_len) * (line.b_width-line.a_width) / line_len; width.push_back(w); width.push_back(w); } @@ -34,48 +35,47 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline& thi width.push_back(line.b_width); assert(pp.size() == segments + 1u); - assert(width.size() == segments * 2); + assert(width.size() == segments*2); } // delete this line and insert new ones lines.erase(lines.begin() + i); for (size_t j = 0; j < segments; ++j) { - ThickLine new_line(pp[j], pp[j + 1]); - new_line.a_width = width[2 * j]; - new_line.b_width = width[2 * j + 1]; + ThickLine new_line(pp[j], pp[j+1]); + new_line.a_width = width[2*j]; + new_line.b_width = width[2*j+1]; lines.insert(lines.begin() + i + j, new_line); } - --i; + -- i; continue; } - const double w = fmax(line.a_width, line.b_width); + const double w = fmax(line.a_width, line.b_width); + const Flow new_flow = (role == erOverhangPerimeter && flow.bridge()) ? flow : flow.with_width(unscale(w) + flow.height() * float(1. - 0.25 * PI)); if (path.polyline.points.empty()) { path.polyline.append(line.a); 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 new_flow = flow.with_width(unscale(w) + flow.height() * float(1. - 0.25 * PI)); -#ifdef SLIC3R_DEBUG + #ifdef SLIC3R_DEBUG printf(" filling %f gap\n", flow.width); -#endif - 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); - if (thickness_delta <= tolerance) { - // the width difference between this line and the current flow width is - // within the accepted tolerance + #endif + path.mm3_per_mm = new_flow.mm3_per_mm(); + path.width = new_flow.width(); + path.height = new_flow.height(); + } else { + assert(path.width >= EPSILON); + thickness_delta = scaled(fabs(path.width - new_flow.width())); + if (thickness_delta <= merge_tolerance) { + // the width difference between this line and the current flow + // (of the previous line) width is within the accepted tolerance path.polyline.append(line.b); - } - else { + } else { // we need to initialize a new line paths.emplace_back(std::move(path)); path = ExtrusionPath(role); - --i; + -- i; } } } diff --git a/src/libslic3r/VariableWidth.hpp b/src/libslic3r/VariableWidth.hpp index f00bac483b..bfca418a36 100644 --- a/src/libslic3r/VariableWidth.hpp +++ b/src/libslic3r/VariableWidth.hpp @@ -6,6 +6,7 @@ #include "Flow.hpp" namespace Slic3r { + ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline& thick_polyline, ExtrusionRole role, const Flow& flow, const float tolerance, const float merge_tolerance); void variable_width(const ThickPolylines& polylines, ExtrusionRole role, const Flow& flow, std::vector& out); } diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index f4e122e00a..b112e58924 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -72,6 +72,8 @@ set(SLIC3R_GUI_SOURCES GUI/Widgets/wxStaticText2.hpp GUI/AboutDialog.cpp GUI/AboutDialog.hpp + GUI/NetworkTestDialog.cpp + GUI/NetworkTestDialog.hpp GUI/AuxiliaryDialog.cpp GUI/AuxiliaryDialog.hpp GUI/Auxiliary.cpp @@ -217,8 +219,8 @@ set(SLIC3R_GUI_SOURCES GUI/MonitorPage.hpp GUI/StatusPanel.cpp GUI/StatusPanel.hpp - GUI/UpdateErrorMessage.cpp - GUI/UpdateErrorMessage.hpp + GUI/HMS.hpp + GUI/HMS.cpp GUI/SliceInfoPanel.cpp GUI/SliceInfoPanel.hpp GUI/CameraPopup.cpp @@ -426,8 +428,8 @@ source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SLIC3R_GUI_SOURCES}) encoding_check(libslic3r_gui) -target_link_libraries(libslic3r_gui libslic3r cereal imgui minilzo GLEW::GLEW OpenGL::GL hidapi libcurl OpenSSL::SSL OpenSSL::Crypto -${wxWidgets_LIBRARIES} glfw) +target_link_libraries(libslic3r_gui libslic3r cereal imgui minilzo GLEW::GLEW OpenGL::GL hidapi ${wxWidgets_LIBRARIES} glfw libcurl OpenSSL::SSL OpenSSL::Crypto) +#target_link_libraries(libslic3r_gui libslic3r cereal imgui minilzo GLEW::GLEW OpenGL::GL hidapi libcurl OpenSSL::SSL OpenSSL::Crypto ${wxWidgets_LIBRARIES} glfw) if (MSVC) target_link_libraries(libslic3r_gui Setupapi.lib) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 1aae500a38..e5d9cc2467 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -83,6 +83,21 @@ std::vector> get_extruders_colors() return colors_out; } +std::array adjust_color_for_rendering(const std::array& colors) +{ + if ((colors[0] < 0.1) && (colors[1] < 0.1) && (colors[2] < 0.1)) + { + std::array new_color; + new_color[0] = 0.1; + new_color[1] = 0.1; + new_color[2] = 0.1; + new_color[3] = colors[3]; + return new_color; + } + + return colors; +} + namespace Slic3r { #if ENABLE_SMOOTH_NORMALS @@ -488,8 +503,11 @@ void GLVolume::set_render_color() else if (is_outside && shader_outside_printer_detection_enabled) set_render_color(OUTSIDE_COLOR); #endif - else - set_render_color(color); + else { + //to make black not too hard too see + std::array new_color = adjust_color_for_rendering(color); + set_render_color(new_color); + } } if (force_transparent) @@ -708,13 +726,24 @@ void GLVolume::render(bool with_outline) const ModelObject* mo = model_objects[object_idx()]; ModelVolume* mv = mo->volumes[volume_idx()]; int extruder_id = mv->extruder_id(); - shader->set_uniform("uniform_color", colors[extruder_id - 1]); + //shader->set_uniform("uniform_color", colors[extruder_id - 1]); + //to make black not too hard too see + std::array new_color = adjust_color_for_rendering(colors[extruder_id - 1]); + shader->set_uniform("uniform_color", new_color); } else { - if (idx <= colors.size()) - shader->set_uniform("uniform_color", colors[idx - 1]); - else - shader->set_uniform("uniform_color", colors[0]); + if (idx <= colors.size()) { + //shader->set_uniform("uniform_color", colors[idx - 1]); + //to make black not too hard too see + std::array new_color = adjust_color_for_rendering(colors[idx - 1]); + shader->set_uniform("uniform_color", new_color); + } + else { + //shader->set_uniform("uniform_color", colors[0]); + //to make black not too hard too see + std::array new_color = adjust_color_for_rendering(colors[0]); + shader->set_uniform("uniform_color", new_color); + } } } iva.render(this->tverts_range, this->qverts_range); @@ -913,13 +942,23 @@ void GLVolume::simple_render(GLShaderProgram* shader, ModelObjectPtrs& model_obj if (shader) { if (idx == 0) { int extruder_id = model_volume->extruder_id(); - shader->set_uniform("uniform_color", extruder_colors[extruder_id - 1]); + //to make black not too hard too see + std::array new_color = adjust_color_for_rendering(extruder_colors[extruder_id - 1]); + shader->set_uniform("uniform_color", new_color); } else { - if (idx <= extruder_colors.size()) - shader->set_uniform("uniform_color", extruder_colors[idx - 1]); - else - shader->set_uniform("uniform_color", extruder_colors[0]); + if (idx <= extruder_colors.size()) { + //shader->set_uniform("uniform_color", extruder_colors[idx - 1]); + //to make black not too hard too see + std::array new_color = adjust_color_for_rendering(extruder_colors[idx - 1]); + shader->set_uniform("uniform_color", new_color); + } + else { + //shader->set_uniform("uniform_color", extruder_colors[0]); + //to make black not too hard too see + std::array new_color = adjust_color_for_rendering(extruder_colors[0]); + shader->set_uniform("uniform_color", new_color); + } } } iva.render(this->tverts_range, this->qverts_range); @@ -978,8 +1017,10 @@ void GLWipeTowerVolume::render(bool with_outline) const GLShaderProgram* shader = GUI::wxGetApp().get_current_shader(); for (int i = 0; i < m_colors.size(); i++) { - if (shader) - shader->set_uniform("uniform_color", m_colors[i]); + if (shader) { + std::array new_color = adjust_color_for_rendering(m_colors[i]); + shader->set_uniform("uniform_color", new_color); + } this->iva_per_colors[i].render(); } diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index ac3fa356d1..2eaf09ed83 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -31,6 +31,8 @@ #define glcheck() #endif // HAS_GLSAFE extern std::vector> get_extruders_colors(); +extern std::array adjust_color_for_rendering(const std::array& colors); + namespace Slic3r { class SLAPrintObject; @@ -373,7 +375,6 @@ public: bool is_modifier : 1; // Wheter or not this volume has been generated from the wipe tower bool is_wipe_tower : 1; - bool is_timelapse_wipe_tower : 1; // Wheter or not this volume has been generated from an extrusion path bool is_extrusion_path : 1; // Wheter or not to always render this volume using its own alpha diff --git a/src/slic3r/GUI/Auxiliary.cpp b/src/slic3r/GUI/Auxiliary.cpp index fb49f4a989..73d5baa94b 100644 --- a/src/slic3r/GUI/Auxiliary.cpp +++ b/src/slic3r/GUI/Auxiliary.cpp @@ -502,7 +502,7 @@ AuFolderPanel::AuFolderPanel(wxWindow *parent, AuxiliaryFolderType type, wxWindo m_button_add->SetBackgroundColor(btn_bg_white); m_button_add->SetBorderColor(btn_bd_white); m_button_add->SetMinSize(wxSize(-1, FromDIP(24))); - m_button_add->SetCornerRadius(12); + m_button_add->SetCornerRadius(FromDIP(12)); m_button_add->SetFont(Label::Body_14); // m_button_add->Bind(wxEVT_LEFT_UP, &AuxiliaryPanel::on_add, this); diff --git a/src/slic3r/GUI/BBLStatusBar.cpp b/src/slic3r/GUI/BBLStatusBar.cpp index 3bc0236198..e0cde171cc 100644 --- a/src/slic3r/GUI/BBLStatusBar.cpp +++ b/src/slic3r/GUI/BBLStatusBar.cpp @@ -51,8 +51,8 @@ BBLStatusBar::BBLStatusBar(wxWindow *parent, int id) m_sizer->Add(m_object_info_sizer, 1, wxEXPAND | wxALL | wxALIGN_LEFT, 5); m_sizer->Add(m_slice_info_sizer, 1, wxEXPAND | wxALL | wxALIGN_LEFT, 5); m_sizer->Add(m_status_text, 1, wxEXPAND | wxALL | wxALIGN_LEFT, 5); - m_sizer->Add(m_prog, 0, wxEXPAND | wxLEFT | wxALL | wxALIGN_RIGHT, 5); - m_sizer->Add(m_cancelbutton, 0, wxEXPAND | wxALL | wxALIGN_RIGHT, 5); + m_sizer->Add(m_prog, 0, wxEXPAND | wxLEFT | wxALL, 5); + m_sizer->Add(m_cancelbutton, 0, wxEXPAND | wxALL, 5); m_sizer->SetSizeHints(m_self); m_self->SetSizer(m_sizer); diff --git a/src/slic3r/GUI/BBLStatusBarSend.cpp b/src/slic3r/GUI/BBLStatusBarSend.cpp index f73c1c540d..5d9eab1370 100644 --- a/src/slic3r/GUI/BBLStatusBarSend.cpp +++ b/src/slic3r/GUI/BBLStatusBarSend.cpp @@ -46,7 +46,7 @@ BBLStatusBarSend::BBLStatusBarSend(wxWindow *parent, int id) m_cancelbutton->SetMinSize(wxSize(m_self->FromDIP(64), m_self->FromDIP(24))); m_cancelbutton->SetTextColor(wxColour(107, 107, 107)); m_cancelbutton->SetBackgroundColor(wxColour(255, 255, 255)); - m_cancelbutton->SetCornerRadius(12); + m_cancelbutton->SetCornerRadius(m_self->FromDIP(12)); m_cancelbutton->Bind(wxEVT_BUTTON, [this](wxCommandEvent &evt) { m_was_cancelled = true; diff --git a/src/slic3r/GUI/BBLTopbar.cpp b/src/slic3r/GUI/BBLTopbar.cpp index d80fc478ec..801a5a6862 100644 --- a/src/slic3r/GUI/BBLTopbar.cpp +++ b/src/slic3r/GUI/BBLTopbar.cpp @@ -245,7 +245,7 @@ void BBLTopbar::Init(wxFrame* parent) this->AddStretchSpacer(1); m_title_item = this->AddLabel(ID_TITLE, "", FromDIP(TOPBAR_TITLE_WIDTH)); - m_title_item->SetAlignment(wxCENTER); + m_title_item->SetAlignment(wxALIGN_CENTRE); this->AddSpacer(FromDIP(10)); this->AddStretchSpacer(1); @@ -407,7 +407,7 @@ void BBLTopbar::SetTitle(wxString title) title = wxControl::Ellipsize(title, dc, wxELLIPSIZE_END, FromDIP(TOPBAR_TITLE_WIDTH)); m_title_item->SetLabel(title); - m_title_item->SetAlignment(wxALIGN_CENTRE_HORIZONTAL); + m_title_item->SetAlignment(wxALIGN_CENTRE); this->Refresh(); } @@ -493,7 +493,7 @@ void BBLTopbar::OnFullScreen(wxAuiToolBarEvent& event) m_frame->Restore(); } else { - wxDisplay display(wxDisplay::GetFromWindow(this)); + wxDisplay display(this); auto size = display.GetClientArea().GetSize(); m_frame->SetMaxSize(size + wxSize{16, 16}); m_normalRect = m_frame->GetRect(); @@ -524,7 +524,7 @@ void BBLTopbar::OnMouseLeftDClock(wxMouseEvent& mouse) m_frame->Restore(); } else { - wxDisplay display(wxDisplay::GetFromWindow(this)); + wxDisplay display(this); auto size = display.GetClientArea().GetSize(); m_frame->SetMaxSize(size + wxSize{16, 16}); m_normalRect = m_frame->GetRect(); diff --git a/src/slic3r/GUI/BindDialog.cpp b/src/slic3r/GUI/BindDialog.cpp index b47a7a013b..630ced8e91 100644 --- a/src/slic3r/GUI/BindDialog.cpp +++ b/src/slic3r/GUI/BindDialog.cpp @@ -32,7 +32,7 @@ namespace GUI { m_panel_left = new StaticBox(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(201), FromDIP(212)), wxBORDER_NONE); m_panel_left->SetMinSize(wxSize(FromDIP(201), FromDIP(212))); - m_panel_left->SetCornerRadius(8); + m_panel_left->SetCornerRadius(FromDIP(8)); m_panel_left->SetBackgroundColor(BIND_DIALOG_GREY200); wxBoxSizer *m_sizere_left_h = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer *m_sizere_left_v= new wxBoxSizer(wxVERTICAL); @@ -55,7 +55,7 @@ namespace GUI { m_panel_right = new StaticBox(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(201), FromDIP(212)), wxBORDER_NONE); m_panel_right->SetMinSize(wxSize(FromDIP(201), FromDIP(212))); - m_panel_right->SetCornerRadius(8); + m_panel_right->SetCornerRadius(FromDIP(8)); m_panel_right->SetBackgroundColor(BIND_DIALOG_GREY200); m_user_name = new wxStaticText(m_panel_right, wxID_ANY, wxEmptyString); @@ -136,7 +136,7 @@ namespace GUI { m_button_bind->SetTextColor(*wxWHITE); m_button_bind->SetSize(BIND_DIALOG_BUTTON_SIZE); m_button_bind->SetMinSize(BIND_DIALOG_BUTTON_SIZE); - m_button_bind->SetCornerRadius(10); + m_button_bind->SetCornerRadius(FromDIP(12)); StateColor btn_bg_white(std::pair(wxColour(206, 206, 206), StateColor::Hovered), @@ -148,7 +148,7 @@ namespace GUI { m_button_cancel->SetSize(BIND_DIALOG_BUTTON_SIZE); m_button_cancel->SetMinSize(BIND_DIALOG_BUTTON_SIZE); m_button_cancel->SetTextColor(BIND_DIALOG_GREY900); - m_button_cancel->SetCornerRadius(10); + m_button_cancel->SetCornerRadius(FromDIP(12)); m_sizer_button->Add(m_button_bind, 0, wxALIGN_CENTER, 0); m_sizer_button->Add(0, 0, 0, wxLEFT, FromDIP(13)); @@ -273,7 +273,7 @@ UnBindMachineDilaog::UnBindMachineDilaog(Plater *plater /*= nullptr*/) auto m_panel_left = new StaticBox(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(201), FromDIP(212)), wxBORDER_NONE); m_panel_left->SetMinSize(wxSize(FromDIP(201), FromDIP(212))); - m_panel_left->SetCornerRadius(8); + m_panel_left->SetCornerRadius(FromDIP(8)); m_panel_left->SetBackgroundColor(BIND_DIALOG_GREY200); wxBoxSizer *m_sizere_left_h = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer *m_sizere_left_v= new wxBoxSizer(wxVERTICAL); @@ -297,7 +297,7 @@ UnBindMachineDilaog::UnBindMachineDilaog(Plater *plater /*= nullptr*/) auto m_panel_right = new StaticBox(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(201), FromDIP(212)), wxBORDER_NONE); m_panel_right->SetMinSize(wxSize(FromDIP(201), FromDIP(212))); - m_panel_right->SetCornerRadius(8); + m_panel_right->SetCornerRadius(FromDIP(8)); m_panel_right->SetBackgroundColor(BIND_DIALOG_GREY200); m_user_name = new wxStaticText(m_panel_right, wxID_ANY, wxEmptyString); m_user_name->SetBackgroundColour(BIND_DIALOG_GREY200); @@ -374,7 +374,7 @@ UnBindMachineDilaog::UnBindMachineDilaog(Plater *plater /*= nullptr*/) m_button_unbind->SetTextColor(*wxWHITE); m_button_unbind->SetSize(BIND_DIALOG_BUTTON_SIZE); m_button_unbind->SetMinSize(BIND_DIALOG_BUTTON_SIZE); - m_button_unbind->SetCornerRadius(10); + m_button_unbind->SetCornerRadius(FromDIP(12)); StateColor btn_bg_white(std::pair(wxColour(206, 206, 206), StateColor::Hovered), @@ -386,7 +386,7 @@ UnBindMachineDilaog::UnBindMachineDilaog(Plater *plater /*= nullptr*/) m_button_cancel->SetSize(BIND_DIALOG_BUTTON_SIZE); m_button_cancel->SetMinSize(BIND_DIALOG_BUTTON_SIZE); m_button_cancel->SetTextColor(BIND_DIALOG_GREY900); - m_button_cancel->SetCornerRadius(10); + m_button_cancel->SetCornerRadius(FromDIP(12)); m_sizer_button->Add(m_button_unbind, 0, wxALIGN_CENTER, 0); m_sizer_button->Add(0, 0, 0, wxLEFT, FromDIP(13)); diff --git a/src/slic3r/GUI/Calibration.cpp b/src/slic3r/GUI/Calibration.cpp index 5994566be1..8b10e1198a 100644 --- a/src/slic3r/GUI/Calibration.cpp +++ b/src/slic3r/GUI/Calibration.cpp @@ -113,7 +113,7 @@ CalibrationDialog::CalibrationDialog(Plater *plater) calibration_panel->SetSizer(calibration_sizer); calibration_panel->Layout(); - calibration_sizer->Add(m_calibration_flow, 0, wxALIGN_CENTER_HORIZONTAL | wxEXPAND, 0); + calibration_sizer->Add(m_calibration_flow, 0, wxEXPAND, 0); StateColor btn_bg_green(std::pair(AMS_CONTROL_DISABLE_COLOUR, StateColor::Disabled), std::pair(wxColour(27, 136, 68), StateColor::Pressed), std::pair(wxColour(61, 203, 115), StateColor::Hovered), std::pair(AMS_CONTROL_BRAND_COLOUR, StateColor::Normal)); @@ -138,7 +138,7 @@ CalibrationDialog::CalibrationDialog(Plater *plater) cali_right_panel->SetSizer(cali_right_sizer_h); cali_right_panel->Layout(); - sizer_body->Add(cali_right_panel, 0, wxALIGN_CENTER_HORIZONTAL | wxEXPAND, 0); + sizer_body->Add(cali_right_panel, 0, wxEXPAND, 0); body_panel->SetSizer(sizer_body); body_panel->Layout(); diff --git a/src/slic3r/GUI/DeviceManager.cpp b/src/slic3r/GUI/DeviceManager.cpp index 7b7f48769d..467755c4e3 100644 --- a/src/slic3r/GUI/DeviceManager.cpp +++ b/src/slic3r/GUI/DeviceManager.cpp @@ -209,6 +209,18 @@ bool HMSItem::parse_hms_info(unsigned attr, unsigned code) return result; } +std::string HMSItem::get_long_error_code() +{ + char buf[64]; + ::sprintf(buf, "%02X%02X%02X00000%1X%04X", + this->module_id, + this->module_num, + this->part_id, + (int)this->msg_level, + this->msg_code); + return std::string(buf); +} + wxString HMSItem::get_module_name(ModuleID module_id) { switch (module_id) diff --git a/src/slic3r/GUI/DeviceManager.hpp b/src/slic3r/GUI/DeviceManager.hpp index ba464ac968..241e8424fb 100644 --- a/src/slic3r/GUI/DeviceManager.hpp +++ b/src/slic3r/GUI/DeviceManager.hpp @@ -240,6 +240,8 @@ public: HMSMessageLevel msg_level = HMS_UNKNOWN; int msg_code = 0; bool parse_hms_info(unsigned attr, unsigned code); + std::string get_long_error_code(); + static wxString get_module_name(ModuleID module_id); static wxString get_hms_msg_level_str(HMSMessageLevel level); }; diff --git a/src/slic3r/GUI/DownloadProgressDialog.cpp b/src/slic3r/GUI/DownloadProgressDialog.cpp index 2e7bbcef75..f1af5269d1 100644 --- a/src/slic3r/GUI/DownloadProgressDialog.cpp +++ b/src/slic3r/GUI/DownloadProgressDialog.cpp @@ -44,7 +44,7 @@ DownloadProgressDialog::DownloadProgressDialog(wxString title) m_panel_download->SetSize(wxSize(FromDIP(340), -1)); m_panel_download->SetMinSize(wxSize(FromDIP(340), -1)); m_panel_download->SetMaxSize(wxSize(FromDIP(340), -1)); - m_sizer_main->Add(m_panel_download, 0, wxALIGN_CENTER_VERTICAL|wxALL, FromDIP(20)); + m_sizer_main->Add(m_panel_download, 0, wxALL, FromDIP(20)); m_sizer_main->Add(0, 0, 1, wxBOTTOM, 10); SetSizer(m_sizer_main); diff --git a/src/slic3r/GUI/ExtraRenderers.cpp b/src/slic3r/GUI/ExtraRenderers.cpp index 70baf49fff..4931a71ea2 100644 --- a/src/slic3r/GUI/ExtraRenderers.cpp +++ b/src/slic3r/GUI/ExtraRenderers.cpp @@ -171,7 +171,10 @@ wxSize BitmapTextRenderer::GetSize() const } else #endif // SUPPORTS_MARKUP && wxHAS_GENERIC_DATAVIEWCTRL + { size = GetTextExtent(m_value.GetText()); + size.x = size.x * 9 / 8; + } if (m_value.GetBitmap().IsOk()) size.x += m_value.GetBitmap().GetWidth() + 4; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 184e328a44..d9d8ef59da 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -3000,7 +3000,8 @@ void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_p const double max_z = print.objects()[0]->model_object()->get_model()->bounding_box().max(2); const PrintConfig& config = print.config(); - if (extruders_count > 1 && config.enable_prime_tower && (config.print_sequence == PrintSequence::ByLayer)) { + if (print.enable_timelapse_print() + || (extruders_count > 1 && config.enable_prime_tower && (config.print_sequence == PrintSequence::ByLayer))) { const float depth = print.wipe_tower_data(extruders_count).depth; const float brim_width = print.wipe_tower_data(extruders_count).brim_width; @@ -3063,8 +3064,10 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool case EViewType::ColorPrint: { if (path.cp_color_id >= static_cast(m_tools.m_tool_colors.size())) color = { 0.5f, 0.5f, 0.5f, 1.0f }; - else + else { color = m_tools.m_tool_colors[path.cp_color_id]; + color = adjust_color_for_rendering(color); + } break; } case EViewType::FilamentId: { @@ -3946,6 +3949,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv //BBS: GUI refactor: move to the right imgui.set_next_window_pos(float(canvas_width - right_margin * m_scale), 0.0f, ImGuiCond_Always, 1.0f, 0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0,0.0)); ImGui::PushStyleColor(ImGuiCol_Separator, ImVec4(1.0f,1.0f,1.0f,0.6f)); ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.00f, 0.68f, 0.26f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.00f, 0.68f, 0.26f, 1.0f)); @@ -3979,8 +3983,15 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv const float percent_bar_size = 0; bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 pos_rect = ImGui::GetCursorScreenPos(); + float window_padding = 4.0f * m_scale; - auto append_item = [icon_size, percent_bar_size, &imgui, imperial_units](EItemType type, const Color &color, const std::string &label, + draw_list->AddRectFilled(ImVec2(pos_rect.x,pos_rect.y - ImGui::GetStyle().WindowPadding.y), + ImVec2(pos_rect.x + ImGui::GetWindowWidth() + ImGui::GetFrameHeight(),pos_rect.y + ImGui::GetFrameHeight() + window_padding * 2.5), + ImGui::GetColorU32(ImVec4(0,0,0,0.3))); + + auto append_item = [icon_size, percent_bar_size, &imgui, imperial_units,&window_padding,&draw_list,this](EItemType type, const Color &color, const std::string &label, bool visible = true, const std::string& time = "", float percent = 0.0f, float max_percent = 0.0f, const std::array& offsets = { 0.0f, 0.0f, 0.0f, 0.0f }, double used_filament_m = 0.0, double used_filament_g = 0.0, std::function callback = nullptr) { @@ -3989,13 +4000,13 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); */ - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - ImVec2 pos = ImGui::GetCursorScreenPos(); - float dummy_size = icon_size; + ImVec2 pos = ImVec2(ImGui::GetCursorScreenPos().x + window_padding * 3, ImGui::GetCursorScreenPos().y); + float dummy_size = icon_size * m_scale; + switch (type) { default: case EItemType::Rect: { - draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 5.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size + 3.0f }, + draw_list->AddRectFilled({ pos.x + 1.0f * m_scale, pos.y + 3.0f * m_scale }, { pos.x + icon_size - 1.0f * m_scale, pos.y + icon_size + 1.0f * m_scale }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); break; } @@ -4019,10 +4030,18 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv } // draw text - ImGui::Dummy({ dummy_size, dummy_size }); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(20.0, 6.0 * m_scale)); + ImGui::Dummy({ dummy_size, 0.0 }); ImGui::SameLine(); if (callback != nullptr) { - if (ImGui::MenuItem(label.c_str())) + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f * m_scale); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0 * m_scale,0.0)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(1.00f, 0.68f, 0.26f, 0.0f)); + ImGui::PushStyleColor(ImGuiCol_BorderActive, ImVec4(0.00f, 0.68f, 0.26f, 1.00f)); + bool b_menu_item = ImGui::BBLMenuItem(label.c_str()); + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(2); + if (b_menu_item) callback(); else { // show tooltip @@ -4111,6 +4130,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv imgui.text(buf); }*/ } + ImGui::PopStyleVar(1); /* BBS GUI refactor */ /*if (!visible) @@ -4143,10 +4163,10 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv auto append_headers = [&imgui](const std::array& texts, const std::array& offsets) { size_t i = 0; for (; i < offsets.size(); i++) { - imgui.text(texts[i]); + imgui.bold_text(texts[i]); ImGui::SameLine(offsets[i]); } - imgui.text(texts[i]); + imgui.bold_text(texts[i]); ImGui::Separator(); }; @@ -4230,6 +4250,9 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv }; //BBS display Color Scheme + ImGui::Dummy({ window_padding, window_padding }); + ImGui::Dummy({ window_padding, window_padding }); + ImGui::SameLine(); std::wstring btn_name; if (m_fold) btn_name = ImGui::UnfoldButtonIcon + boost::nowide::widen(std::string("")); @@ -4247,10 +4270,9 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv ImGui::PopStyleColor(3); ImGui::PopStyleVar(1); ImGui::SameLine(); - ImGui::Text(_u8L("Color Scheme").c_str()); + imgui.bold_text(_u8L("Color Scheme")); push_combo_style(); - float combo_width = ImGui::GetContentRegionAvailWidth() - ImGui::CalcTextSize(_u8L("Color Scheme").c_str()).x - button_width - ImGui::GetStyle().FramePadding.x * 4; - ImGui::PushItemWidth(combo_width); + ImGui::SameLine(); const char* view_type_value = view_type_items_str[m_view_type_sel].c_str(); ImGuiComboFlags flags = 0; @@ -4276,11 +4298,13 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv ImGui::EndCombo(); } pop_combo_style(); + ImGui::SameLine(); + ImGui::Dummy({ window_padding, window_padding }); if (m_fold) { imgui.end(); ImGui::PopStyleColor(6); - ImGui::PopStyleVar(); + ImGui::PopStyleVar(2); return; } @@ -4355,6 +4379,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv } // extrusion paths section -> title + ImGui::Dummy({ window_padding, window_padding }); + ImGui::SameLine(); switch (m_view_type) { case EViewType::FeatureType: @@ -4467,6 +4493,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv case EViewType::Feedrate: { append_range(m_extrusions.ranges.feedrate, 0); ImGui::Spacing(); + ImGui::Dummy({ window_padding, window_padding }); + ImGui::SameLine(); append_headers({_u8L("Options"), "", "", "", _u8L("Display")}, offsets); const bool travel_visible = m_buffers[buffer_id(EMoveType::Travel)].visible; ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 3.0f)); @@ -4596,6 +4624,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv max_len += std::max(ImGui::CalcTextSize(filament_change_str.c_str()).x, ImGui::CalcTextSize(flushed_filament_str.c_str()).x); //BBS: display total flushed filament { + ImGui::Dummy({window_padding, window_padding}); + ImGui::SameLine(); imgui.text(flushed_filament_str + ":"); ImGui::SameLine(max_len); char buf[64]; @@ -4607,6 +4637,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv } //BBS display filament change times { + ImGui::Dummy({window_padding, window_padding}); + ImGui::SameLine(); imgui.text(filament_change_str + ":"); ImGui::SameLine(max_len); char temp_buf[64]; @@ -4962,6 +4994,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv } } ImGui::Dummy(ImVec2(0.0f, ImGui::GetFontSize() * 0.1)); + ImGui::Dummy({ window_padding, window_padding }); + ImGui::SameLine(); imgui.title(time_title); std::string filament_str = _u8L("Filament"); std::string prepare_str = _u8L("Prepare time"); @@ -4976,6 +5010,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv } //BBS display filament cost + ImGui::Dummy({ window_padding, window_padding }); + ImGui::SameLine(); imgui.text(filament_str + ":"); ImGui::SameLine(max_len); @@ -4997,13 +5033,19 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv }; //BBS: start gcode is prepeare time if (role_time(erCustom) != 0.0f) { + ImGui::Dummy({ window_padding, window_padding }); + ImGui::SameLine(); imgui.text(prepare_str + ":"); ImGui::SameLine(max_len); imgui.text(short_time(get_time_dhms(role_time(erCustom)))); } + ImGui::Dummy({ window_padding, window_padding }); + ImGui::SameLine(); imgui.text(print_str + ":"); ImGui::SameLine(max_len); imgui.text(short_time(get_time_dhms(time_mode.time - role_time(erCustom)))); + ImGui::Dummy({ window_padding, window_padding }); + ImGui::SameLine(); imgui.text(total_str + ":"); ImGui::SameLine(max_len); imgui.text(short_time(get_time_dhms(time_mode.time))); @@ -5035,16 +5077,17 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv } } legend_height = ImGui::GetCurrentWindow()->Size.y; - + ImGui::Dummy({ window_padding, window_padding}); imgui.end(); ImGui::PopStyleColor(6); - ImGui::PopStyleVar(); + ImGui::PopStyleVar(2); } void GCodeViewer::push_combo_style() { ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0,8.0)); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.3f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.0f, 0.0f, 0.0f, 0.3f)); ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.0f, 0.0f, 0.0f, 0.3f)); @@ -5056,7 +5099,7 @@ void GCodeViewer::push_combo_style() } void GCodeViewer::pop_combo_style() { - ImGui::PopStyleVar(2); + ImGui::PopStyleVar(3); ImGui::PopStyleColor(8); } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index d9c5e0ab95..e8fe125247 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -75,8 +75,7 @@ static constexpr const float TRACKBALLSIZE = 0.8f; static const float SLIDER_DEFAULT_RIGHT_MARGIN = 10.0f; static const float SLIDER_DEFAULT_BOTTOM_MARGIN = 10.0f; static const float SLIDER_RIGHT_MARGIN = 105.0f; -static const float SLIDER_BOTTOM_MARGIN = 65.0f; - +static const float SLIDER_BOTTOM_MARGIN = 90.0f; float GLCanvas3D::DEFAULT_BG_LIGHT_COLOR[3] = { 0.906f, 0.906f, 0.906f }; float GLCanvas3D::ERROR_BG_LIGHT_COLOR[3] = { 0.753f, 0.192f, 0.039f }; @@ -1926,7 +1925,11 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re bool wt = dynamic_cast(m_config->option("enable_prime_tower"))->value; auto co = dynamic_cast*>(m_config->option>("print_sequence")); - if (filaments_count > 1 && wt && co != nullptr && co->value != PrintSequence::ByObject) { + const DynamicPrintConfig &dconfig = wxGetApp().preset_bundle->prints.get_edited_preset().config; + const ConfigOption * option = dconfig.option("timelapse_no_toolhead"); + bool timelapse_enabled = option ? option->getBool() : false; + + if (timelapse_enabled || (filaments_count > 1 && wt && co != nullptr && co->value != PrintSequence::ByObject)) { for (int plate_id = 0; plate_id < n_plates; plate_id++) { DynamicPrintConfig& proj_cfg = wxGetApp().preset_bundle->project_config; float x = dynamic_cast(proj_cfg.option("wipe_tower_x"))->get_at(plate_id); @@ -3828,32 +3831,6 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type) } } -#if 1 - // BBS: update Timelapse Wipe Tower according to max height - for (unsigned int obj_idx = 0; obj_idx < (unsigned int) m_model->objects.size(); ++obj_idx) { - ModelObject *model_object = m_model->objects[obj_idx]; - if (model_object->is_timelapse_wipe_tower) { - for (GLVolume *volume : m_volumes.volumes) { - if (volume->composite_id.object_id == obj_idx) { - int instance_idx = volume->instance_idx(); - auto curr_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate(); - double max_height = curr_plate->estimate_timelapse_wipe_tower_height(); - float z_factor = max_height / model_object->raw_mesh_bounding_box().size()[2]; - volume->set_instance_scaling_factor(Vec3d(1.0, 1.0, z_factor)); - model_object->instances[instance_idx]->set_scaling_factor(Vec3d(1.0, 1.0, z_factor)); - volume->is_timelapse_wipe_tower = true; - break; - } - } - - ensure_on_bed(obj_idx, false); - model_object->invalidate_bounding_box(); - break; - } - } -#endif - - //BBS: notify instance updates to part plater list m_selection.notify_instance_update(-1, -1); @@ -5869,6 +5846,9 @@ void GLCanvas3D::_check_and_update_toolbar_icon_scale() m_assemble_view_toolbar.set_scale(sc); collapse_toolbar.set_scale(sc); size *= m_retina_helper->get_scale_factor(); + + auto *m_notification = wxGetApp().plater()->get_notification_manager(); + m_notification->set_scale(sc); #else //BBS: GUI refactor: GLToolbar m_main_toolbar.set_icons_size(GLGizmosManager::Default_Icons_Size * scale); diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index a2e0ce2055..e0827c9180 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -805,8 +805,8 @@ void GLToolbar::do_action(GLToolbarItem::EActionType type, int item_id, GLCanvas if ((m_type == Normal) && (item->get_state() != GLToolbarItem::Disabled)) { - // the item may get disabled during the action, if not, set it back to hover state - item->set_state(GLToolbarItem::Hover); + // the item may get disabled during the action, if not, set it back to normal state + item->set_state(GLToolbarItem::Normal); parent.render(); } } diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index a258ac6315..da8814e5c6 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1089,6 +1089,12 @@ void GUI_App::post_init() mainframe->refresh_plugin_tips(); }); + // update hms info + CallAfter([this] { + if (hms_query) + hms_query->check_hms_info(); + }); + BOOST_LOG_TRIVIAL(info) << "finished post_init"; //BBS: remove the single instance currently /*#ifdef _WIN32 @@ -1111,6 +1117,7 @@ GUI_App::GUI_App() , m_app_mode(EAppMode::Editor) , m_em_unit(10) , m_imgui(new ImGuiWrapper()) + , hms_query(new HMSQuery()) //, m_removable_drive_manager(std::make_unique()) //, m_other_instance_message_handler(std::make_unique()) { @@ -2203,6 +2210,7 @@ __retry: auto bambu_source = Slic3r::NetworkAgent::get_bambu_source_entry(); if (!bambu_source) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": can not get bambu source module!"; + m_networking_compatible = false; if (app_config->get("installed_networking") == "1") { m_networking_need_update = true; } @@ -2610,6 +2618,10 @@ void GUI_App::recreate_GUI(const wxString& msg_name) obj_list()->set_min_height(); update_mode(); + //check hms info for different language + if (hms_query) + hms_query->check_hms_info(); + //BBS: trigger restore project logic here, and skip confirm plater_->trigger_restore_project(1); @@ -3019,10 +3031,12 @@ std::string GUI_App::handle_web_request(std::string cmd) wxKeyEvent e(wxEVT_CHAR_HOOK); #ifdef __APPLE__ e.SetControlDown(cmdKey); + e.SetRawControlDown(ctrlKey); #else e.SetControlDown(ctrlKey); #endif e.SetShiftDown(shiftKey); + keyCode = keyCode == 188 ? ',' : keyCode; e.m_keyCode = keyCode; e.SetEventObject(mainframe); wxPostEvent(mainframe, e); diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index d02b0fe941..3f0470e9e0 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -12,6 +12,7 @@ #include "slic3r/GUI/DeviceManager.hpp" #include "slic3r/Utils/NetworkAgent.hpp" #include "slic3r/GUI/WebViewDialog.hpp" +#include "slic3r/GUI/HMS.hpp" #include "slic3r/GUI/Jobs/UpgradeNetworkJob.hpp" #include "../Utils/PrintHost.hpp" @@ -62,6 +63,7 @@ class ParamsPanel; class NotificationManager; struct GUI_InitParams; class ParamsDialog; +class HMSQuery; enum FileType @@ -264,6 +266,7 @@ private: std::shared_ptr m_upgrade_network_job; VersionInfo version_info; + HMSQuery *hms_query { nullptr }; boost::thread m_sync_update_thread; bool enable_sync = false; @@ -280,6 +283,7 @@ public: void show_message_box(std::string msg) { wxMessageBox(msg); } EAppMode get_app_mode() const { return m_app_mode; } Slic3r::DeviceManager* getDeviceManager() { return m_device_manager; } + HMSQuery* get_hms_query() { return hms_query; } NetworkAgent* getAgent() { return m_agent; } bool is_editor() const { return m_app_mode == EAppMode::Editor; } bool is_gcode_viewer() const { return m_app_mode == EAppMode::GCodeViewer; } diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index d31132266f..4f1bf04917 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -459,15 +459,6 @@ wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType ty [type, item](wxCommandEvent&) { obj_list()->load_generic_subobject(item, type); }, "", menu); } - // BBS: only add timelapse tower item for plate - if (type == ModelVolumeType::INVALID) { - auto item = L("Timelapse Wipe Tower"); - type = ModelVolumeType::TIMELAPSE_WIPE_TOWER; - append_menu_item(sub_menu, wxID_ANY, _(item), "", - [type, item](wxCommandEvent &) { obj_list()->load_generic_subobject(item, type); }, "", menu, - []() { return plater()->can_add_timelapse_wt(); }, m_parent); - } - return sub_menu; } @@ -1392,8 +1383,8 @@ void MenuFactory::append_menu_item_locked(wxMenu* menu) m_parent->Bind(wxEVT_UPDATE_UI, [](wxUpdateUIEvent& evt) { PartPlate* plate = plater()->get_partplate_list().get_selected_plate(); assert(plate); - bool check = plate->is_locked(); - evt.Check(check); + //bool check = plate->is_locked(); + //evt.Check(check); plater()->set_current_canvas_as_dirty(); }, item->GetId()); } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 7cb6a6156f..b93c0de6a3 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "slic3r/Utils/FixModelByWin10.hpp" #include "libslic3r/Format/bbs_3mf.hpp" @@ -75,7 +76,11 @@ ObjectList::ObjectList(wxWindow* parent) : wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE) { wxGetApp().UpdateDVCDarkUI(this, true); - + SetFont(Label::sysFont(13)); +#ifdef __WXMSW__ + GenericGetHeader()->SetFont(Label::sysFont(13)); +#endif + // create control create_objects_ctrl(); @@ -1864,7 +1869,7 @@ static TriangleMesh create_mesh(const std::string& type_name, const BoundingBoxf const double side = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.1); indexed_triangle_set mesh; - if (type_name == "Cube" || type_name == "Timelapse Wipe Tower") + if (type_name == "Cube") // Sitting on the print bed, left front front corner at (0, 0). mesh = its_make_cube(side, side, side); else if (type_name == "Cylinder") @@ -1892,10 +1897,6 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode load_shape_object(type_name); return; } - else if (type == ModelVolumeType::TIMELAPSE_WIPE_TOWER) { - load_shape_object(type_name, true); - return; - } const int obj_idx = get_selected_obj_idx(); if (obj_idx < 0) @@ -1999,7 +2000,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode } } -void ObjectList::load_shape_object(const std::string &type_name, bool is_timelapse_wt) +void ObjectList::load_shape_object(const std::string &type_name) { const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); //assert(selection.get_object_idx() == -1); // Add nothing is something is selected on 3DScene @@ -2016,11 +2017,11 @@ void ObjectList::load_shape_object(const std::string &type_name, bool is_timelap BoundingBoxf3 bb; TriangleMesh mesh = create_mesh(type_name, bb); // BBS: remove "Shape" prefix - load_mesh_object(mesh, _(type_name), true, is_timelapse_wt); + load_mesh_object(mesh, _(type_name)); wxGetApp().mainframe->update_title(); } -void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center, bool is_timelapse_wt) +void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center) { // Add mesh to model as a new object Model& model = wxGetApp().plater()->model(); @@ -2044,27 +2045,11 @@ void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name new_object->invalidate_bounding_box(); new_object->translate(-bb.center()); - if (is_timelapse_wt) { - new_object->is_timelapse_wipe_tower = true; - auto curr_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate(); - int highest_extruder = 0; - double max_height = curr_plate->estimate_timelapse_wipe_tower_height(&highest_extruder); - new_object->scale(1, 1, max_height / new_object->bounding_box().size()[2]); + // BBS: find an empty cell to put the copied object + auto start_point = wxGetApp().plater()->build_volume().bounding_volume2d().center(); + auto empty_cell = wxGetApp().plater()->canvas3D()->get_nearest_empty_cell({start_point(0), start_point(1)}); - // move to garbage bin of curr plate - auto offset = curr_plate->get_origin() + Vec3d(80.0, 230.0, -new_object->origin_translation.z()); - new_object->instances[0]->set_offset(offset); - - new_object->config.set_key_value("sparse_infill_density", new ConfigOptionPercent(0)); - new_object->config.set_key_value("top_shell_layers", new ConfigOptionInt(0)); - new_object->config.set("extruder", highest_extruder); - } else { - // BBS: find an empty cell to put the copied object - auto start_point = wxGetApp().plater()->build_volume().bounding_volume2d().center(); - auto empty_cell = wxGetApp().plater()->canvas3D()->get_nearest_empty_cell({start_point(0), start_point(1)}); - - new_object->instances[0]->set_offset(center ? to_3d(Vec2d(empty_cell(0), empty_cell(1)), -new_object->origin_translation.z()) : bb.center()); - } + new_object->instances[0]->set_offset(center ? to_3d(Vec2d(empty_cell(0), empty_cell(1)), -new_object->origin_translation.z()) : bb.center()); new_object->ensure_on_bed(); @@ -2760,11 +2745,6 @@ bool ObjectList::can_merge_to_multipart_object() const for (wxDataViewItem item : sels) { if (!(m_objects_model->GetItemType(item) & (itObject | itInstance))) return false; - - // BBS: do not support to merge timelapse wipe tower with other objects - ObjectDataViewModelNode* node = static_cast(item.GetID()); - if (node != nullptr && node->IsTimelapseWipeTower()) - return false; } return true; diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 2387142728..dac2698469 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -279,8 +279,8 @@ public: //void load_part(ModelObject& model_object, std::vector& added_volumes, ModelVolumeType type, bool from_galery = false); void load_modifier(const wxArrayString& input_files, ModelObject& model_object, std::vector& added_volumes, ModelVolumeType type, bool from_galery = false); void load_generic_subobject(const std::string& type_name, const ModelVolumeType type); - void load_shape_object(const std::string &type_name, bool is_timelapse_wt = false); - void load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center = true, bool is_timelapse_wt = false); + void load_shape_object(const std::string &type_name); + void load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center = true); void del_object(const int obj_idx, bool refresh_immediately = true); void del_subobject_item(wxDataViewItem& item); void del_settings_from_config(const wxDataViewItem& parent_item); diff --git a/src/slic3r/GUI/GUI_ObjectTable.cpp b/src/slic3r/GUI/GUI_ObjectTable.cpp index b92a346c5c..0f07a6d576 100644 --- a/src/slic3r/GUI/GUI_ObjectTable.cpp +++ b/src/slic3r/GUI/GUI_ObjectTable.cpp @@ -2911,7 +2911,7 @@ ObjectTableDialog::ObjectTableDialog(wxWindow* parent, Plater* platerObj, Model wxSize panel_size = m_obj_panel->get_init_size(); g_max_size_from_parent = maxSize; if ((maxSize.GetWidth() == -1) || (maxSize.GetHeight() == -1)) { - wxDisplay display(wxDisplay::GetFromWindow(this)); + wxDisplay display(this); //auto drect = display.GetGeometry(); wxRect client_area = display.GetClientArea (); g_max_size_from_parent.SetWidth(client_area.GetWidth()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 5398a99c6e..07aa30f9d1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -12,6 +12,7 @@ #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/format.hpp" +#include "slic3r/GUI/GUI.hpp" #include "slic3r/Utils/UndoRedo.hpp" @@ -181,12 +182,6 @@ void GLGizmoFdmSupports::on_set_state() } } -static std::string into_u8(const wxString& str) -{ - auto buffer_utf8 = str.utf8_str(); - return std::string(buffer_utf8.data()); -} - void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_limit) { init_print_instance(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 080e9743cb..d63feb4e79 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -9,6 +9,7 @@ #include "slic3r/GUI/format.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/NotificationManager.hpp" +#include "slic3r/GUI/GUI.hpp" #include "libslic3r/PresetBundle.hpp" #include "libslic3r/Model.hpp" #include "slic3r/Utils/UndoRedo.hpp" @@ -291,12 +292,6 @@ static void render_extruders_combo(const std::string &labe selection_idx = selection_out; } -static std::string into_u8(const wxString& str) -{ - auto buffer_utf8 = str.utf8_str(); - return std::string(buffer_utf8.data()); -} - void GLGizmoMmuSegmentation::show_tooltip_information(float caption_max, float x, float y) { ImTextureID normal_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 189db6236b..0d09e40368 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -1064,7 +1064,10 @@ void TriangleSelectorPatch::render(ImGuiWrapper* imgui) size_t color_idx = (size_t)patch.type; color = m_ebt_colors[color_idx]; } - shader->set_uniform("uniform_color", color); + //to make black not too hard too see + std::array new_color = adjust_color_for_rendering(color); + shader->set_uniform("uniform_color", new_color); + //shader->set_uniform("uniform_color", color); this->render(buffer_idx); } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index 4c1d837418..870406c1f9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -8,6 +8,7 @@ #include "slic3r/GUI/ImGuiWrapper.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/GUI/GUI.hpp" #include "slic3r/Utils/UndoRedo.hpp" #include @@ -155,12 +156,6 @@ void GLGizmoSeam::tool_changed(wchar_t old_tool, wchar_t new_tool) } } -static std::string into_u8(const wxString& str) -{ - auto buffer_utf8 = str.utf8_str(); - return std::string(buffer_utf8.data()); -} - void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) { if (! m_c->selection_info()->model_object()) diff --git a/src/slic3r/GUI/HMS.cpp b/src/slic3r/GUI/HMS.cpp new file mode 100644 index 0000000000..203b2d3217 --- /dev/null +++ b/src/slic3r/GUI/HMS.cpp @@ -0,0 +1,281 @@ +#include "HMS.hpp" + + + +namespace Slic3r { +namespace GUI { + +int get_hms_info_version(std::string& version) +{ + AppConfig* config = wxGetApp().app_config; + if (!config) + return -1; + std::string hms_host = config->get_hms_host(); + if(hms_host.empty()) { + BOOST_LOG_TRIVIAL(error) << "hms_host is empty"; + return -1; + } + int result = -1; + version = ""; + std::string url = (boost::format("https://%1%/GetVersion.php") % hms_host).str(); + Slic3r::Http http = Slic3r::Http::get(url); + http.timeout_max(10) + .on_complete([&result, &version](std::string body, unsigned status){ + try { + json j = json::parse(body); + if (j.contains("ver")) { + version = std::to_string(j["ver"].get()); + } + } catch (...) { + ; + } + }) + .on_error([&result](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << "get_hms_info_version: body = " << body << ", status = " << status << ", error = " << error; + result = -1; + }) + .perform_sync(); + return result; +} + +int HMSQuery::download_hms_info() +{ + AppConfig* config = wxGetApp().app_config; + if (!config) return -1; + + std::string hms_host = wxGetApp().app_config->get_hms_host(); + std::string lang_code = wxGetApp().app_config->get_language_code(); + std::string url = (boost::format("https://%1%/query.php?lang=%2%") % hms_host % lang_code).str(); + + Slic3r::Http http = Slic3r::Http::get(url); + + http.on_complete([this](std::string body, unsigned status) { + try { + json j = json::parse(body); + if (j.contains("result")) { + if (j["result"] == 0 && j.contains("data")) { + this->m_hms_json = j["data"]; + if (j.contains("ver")) + m_hms_json["version"] = std::to_string(j["ver"].get()); + } else { + this->m_hms_json.clear(); + BOOST_LOG_TRIVIAL(info) << "HMSQuery: update hms info error = " << j["result"].get(); + } + } + } catch (...) { + ; + } + }) + .timeout_max(20) + .on_error([](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << "HMSQuery: update hms info error = " << error << ", body = " << body << ", status = " << status; + }).perform_sync(); + + save_to_local(); + return 0; +} + +int HMSQuery::load_from_local(std::string &version_info) +{ + if (data_dir().empty()) { + version_info = ""; + BOOST_LOG_TRIVIAL(error) << "HMS: load_from_local, data_dir() is empty"; + return -1; + } + std::string filename = get_hms_file(); + std::string dir_str = (boost::filesystem::path(data_dir()) / filename).make_preferred().string(); + std::ifstream json_file(encode_path(dir_str.c_str())); + try { + if (json_file.is_open()) { + json_file >> m_hms_json; + if (m_hms_json.contains("version")) { + version_info = m_hms_json["version"].get(); + return 0; + } else { + BOOST_LOG_TRIVIAL(warning) << "HMS: load_from_local, no version info"; + return 0; + } + } + } catch(...) { + version_info = ""; + return -1; + } + version_info = ""; + return 0; +} + +int HMSQuery::save_to_local() +{ + if (data_dir().empty()) { + BOOST_LOG_TRIVIAL(error) << "HMS: save_to_local, data_dir() is empty"; + return -1; + } + std::string filename = get_hms_file(); + std::string dir_str = (boost::filesystem::path(data_dir()) / filename).make_preferred().string(); + std::ofstream json_file(encode_path(dir_str.c_str())); + if (json_file.is_open()) { + json_file << std::setw(4) << m_hms_json << std::endl; + json_file.close(); + return 0; + } + return -1; +} + +std::string HMSQuery::get_hms_file() +{ + AppConfig* config = wxGetApp().app_config; + if (!config) + return HMS_INFO_FILE; + std::string lang_code = wxGetApp().app_config->get_language_code(); + return (boost::format("hms_%1%.json") % lang_code).str(); +} + +wxString HMSQuery::query_hms_msg(std::string long_error_code) +{ + if (long_error_code.empty()) + return wxEmptyString; + AppConfig* config = wxGetApp().app_config; + if (!config) return wxEmptyString; + + std::string hms_host = wxGetApp().app_config->get_hms_host(); + std::string lang_code = wxGetApp().app_config->get_language_code(); + + if (m_hms_json.contains("device_hms")) { + if (m_hms_json["device_hms"].contains(lang_code)) { + for (auto item = m_hms_json["device_hms"][lang_code].begin(); item != m_hms_json["device_hms"][lang_code].end(); item++) { + if (item->contains("ecode") && (*item)["ecode"].get() == long_error_code) { + if (item->contains("intro")) { + return wxString::FromUTF8((*item)["intro"].get()); + } + } + } + BOOST_LOG_TRIVIAL(info) << "hms: query_hms_msg, not found error_code = " << long_error_code; + } + } else { + return wxEmptyString; + } + return wxEmptyString; +} + +wxString HMSQuery::query_error_msg(std::string error_code) +{ + AppConfig* config = wxGetApp().app_config; + if (!config) return wxEmptyString; + + std::string hms_host = wxGetApp().app_config->get_hms_host(); + std::string lang_code = wxGetApp().app_config->get_language_code(); + + if (m_hms_json.contains("device_error")) { + if (m_hms_json["device_error"].contains(lang_code)) { + for (auto item = m_hms_json["device_error"][lang_code].begin(); item != m_hms_json["device_error"][lang_code].end(); item++) { + if (item->contains("ecode") && (*item)["ecode"].get() == error_code) { + if (item->contains("intro")) { + return wxString::FromUTF8((*item)["intro"].get()); + } + } + } + BOOST_LOG_TRIVIAL(info) << "hms: query_error_msg, not found error_code = " << error_code; + } + } + else { + return wxEmptyString; + } + return wxEmptyString; +} + +wxString HMSQuery::query_print_error_msg(int print_error) +{ + char buf[32]; + ::sprintf(buf, "%08X", print_error); + return query_error_msg(std::string(buf)); +} + +int HMSQuery::check_hms_info() +{ + int result = 0; + bool download_new_hms_info = true; + + // load local hms json file + std::string version = ""; + if (load_from_local(version) == 0) { + BOOST_LOG_TRIVIAL(info) << "HMS: check_hms_info current version = " << version; + std::string new_version; + get_hms_info_version(new_version); + BOOST_LOG_TRIVIAL(info) << "HMS: check_hms_info latest version = " << new_version; + if (!version.empty() && version == new_version) { + download_new_hms_info = false; + } + } + BOOST_LOG_TRIVIAL(info) << "HMS: check_hms_info need download new hms info = " << download_new_hms_info; + // download if version is update + if (download_new_hms_info) { + result = download_hms_info(); + } + return result; +} + +std::string get_hms_wiki_url(std::string error_code) +{ + AppConfig* config = wxGetApp().app_config; + if (!config) return ""; + + std::string hms_host = wxGetApp().app_config->get_hms_host(); + std::string lang_code = wxGetApp().app_config->get_language_code(); + std::string url = (boost::format("https://%1%/index.php?e=%2%&s=device_hms&lang=%3%") + % hms_host + % error_code + % lang_code).str(); + return url; +} + +std::string get_error_message(int error_code) +{ + char buf[64]; + std::string result_str = ""; + std::sprintf(buf,"%08X",error_code); + std::string hms_host = wxGetApp().app_config->get_hms_host(); + std::string get_lang = wxGetApp().app_config->get_language_code(); + + std::string url = (boost::format("https://%1%/query.php?lang=%2%&e=%3%") + %hms_host + %get_lang + %buf).str(); + + Slic3r::Http http = Slic3r::Http::get(url); + http.header("accept", "application/json") + .timeout_max(10) + .on_complete([get_lang, &result_str](std::string body, unsigned status) { + try { + json j = json::parse(body); + if (j.contains("result")) { + if (j["result"].get() == 0) { + if (j.contains("data")) { + json jj = j["data"]; + if (jj.contains("device_error")) { + if (jj["device_error"].contains(get_lang)) { + if (jj["device_error"][get_lang].size() > 0) { + if (!jj["device_error"][get_lang][0]["intro"].empty() || !jj["device_error"][get_lang][0]["ecode"].empty()) { + std::string error_info = jj["device_error"][get_lang][0]["intro"].get(); + std::string error_code = jj["device_error"][get_lang][0]["ecode"].get(); + error_code.insert(4, " "); + result_str = from_u8(error_info).ToStdString() + "[" + error_code + "]"; + } + } + } + } + } + } + } + } catch (...) { + ; + } + }) + .on_error([](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(trace) << boost::format("[BBL ErrorMessage]: status=%1%, error=%2%, body=%3%") % status % error % body; + }).perform_sync(); + + return result_str; +} + +} +} \ No newline at end of file diff --git a/src/slic3r/GUI/HMS.hpp b/src/slic3r/GUI/HMS.hpp new file mode 100644 index 0000000000..fd45a2c3a8 --- /dev/null +++ b/src/slic3r/GUI/HMS.hpp @@ -0,0 +1,45 @@ +#ifndef slic3r_HMS_hpp_ +#define slic3r_HMS_hpp_ + +#include "GUI_App.hpp" +#include "GUI.hpp" +#include "I18N.hpp" +#include "Widgets/Label.hpp" +#include "Widgets/Button.hpp" +#include "Widgets/StepCtrl.hpp" +#include "BitmapCache.hpp" +#include "slic3r/Utils/Http.hpp" +#include "libslic3r/Thread.hpp" +#include "nlohmann/json.hpp" + +namespace Slic3r { +namespace GUI { + +#define HMS_INFO_FILE "hms.json" + +class HMSQuery { +protected: + json m_hms_json; + int download_hms_info(); + int load_from_local(std::string& version_info); + int save_to_local(); + std::string get_hms_file(); +public: + HMSQuery() {} + int check_hms_info(); + wxString query_hms_msg(std::string long_error_code); + wxString query_error_msg(std::string error_code); + wxString query_print_error_msg(int print_error); +}; + +int get_hms_info_version(std::string &version); + +std::string get_hms_wiki_url(std::string code); + +std::string get_error_message(int error_code); + +} +} + + +#endif \ No newline at end of file diff --git a/src/slic3r/GUI/HMSPanel.cpp b/src/slic3r/GUI/HMSPanel.cpp index ba3c86adea..45a3c7a683 100644 --- a/src/slic3r/GUI/HMSPanel.cpp +++ b/src/slic3r/GUI/HMSPanel.cpp @@ -1,11 +1,124 @@ +#include "HMS.hpp" #include "HMSPanel.hpp" #include #include #include "GUI.hpp" +#include "GUI_App.hpp" namespace Slic3r { namespace GUI { +#define HMS_NOTIFY_ITEM_TEXT_SIZE wxSize(FromDIP(730), -1) +#define HMS_NOTIFY_ITEM_SIZE wxSize(-1, FromDIP(80)) + +HMSNotifyItem::HMSNotifyItem(wxWindow *parent, HMSItem& item) + : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL) + , m_hms_item(item) + , m_url(get_hms_wiki_url(item.get_long_error_code())) +{ + init_bitmaps(); + + this->SetBackgroundColour(*wxWHITE); + + auto main_sizer = new wxBoxSizer(wxVERTICAL); + + m_panel_hms = new wxPanel(this, wxID_ANY, wxDefaultPosition, HMS_NOTIFY_ITEM_SIZE, wxTAB_TRAVERSAL); + auto m_panel_sizer = new wxBoxSizer(wxVERTICAL); + + auto m_panel_sizer_inner = new wxBoxSizer(wxHORIZONTAL); + + m_bitmap_notify = new wxStaticBitmap(m_panel_hms, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0); + m_bitmap_notify->SetBitmap(get_notify_bitmap()); + + m_hms_content = new wxStaticText(m_panel_hms, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END); + m_hms_content->SetSize(HMS_NOTIFY_ITEM_TEXT_SIZE); + m_hms_content->SetMinSize(HMS_NOTIFY_ITEM_TEXT_SIZE); + m_hms_content->SetLabelText(_L(wxGetApp().get_hms_query()->query_hms_msg(m_hms_item.get_long_error_code()))); + m_hms_content->Wrap(HMS_NOTIFY_ITEM_TEXT_SIZE.GetX()); + + m_bitmap_arrow = new wxStaticBitmap(m_panel_hms, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0); + if (!m_url.empty()) + m_bitmap_arrow->SetBitmap(m_img_arrow); + + m_panel_sizer_inner->Add(m_bitmap_notify, 0, wxALIGN_CENTER_VERTICAL, 0); + m_panel_sizer_inner->AddSpacer(FromDIP(8)); + m_panel_sizer_inner->Add(m_hms_content, 0, wxALIGN_CENTER_VERTICAL, 0); + m_panel_sizer_inner->AddStretchSpacer(); + m_panel_sizer_inner->Add(m_bitmap_arrow, 0, wxALIGN_CENTER_VERTICAL, 0); + + m_panel_sizer->Add(m_panel_sizer_inner, 1, wxEXPAND | wxALL, FromDIP(20)); + + m_staticline = new wxPanel(m_panel_hms, wxID_DELETE, wxDefaultPosition, wxSize(-1, FromDIP(1))); + m_staticline->SetBackgroundColour(wxColour(238, 238, 238)); + + m_panel_sizer->Add(m_staticline, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(20)); + + m_panel_hms->SetSizer(m_panel_sizer); + m_panel_hms->Layout(); + m_panel_sizer->Fit(m_panel_hms); + + main_sizer->Add(m_panel_hms, 0, wxEXPAND, 0); + + this->SetSizer(main_sizer); + this->Layout(); + + m_hms_content->Bind(wxEVT_ENTER_WINDOW, [this](wxMouseEvent &e) { + e.Skip(); + if (!m_url.empty()) { + auto font = m_hms_content->GetFont(); + font.SetUnderlined(true); + m_hms_content->SetFont(font); + Layout(); + SetCursor(wxCURSOR_HAND); + } + }); + m_hms_content->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent &e) { + e.Skip(); + if (!m_url.empty()) { + auto font = m_hms_content->GetFont(); + font.SetUnderlined(false); + m_hms_content->SetFont(font); + Layout(); + SetCursor(wxCURSOR_ARROW); + } + }); + m_hms_content->Bind(wxEVT_LEFT_UP, [this](wxMouseEvent &e) { + if(!m_url.empty()) + wxLaunchDefaultBrowser(get_hms_wiki_url(m_hms_item.get_long_error_code())); + }); +} +HMSNotifyItem ::~HMSNotifyItem() { + ; +} + +void HMSNotifyItem::init_bitmaps() { + m_img_notify_lv1 = create_scaled_bitmap("hms_notify_lv1", nullptr, 18); + m_img_notify_lv2 = create_scaled_bitmap("hms_notify_lv2", nullptr, 18); + m_img_notify_lv3 = create_scaled_bitmap("hms_notify_lv3", nullptr, 18); + m_img_arrow = create_scaled_bitmap("hms_arrow", nullptr, 14); +} + +wxBitmap & HMSNotifyItem::get_notify_bitmap() +{ + switch (m_hms_item.msg_level) { + case (HMS_FATAL): + return m_img_notify_lv1; + break; + case (HMS_SERIOUS): + return m_img_notify_lv2; + break; + case (HMS_COMMON): + return m_img_notify_lv3; + break; + case (HMS_INFO): + //return m_img_notify_lv4; + break; + case (HMS_UNKNOWN): + case (HMS_MSG_LEVEL_MAX): + default: break; + } + return wxNullBitmap; +} HMSPanel::HMSPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style) :wxPanel(parent, id, pos, size, style) @@ -19,39 +132,46 @@ HMSPanel::HMSPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wx m_top_sizer = new wxBoxSizer(wxVERTICAL); - m_hms_content = new wxTextCtrl(m_scrolledWindow, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxTE_AUTO_URL | wxTE_MULTILINE); - - m_top_sizer->Add(m_hms_content, 1, wxALL | wxEXPAND, 0); + m_top_sizer->AddSpacer(FromDIP(30)); m_scrolledWindow->SetSizerAndFit(m_top_sizer); - m_main_sizer->Add(m_scrolledWindow, 1, wxALIGN_CENTER_HORIZONTAL | wxEXPAND, 0); + m_main_sizer->Add(m_scrolledWindow, 1, wxEXPAND, 0); this->SetSizerAndFit(m_main_sizer); - Layout(); } HMSPanel::~HMSPanel() { + ; +} +void HMSPanel::append_hms_panel(HMSItem& item) { + m_notify_item = new HMSNotifyItem(m_scrolledWindow, item); + m_top_sizer->Add(m_notify_item, 0, wxALIGN_CENTER_HORIZONTAL); +} + +void HMSPanel::delete_hms_panels() { + m_scrolledWindow->DestroyChildren(); } void HMSPanel::update(MachineObject *obj) { if (obj) { + this->Freeze(); + delete_hms_panels(); wxString hms_text; for (auto item : obj->hms_list) { - hms_text += wxString::Format("Module_ID = %s, module_num = %d,part_id = %d, msg level = %s msg code: 0x%x\n", - HMSItem::get_module_name(item.module_id), - item.module_num, - item.part_id, - HMSItem::get_hms_msg_level_str(item.msg_level), - (unsigned)item.msg_code); + if (wxGetApp().get_hms_query()) { + append_hms_panel(item); + } } - m_hms_content->SetLabelText(hms_text); + Layout(); + this->Thaw(); } else { - m_hms_content->SetLabelText(""); + delete_hms_panels(); + Layout(); } } @@ -60,5 +180,4 @@ bool HMSPanel::Show(bool show) return wxPanel::Show(show); } -} -} \ No newline at end of file +}} \ No newline at end of file diff --git a/src/slic3r/GUI/HMSPanel.hpp b/src/slic3r/GUI/HMSPanel.hpp index 6761c2df9c..e50e22cbed 100644 --- a/src/slic3r/GUI/HMSPanel.hpp +++ b/src/slic3r/GUI/HMSPanel.hpp @@ -6,17 +6,47 @@ #include #include #include +#include namespace Slic3r { namespace GUI { +class HMSNotifyItem : public wxPanel +{ + HMSItem & m_hms_item; + std::string m_url; + + wxPanel * m_panel_hms; + wxStaticBitmap *m_bitmap_notify; + wxStaticBitmap *m_bitmap_arrow; + wxStaticText * m_hms_content; + wxHtmlWindow * m_html; + wxPanel * m_staticline; + + wxBitmap m_img_notify_lv1; + wxBitmap m_img_notify_lv2; + wxBitmap m_img_notify_lv3; + wxBitmap m_img_arrow; + + void init_bitmaps(); + wxBitmap & get_notify_bitmap(); + +public: + HMSNotifyItem(wxWindow *parent, HMSItem& item); + ~HMSNotifyItem(); + + void msw_rescale() {} +}; class HMSPanel : public wxPanel { protected: - wxScrolledWindow* m_scrolledWindow; - wxBoxSizer* m_top_sizer; - wxTextCtrl* m_hms_content; + wxScrolledWindow *m_scrolledWindow; + wxBoxSizer * m_top_sizer; + HMSNotifyItem * m_notify_item; + + void append_hms_panel(HMSItem &item); + void delete_hms_panels(); public: HMSPanel(wxWindow *parent, wxWindowID id = wxID_ANY, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize, long style = wxTAB_TRAVERSAL); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index b5e88cd155..f3fe8d7704 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -1526,9 +1526,26 @@ void ImGuiWrapper::search_list(const ImVec2& size_, bool (*items_getter)(int, co // check_box(_L("Search in English"), view_params.english); } +void ImGuiWrapper::bold_text(const std::string& str) +{ + if (bold_font){ + ImGui::PushFont(bold_font); + text(str); + ImGui::PopFont(); + } else { + text(str); + } +} + void ImGuiWrapper::title(const std::string& str) { - text(str); + if (bold_font){ + ImGui::PushFont(bold_font); + text(str); + ImGui::PopFont(); + } else { + text(str); + } ImGui::Separator(); } @@ -1690,6 +1707,7 @@ void ImGuiWrapper::init_font(bool compress) //FIXME replace with io.Fonts->AddFontFromMemoryTTF(buf_decompressed_data, (int)buf_decompressed_size, m_font_size, nullptr, ranges.Data); //https://github.com/ocornut/imgui/issues/220 ImFont* font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/" + "HarmonyOS_Sans_SC_Regular.ttf").c_str(), m_font_size, nullptr, ranges.Data); + if (font == nullptr) { font = io.Fonts->AddFontDefault(); if (font == nullptr) { @@ -1697,6 +1715,15 @@ void ImGuiWrapper::init_font(bool compress) } } + ImFontConfig cfg = ImFontConfig(); + cfg.OversampleH = 1; + bold_font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/" + "HarmonyOS_Sans_SC_Bold.ttf").c_str(), m_font_size, &cfg); + + if (bold_font == nullptr) { + bold_font = io.Fonts->AddFontDefault(); + if (bold_font == nullptr) { throw Slic3r::RuntimeError("ImGui: Could not load deafult font"); } + } + #ifdef __APPLE__ ImFontConfig config; config.MergeMode = true; diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 8cb579f504..dee1553eb2 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -147,7 +147,12 @@ public: bool combo(const wxString& label, const std::vector& options, int& selection); // Use -1 to not mark any option as selected bool undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool, int, const char**), int& hovered, int& selected, int& mouse_wheel); void search_list(const ImVec2& size, bool (*items_getter)(int, const char** label, const char** tooltip), char* search_str, - Search::OptionViewParameters& view_params, int& selected, bool& edited, int& mouse_wheel, bool is_localized); + Search::OptionViewParameters &view_params, + int & selected, + bool & edited, + int & mouse_wheel, + bool is_localized); + void bold_text(const std::string &str); void title(const std::string& str); void disabled_begin(bool disabled); @@ -204,6 +209,7 @@ private: static void clipboard_set(void* user_data, const char* text); LastSliderStatus m_last_slider_status; + ImFont* bold_font = nullptr; }; class IMTexture diff --git a/src/slic3r/GUI/Jobs/UpgradeNetworkJob.cpp b/src/slic3r/GUI/Jobs/UpgradeNetworkJob.cpp index 52ba95e306..8b53cb303b 100644 --- a/src/slic3r/GUI/Jobs/UpgradeNetworkJob.cpp +++ b/src/slic3r/GUI/Jobs/UpgradeNetworkJob.cpp @@ -35,6 +35,7 @@ void UpgradeNetworkJob::on_success(std::function success) void UpgradeNetworkJob::update_status(int st, const wxString &msg) { + BOOST_LOG_TRIVIAL(info) << "UpgradeNetworkJob: percent = " << st << "msg = " << msg; GUI::Job::update_status(st, msg); wxCommandEvent event(EVT_UPGRADE_UPDATE_MESSAGE); event.SetString(msg); @@ -59,6 +60,8 @@ void UpgradeNetworkJob::process() auto path_str = tmp_path.string() + wxString::Format(".%d%s", get_current_pid(), ".tmp").ToStdString(); tmp_path = fs::path(path_str); + BOOST_LOG_TRIVIAL(info) << "UpgradeNetworkJob: save netowrk_plugin to " << tmp_path.string(); + auto cancel_fn = [this]() { return was_canceled(); }; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index a75f4e7d28..21d0b36f3f 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -55,6 +55,7 @@ #include "GUI_ObjectList.hpp" #include "NotificationManager.hpp" #include "MarkdownTip.hpp" +#include "NetworkTestDialog.hpp" #ifdef _WIN32 #include @@ -344,7 +345,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_ // BBS: fix taskbar overlay on windows #ifdef WIN32 auto setMaxSize = [this]() { - wxDisplay display(wxDisplay::GetFromWindow(this)); + wxDisplay display(this); auto size = display.GetClientArea().GetSize(); // 8 pixels shadow SetMaxSize(size + wxSize{16, 16}); @@ -355,7 +356,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_ }); setMaxSize(); this->Bind(wxEVT_MAXIMIZE, [this](auto &e) { - wxDisplay display(wxDisplay::GetFromWindow(this)); + wxDisplay display(this); auto pos = display.GetClientArea().GetPosition(); Move(pos - wxPoint{8, 8}); e.Skip(); @@ -480,18 +481,36 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_ Slic3r::run_backup_ui_tasks(); }); ; } - this->Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent &evt) { #ifdef __APPLE__ - if (evt.CmdDown() && evt.GetKeyCode() == 'H') { this->Iconize(); return;} + if (evt.CmdDown() && (evt.GetKeyCode() == 'H')) { + //call parent_menu hide behavior + return;} + if (evt.CmdDown() && (evt.GetKeyCode() == 'M')) { + this->Iconize(); + return; + } if (evt.CmdDown() && evt.GetKeyCode() == 'Q') { wxPostEvent(this, wxCloseEvent(wxEVT_CLOSE_WINDOW)); return;} + if (evt.CmdDown() && evt.RawControlDown() && evt.GetKeyCode() == 'F') { + EnableFullScreenView(true); + if (IsFullScreen()) { + ShowFullScreen(false); + } else { + ShowFullScreen(true); + } + return;} #endif if (evt.CmdDown() && evt.GetKeyCode() == 'N') { m_plater->new_project(); return;} if (evt.CmdDown() && evt.GetKeyCode() == 'O') { m_plater->load_project(); return;} if (evt.CmdDown() && evt.ShiftDown() && evt.GetKeyCode() == 'S') { if (m_plater) m_plater->save_project(true); return;} else if (evt.CmdDown() && evt.GetKeyCode() == 'S') { if (m_plater) m_plater->save_project(); return;} - if (evt.CmdDown() && evt.GetKeyCode() == 'P') { +#ifdef __APPLE__ + if (evt.CmdDown() && evt.GetKeyCode() == ',') +#else + if (evt.CmdDown() && evt.GetKeyCode() == 'P') +#endif + { PreferencesDialog dlg(this); dlg.ShowModal(); #if ENABLE_GCODE_LINES_ID_IN_H_SLIDER @@ -502,6 +521,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_ plater()->refresh_print(); return; } + if (evt.CmdDown() && evt.GetKeyCode() == 'I') { if (!can_add_models()) return; if (m_plater) { m_plater->add_model(); } @@ -1167,24 +1187,28 @@ bool MainFrame::can_change_view() const } } +bool MainFrame::can_clone() const { + return can_select() && !m_plater->is_selection_empty(); +} + bool MainFrame::can_select() const { - return (m_plater != nullptr) && !m_plater->model().objects.empty(); + return (m_plater != nullptr) && (m_tabpanel->GetSelection() == TabPosition::tp3DEditor) && !m_plater->model().objects.empty(); } bool MainFrame::can_deselect() const { - return (m_plater != nullptr) && !m_plater->is_selection_empty(); + return (m_plater != nullptr) && (m_tabpanel->GetSelection() == TabPosition::tp3DEditor) && !m_plater->is_selection_empty(); } bool MainFrame::can_delete() const { - return (m_plater != nullptr) && !m_plater->is_selection_empty(); + return (m_plater != nullptr) && (m_tabpanel->GetSelection() == TabPosition::tp3DEditor) && !m_plater->is_selection_empty(); } bool MainFrame::can_delete_all() const { - return (m_plater != nullptr) && !m_plater->model().objects.empty(); + return (m_plater != nullptr) && (m_tabpanel->GetSelection() == TabPosition::tp3DEditor) && !m_plater->model().objects.empty(); } bool MainFrame::can_reslice() const @@ -1656,6 +1680,12 @@ static wxMenu* generate_help_menu() append_menu_item(helpMenu, wxID_ANY, about_title, about_title, [](wxCommandEvent&) { Slic3r::GUI::about(); }); #endif + + append_menu_item(helpMenu, wxID_ANY, _L("Open Network Test"), _L("Open Network Test"), [](wxCommandEvent&) { + NetworkTestDialog dlg(wxGetApp().mainframe); + dlg.ShowModal(); + }); + return helpMenu; } @@ -1840,7 +1870,7 @@ void MainFrame::init_menubar_as_editor() _L("Clone copies of selections"),[this](wxCommandEvent&) { m_plater->clone_selection(); }, - "menu_remove", nullptr, [this](){return can_select(); }, this); + "menu_remove", nullptr, [this](){return can_clone(); }, this); editMenu->AppendSeparator(); #else // BBS undo @@ -1878,7 +1908,7 @@ void MainFrame::init_menubar_as_editor() _L("Clone copies of selections"),[this](wxCommandEvent&) { m_plater->clone_selection(); }, - "", nullptr, [this](){return can_select(); }, this); + "", nullptr, [this](){return can_clone(); }, this); editMenu->AppendSeparator(); #endif @@ -1951,117 +1981,122 @@ void MainFrame::init_menubar_as_editor() #ifdef __APPLE__ wxWindowID bambu_studio_id_base = wxWindow::NewControlId(int(2)); wxMenu* parent_menu = m_menubar->OSXGetAppleMenu(); - auto preference_item = new wxMenuItem(parent_menu, BambuStudioMenuPreferences + bambu_studio_id_base, _L("Preferences") + "\tCtrl+P", ""); + auto preference_item = new wxMenuItem(parent_menu, BambuStudioMenuPreferences + bambu_studio_id_base, _L("Preferences") + "\tCtrl+,", ""); #else wxMenu* parent_menu = m_topbar->GetTopMenu(); auto preference_item = new wxMenuItem(parent_menu, ConfigMenuPreferences + config_id_base, _L("Preferences") + "\tCtrl+P", ""); + #endif - //auto printer_item = new wxMenuItem(parent_menu, ConfigMenuPrinter + config_id_base, _L("Printer"), ""); //auto language_item = new wxMenuItem(parent_menu, ConfigMenuLanguage + config_id_base, _L("Switch Language"), ""); - parent_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent& event) { - switch (event.GetId() - config_id_base) { - //case ConfigMenuLanguage: - //{ - // /* Before change application language, let's check unsaved changes on 3D-Scene - // * and draw user's attention to the application restarting after a language change - // */ - // { - // // the dialog needs to be destroyed before the call to switch_language() - // // or sometimes the application crashes into wxDialogBase() destructor - // // so we put it into an inner scope - // wxString title = _L("Language selection"); - // wxMessageDialog dialog(nullptr, - // _L("Switching the language requires application restart.\n") + "\n\n" + - // _L("Do you want to continue?"), - // title, - // wxICON_QUESTION | wxOK | wxCANCEL); - // if (dialog.ShowModal() == wxID_CANCEL) - // return; - // } - - // wxGetApp().switch_language(); - // break; - //} - //case ConfigMenuWizard: - //{ - // wxGetApp().run_wizard(ConfigWizard::RR_USER); - // break; - //} - case ConfigMenuPrinter: - { - wxGetApp().params_dialog()->Popup(); - wxGetApp().get_tab(Preset::TYPE_PRINTER)->restore_last_select_item(); - break; - } - case ConfigMenuPreferences: - { - wxGetApp().CallAfter([this] { - PreferencesDialog dlg(this); - dlg.ShowModal(); -#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER - if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed()) -#else - if (dlg.seq_top_layer_only_changed()) -#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER - plater()->refresh_print(); -#if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN -#ifdef _WIN32 - /* - if (wxGetApp().app_config()->get("associate_3mf") == "true") - wxGetApp().associate_3mf_files(); - if (wxGetApp().app_config()->get("associate_stl") == "true") - wxGetApp().associate_stl_files(); - /*if (wxGetApp().app_config()->get("associate_step") == "true") - wxGetApp().associate_step_files();*/ -#endif // _WIN32 -#endif - }); - break; - } - default: - break; - } - }); +// parent_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent& event) { +// switch (event.GetId() - config_id_base) { +// //case ConfigMenuLanguage: +// //{ +// // /* Before change application language, let's check unsaved changes on 3D-Scene +// // * and draw user's attention to the application restarting after a language change +// // */ +// // { +// // // the dialog needs to be destroyed before the call to switch_language() +// // // or sometimes the application crashes into wxDialogBase() destructor +// // // so we put it into an inner scope +// // wxString title = _L("Language selection"); +// // wxMessageDialog dialog(nullptr, +// // _L("Switching the language requires application restart.\n") + "\n\n" + +// // _L("Do you want to continue?"), +// // title, +// // wxICON_QUESTION | wxOK | wxCANCEL); +// // if (dialog.ShowModal() == wxID_CANCEL) +// // return; +// // } +// +// // wxGetApp().switch_language(); +// // break; +// //} +// //case ConfigMenuWizard: +// //{ +// // wxGetApp().run_wizard(ConfigWizard::RR_USER); +// // break; +// //} +// case ConfigMenuPrinter: +// { +// wxGetApp().params_dialog()->Popup(); +// wxGetApp().get_tab(Preset::TYPE_PRINTER)->restore_last_select_item(); +// break; +// } +// case ConfigMenuPreferences: +// { +// wxGetApp().CallAfter([this] { +// PreferencesDialog dlg(this); +// dlg.ShowModal(); +//#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER +// if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed()) +//#else +// if (dlg.seq_top_layer_only_changed()) +//#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER +// plater()->refresh_print(); +//#if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN +//#ifdef _WIN32 +// /* +// if (wxGetApp().app_config()->get("associate_3mf") == "true") +// wxGetApp().associate_3mf_files(); +// if (wxGetApp().app_config()->get("associate_stl") == "true") +// wxGetApp().associate_stl_files(); +// /*if (wxGetApp().app_config()->get("associate_step") == "true") +// wxGetApp().associate_step_files();*/ +//#endif // _WIN32 +//#endif +// }); +// break; +// } +// default: +// break; +// } +// }); #ifdef __APPLE__ wxString about_title = wxString::Format(_L("&About %s"), SLIC3R_APP_FULL_NAME); auto about_item = new wxMenuItem(parent_menu, BambuStudioMenuAbout + bambu_studio_id_base, about_title, ""); - parent_menu->Bind(wxEVT_MENU, [this, bambu_studio_id_base](wxEvent& event) { - switch (event.GetId() - bambu_studio_id_base) { - case BambuStudioMenuAbout: - Slic3r::GUI::about(); - break; - case BambuStudioMenuPreferences: - wxGetApp().CallAfter([this] { - PreferencesDialog dlg(this); - dlg.ShowModal(); - #if ENABLE_GCODE_LINES_ID_IN_H_SLIDER - if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed()) - #else - if (dlg.seq_top_layer_only_changed()) - #endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER - plater()->refresh_print(); - #if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN - #ifdef _WIN32 - /* - if (wxGetApp().app_config()->get("associate_3mf") == "true") - wxGetApp().associate_3mf_files(); - if (wxGetApp().app_config()->get("associate_stl") == "true") - wxGetApp().associate_stl_files(); - /*if (wxGetApp().app_config()->get("associate_step") == "true") - wxGetApp().associate_step_files();*/ - #endif // _WIN32 - #endif - }); - break; - default: - break; - } - - }); - parent_menu->Insert(0, about_item); - parent_menu->Insert(1, preference_item); + //parent_menu->Bind(wxEVT_MENU, [this, bambu_studio_id_base](wxEvent& event) { + // switch (event.GetId() - bambu_studio_id_base) { + // case BambuStudioMenuAbout: + // Slic3r::GUI::about(); + // break; + // case BambuStudioMenuPreferences: + // wxGetApp().CallAfter([this] { + // PreferencesDialog dlg(this); + // dlg.ShowModal(); + //#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER + // if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed()) + //#else + // if (dlg.seq_top_layer_only_changed()) + //#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER + // plater()->refresh_print(); + // }); + // break; + // default: + // break; + // } + //}); + //parent_menu->Insert(0, about_item); + append_menu_item( + parent_menu, wxID_ANY, _L("About") + "", _L(""), + [this](wxCommandEvent &) { Slic3r::GUI::about();}, + "", nullptr, []() { return true; }, this, 0); + append_menu_item( + parent_menu, wxID_ANY, _L("Preferences") + "\tCtrl+'", _L(""), + [this](wxCommandEvent &) { + PreferencesDialog dlg(this); + dlg.ShowModal(); +#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER + if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed()) +#else + if (dlg.seq_top_layer_only_changed()) +#endif + plater()->refresh_print(); + }, + "", nullptr, []() { return true; }, this, 1); + //parent_menu->Insert(1, preference_item); #endif // Help menu auto helpMenu = generate_help_menu(); @@ -2074,7 +2109,21 @@ void MainFrame::init_menubar_as_editor() if (viewMenu) m_topbar->AddDropDownSubMenu(viewMenu, _L("View")); //BBS add Preference - m_topbar->AddDropDownMenuItem(preference_item); + + append_menu_item( + m_topbar->GetTopMenu(), wxID_ANY, _L("Preferences") + "\tCtrl+P", _L(""), + [this](wxCommandEvent &) { + PreferencesDialog dlg(this); + dlg.ShowModal(); +#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER + if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed()) +#else + if (dlg.seq_top_layer_only_changed()) +#endif + plater()->refresh_print(); + }, + "", nullptr, []() { return true; }, this); + //m_topbar->AddDropDownMenuItem(preference_item); //m_topbar->AddDropDownMenuItem(printer_item); //m_topbar->AddDropDownMenuItem(language_item); //m_topbar->AddDropDownMenuItem(config_item); diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 1b80eb9234..321b92af87 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -118,6 +118,7 @@ class MainFrame : public DPIFrame bool can_change_view() const; bool can_select() const; bool can_deselect() const; + bool can_clone() const; bool can_delete() const; bool can_delete_all() const; bool can_reslice() const; diff --git a/src/slic3r/GUI/MarkdownTip.cpp b/src/slic3r/GUI/MarkdownTip.cpp index c0c25f5108..cb62678d23 100644 --- a/src/slic3r/GUI/MarkdownTip.cpp +++ b/src/slic3r/GUI/MarkdownTip.cpp @@ -83,6 +83,8 @@ MarkdownTip::MarkdownTip() _timer->Bind(wxEVT_TIMER, &MarkdownTip::OnTimer, this); } +MarkdownTip::~MarkdownTip() { delete _timer; } + void MarkdownTip::LoadStyle() { _language = GUI::into_u8(GUI::wxGetApp().current_language_code()); @@ -139,7 +141,7 @@ bool MarkdownTip::ShowTip(wxPoint pos, std::string const &tip, std::string const this->Hide(); } if (_tipView->GetParent() == this) { - wxSize size = wxDisplay(wxDisplay::GetFromWindow(this)).GetClientArea().GetSize(); + wxSize size = wxDisplay(this).GetClientArea().GetSize(); _requestPos = pos; if (pos.y + this->GetSize().y > size.y) pos.y = size.y - this->GetSize().y; @@ -252,7 +254,7 @@ void MarkdownTip::OnTitleChanged(wxWebViewEvent& event) return; _lastHeight = height; height *= 1.25; height += 50; - wxSize size = wxDisplay(wxDisplay::GetFromWindow(this)).GetClientArea().GetSize(); + wxSize size = wxDisplay(this).GetClientArea().GetSize(); if (height > size.y) height = size.y; wxPoint pos = _requestPos; diff --git a/src/slic3r/GUI/MarkdownTip.hpp b/src/slic3r/GUI/MarkdownTip.hpp index 8878a064e2..0b28ea091f 100644 --- a/src/slic3r/GUI/MarkdownTip.hpp +++ b/src/slic3r/GUI/MarkdownTip.hpp @@ -28,6 +28,8 @@ private: MarkdownTip(); + ~MarkdownTip(); + void LoadStyle(); bool ShowTip(wxPoint pos, std::string const &tip, std::string const & tooltip); @@ -48,13 +50,13 @@ private: void OnTimer(wxTimerEvent& event); private: - wxWebView* _tipView = NULL; + wxWebView * _tipView = nullptr; std::string _lastTip; std::string _pendingScript = " "; std::string _language; wxPoint _requestPos; double _lastHeight = 0; - wxTimer* _timer; + wxTimer* _timer = nullptr; bool _hide = false; bool _data_dir = false; }; diff --git a/src/slic3r/GUI/MediaFilePanel.cpp b/src/slic3r/GUI/MediaFilePanel.cpp index 0f2fa8d2f4..5c8f1a42d5 100644 --- a/src/slic3r/GUI/MediaFilePanel.cpp +++ b/src/slic3r/GUI/MediaFilePanel.cpp @@ -45,12 +45,12 @@ MediaFilePanel::MediaFilePanel(wxWindow * parent) time_sizer->Add(m_button_month, 0, wxALIGN_CENTER_VERTICAL); time_sizer->Add(m_button_all, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 24); m_time_panel->SetSizer(time_sizer); - top_sizer->Add(m_time_panel, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND); + top_sizer->Add(m_time_panel, 1, wxEXPAND); // File type m_type_panel = new ::StaticBox(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); m_type_panel->SetBackgroundColor(*wxWHITE); - m_type_panel->SetCornerRadius(5); + m_type_panel->SetCornerRadius(FromDIP(5)); m_type_panel->SetMinSize({-1, 48 * em_unit(this) / 10}); m_button_timelapse = new ::Button(m_type_panel, _L("Timelapse"), "", wxBORDER_NONE); m_button_video = new ::Button(m_type_panel, _L("Video"), "", wxBORDER_NONE); @@ -74,7 +74,7 @@ MediaFilePanel::MediaFilePanel(wxWindow * parent) manage_sizer->Add(m_button_download, 0, wxALIGN_CENTER_VERTICAL)->Show(false); manage_sizer->Add(m_button_management, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 24); m_manage_panel->SetSizer(manage_sizer); - top_sizer->Add(m_manage_panel, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND); + top_sizer->Add(m_manage_panel, 1, wxEXPAND); sizer->Add(top_sizer, 0, wxEXPAND); @@ -154,6 +154,11 @@ MediaFilePanel::MediaFilePanel(wxWindow * parent) parent->GetParent()->Bind(wxEVT_SHOW, onShowHide); } +MediaFilePanel::~MediaFilePanel() +{ + SetMachineObject(nullptr); +} + void MediaFilePanel::SetMachineObject(MachineObject* obj) { std::string machine = obj ? obj->dev_id : ""; @@ -269,4 +274,4 @@ MediaFileFrame::MediaFileFrame(wxWindow* parent) void MediaFileFrame::on_dpi_changed(const wxRect& suggested_rect) { m_panel->Rescale(); Refresh(); } -}} \ No newline at end of file +}} diff --git a/src/slic3r/GUI/MediaFilePanel.h b/src/slic3r/GUI/MediaFilePanel.h index 5c3045c539..68bfbe0e8c 100644 --- a/src/slic3r/GUI/MediaFilePanel.h +++ b/src/slic3r/GUI/MediaFilePanel.h @@ -31,6 +31,8 @@ class MediaFilePanel : public wxPanel { public: MediaFilePanel(wxWindow * parent); + + ~MediaFilePanel(); void SetMachineObject(MachineObject * obj); diff --git a/src/slic3r/GUI/MediaPlayCtrl.cpp b/src/slic3r/GUI/MediaPlayCtrl.cpp index 7218dcb0ec..6e37217d1a 100644 --- a/src/slic3r/GUI/MediaPlayCtrl.cpp +++ b/src/slic3r/GUI/MediaPlayCtrl.cpp @@ -18,7 +18,7 @@ MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl2 *media_ctrl, const w m_button_play = new Button(this, "", "media_play", wxBORDER_NONE); - m_label_status = new Label(Label::Body_14, this); + m_label_status = new Label(this); m_button_play->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](auto & e) { TogglePlay(); }); @@ -27,7 +27,7 @@ MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl2 *media_ctrl, const w Bind(wxEVT_RIGHT_UP, [this](auto & e) { wxClipboard & c = *wxTheClipboard; if (c.Open()) { c.SetData(new wxTextDataObject(m_url)); c.Close(); } }); wxBoxSizer * sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(m_button_play, 0, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALL, 0); + sizer->Add(m_button_play, 0, wxEXPAND | wxALL, 0); sizer->AddStretchSpacer(1); sizer->Add(m_label_status, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(25)); SetSizer(sizer); diff --git a/src/slic3r/GUI/Monitor.cpp b/src/slic3r/GUI/Monitor.cpp index ba8fd6f99a..d39fa897c9 100644 --- a/src/slic3r/GUI/Monitor.cpp +++ b/src/slic3r/GUI/Monitor.cpp @@ -52,8 +52,8 @@ AddMachinePanel::AddMachinePanel(wxWindow* parent, wxWindowID id, const wxPoint& horiz_sizer->Add(0, 0, 538, 0, 0); wxBoxSizer* btn_sizer = new wxBoxSizer(wxVERTICAL); - m_button_add_machine = new Button(this, "", "monitor_add_machine", FromDIP(23)); - m_button_add_machine->SetCornerRadius(10); + m_button_add_machine = new Button(this, "", "monitor_add_machine", FromDIP(24)); + m_button_add_machine->SetCornerRadius(FromDIP(12)); StateColor button_bg( std::pair(0xCECECE, StateColor::Pressed), std::pair(0xCECECE, StateColor::Hovered), @@ -129,7 +129,7 @@ MonitorPanel::~MonitorPanel() if (m_refresh_timer) m_refresh_timer->Stop(); - + delete m_refresh_timer; } void MonitorPanel::init_bitmap() diff --git a/src/slic3r/GUI/Monitor.hpp b/src/slic3r/GUI/Monitor.hpp index 4517319e3a..a554fbc6cb 100644 --- a/src/slic3r/GUI/Monitor.hpp +++ b/src/slic3r/GUI/Monitor.hpp @@ -105,7 +105,7 @@ private: wxBitmap m_arrow_img; int last_wifi_signal = -1; - wxTimer* m_refresh_timer; + wxTimer* m_refresh_timer = nullptr; int last_status; bool m_initialized { false }; diff --git a/src/slic3r/GUI/MsgDialog.cpp b/src/slic3r/GUI/MsgDialog.cpp index 4a8ef590ea..f90656410e 100644 --- a/src/slic3r/GUI/MsgDialog.cpp +++ b/src/slic3r/GUI/MsgDialog.cpp @@ -123,7 +123,7 @@ Button* MsgDialog::add_button(wxWindowID btn_id, bool set_focus /*= false*/, con btn->SetMinSize(MSG_DIALOG_LONGER_BUTTON_SIZE); } - btn->SetCornerRadius(12); + btn->SetCornerRadius(FromDIP(12)); StateColor btn_bg_green( std::pair(wxColour(27, 136, 68), StateColor::Pressed), std::pair(wxColour(61, 203, 115), StateColor::Hovered), diff --git a/src/slic3r/GUI/NetworkTestDialog.cpp b/src/slic3r/GUI/NetworkTestDialog.cpp new file mode 100644 index 0000000000..ccf3c9a375 --- /dev/null +++ b/src/slic3r/GUI/NetworkTestDialog.cpp @@ -0,0 +1,821 @@ +#include "NetworkTestDialog.hpp" +#include "I18N.hpp" + +#include "libslic3r/Utils.hpp" +#include "GUI.hpp" +#include "GUI_App.hpp" +#include "I18N.hpp" +#include "slic3r/Utils/Http.hpp" +#include "libslic3r/AppConfig.hpp" +#include + + +namespace Slic3r { +namespace GUI { + +wxDECLARE_EVENT(EVT_UPDATE_RESULT, wxCommandEvent); + +wxDEFINE_EVENT(EVT_UPDATE_RESULT, wxCommandEvent); + +static wxString NA_STR = _L("N/A"); + +NetworkTestDialog::NetworkTestDialog(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style) + : DPIDialog(parent,wxID_ANY,from_u8((boost::format(_utf8(L("Network Test")))).str()),wxDefaultPosition, + wxSize(1000, 700), + /*wxCAPTION*/wxDEFAULT_DIALOG_STYLE|wxMAXIMIZE_BOX|wxMINIMIZE_BOX|wxRESIZE_BORDER) +{ + this->SetSizeHints(wxDefaultSize, wxDefaultSize); + + wxBoxSizer* main_sizer; + main_sizer = new wxBoxSizer(wxVERTICAL); + + wxBoxSizer* top_sizer = create_top_sizer(this); + main_sizer->Add(top_sizer, 0, wxEXPAND, 5); + + wxBoxSizer* info_sizer = create_info_sizer(this); + main_sizer->Add(info_sizer, 0, wxEXPAND, 5); + + wxBoxSizer* content_sizer = create_content_sizer(this); + main_sizer->Add(content_sizer, 0, wxEXPAND, 5); + + wxBoxSizer* result_sizer = create_result_sizer(this); + main_sizer->Add(result_sizer, 1, wxEXPAND, 5); + + set_default(); + + init_bind(); + + this->SetSizer(main_sizer); + this->Layout(); + + this->Centre(wxBOTH); +} + +wxBoxSizer* NetworkTestDialog::create_top_sizer(wxWindow* parent) +{ + auto sizer = new wxBoxSizer(wxVERTICAL); + + auto line_sizer = new wxBoxSizer(wxHORIZONTAL); + btn_start = new wxButton(this, wxID_ANY, _L("Start Test Multi-Thread"), wxDefaultPosition, wxDefaultSize, 0); + line_sizer->Add(btn_start, 0, wxALL, 5); + + btn_start_sequence = new wxButton(this, wxID_ANY, _L("Start Test Single-Thread"), wxDefaultPosition, wxDefaultSize, 0); + line_sizer->Add(btn_start_sequence, 0, wxALL, 5); + + btn_download_log = new wxButton(this, wxID_ANY, _L("Export Log"), wxDefaultPosition, wxDefaultSize, 0); + line_sizer->Add(btn_download_log, 0, wxALL, 5); + btn_download_log->Hide(); + + btn_start->Bind(wxEVT_BUTTON, [this](wxCommandEvent &evt) { + start_all_job(); + }); + btn_start_sequence->Bind(wxEVT_BUTTON, [this](wxCommandEvent &evt) { + start_all_job_sequence(); + }); + sizer->Add(line_sizer, 0, wxEXPAND, 5); + return sizer; +} + +wxBoxSizer* NetworkTestDialog::create_info_sizer(wxWindow* parent) +{ + auto sizer = new wxBoxSizer(wxVERTICAL); + + text_basic_info = new wxStaticText(this, wxID_ANY, _L("Basic Info"), wxDefaultPosition, wxDefaultSize, 0); + text_basic_info->Wrap(-1); + sizer->Add(text_basic_info, 0, wxALL, 5); + + wxBoxSizer* version_sizer = new wxBoxSizer(wxHORIZONTAL); + text_version_title = new wxStaticText(this, wxID_ANY, _L("Studio Version:"), wxDefaultPosition, wxDefaultSize, 0); + text_version_title->Wrap(-1); + version_sizer->Add(text_version_title, 0, wxALL, 5); + + wxString text_version = get_studio_version(); + text_version_val = new wxStaticText(this, wxID_ANY, text_version, wxDefaultPosition, wxDefaultSize, 0); + text_version_val->Wrap(-1); + version_sizer->Add(text_version_val, 0, wxALL, 5); + sizer->Add(version_sizer, 1, wxEXPAND, 5); + + wxBoxSizer* sys_sizer = new wxBoxSizer(wxHORIZONTAL); + + txt_sys_info_title = new wxStaticText(this, wxID_ANY, _L("System Version:"), wxDefaultPosition, wxDefaultSize, 0); + txt_sys_info_title->Wrap(-1); + sys_sizer->Add(txt_sys_info_title, 0, wxALL, 5); + + txt_sys_info_value = new wxStaticText(this, wxID_ANY, get_os_info(), wxDefaultPosition, wxDefaultSize, 0); + txt_sys_info_value->Wrap(-1); + sys_sizer->Add(txt_sys_info_value, 0, wxALL, 5); + + sizer->Add(sys_sizer, 1, wxEXPAND, 5); + + wxBoxSizer* line_sizer = new wxBoxSizer(wxHORIZONTAL); + txt_dns_info_title = new wxStaticText(this, wxID_ANY, _L("DNS Server:"), wxDefaultPosition, wxDefaultSize, 0); + txt_dns_info_title->Wrap(-1); + txt_dns_info_title->Hide(); + line_sizer->Add(txt_dns_info_title, 0, wxALL, 5); + + txt_dns_info_value = new wxStaticText(this, wxID_ANY, get_dns_info(), wxDefaultPosition, wxDefaultSize, 0); + txt_dns_info_value->Hide(); + line_sizer->Add(txt_dns_info_value, 0, wxALL, 5); + sizer->Add(line_sizer, 1, wxEXPAND, 5); + + return sizer; +} + +wxBoxSizer* NetworkTestDialog::create_content_sizer(wxWindow* parent) +{ + auto sizer = new wxBoxSizer(wxVERTICAL); + + wxFlexGridSizer* grid_sizer; + grid_sizer = new wxFlexGridSizer(0, 3, 0, 0); + grid_sizer->SetFlexibleDirection(wxBOTH); + grid_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + + btn_link = new wxButton(this, wxID_ANY, _L("Test BambuLab"), wxDefaultPosition, wxDefaultSize, 0); + grid_sizer->Add(btn_link, 0, wxEXPAND | wxALL, 5); + + text_link_title = new wxStaticText(this, wxID_ANY, _L("Test BambuLab:"), wxDefaultPosition, wxDefaultSize, 0); + text_link_title->Wrap(-1); + grid_sizer->Add(text_link_title, 0, wxALIGN_RIGHT | wxALL, 5); + + text_link_val = new wxStaticText(this, wxID_ANY, _L("N/A"), wxDefaultPosition, wxDefaultSize, 0); + text_link_val->Wrap(-1); + grid_sizer->Add(text_link_val, 0, wxALL, 5); + + btn_bing = new wxButton(this, wxID_ANY, _L("Test Bing.com"), wxDefaultPosition, wxDefaultSize, 0); + grid_sizer->Add(btn_bing, 0, wxEXPAND | wxALL, 5); + + text_bing_title = new wxStaticText(this, wxID_ANY, _L("Test bing.com:"), wxDefaultPosition, wxDefaultSize, 0); + text_bing_title->Wrap(-1); + grid_sizer->Add(text_bing_title, 0, wxALIGN_RIGHT | wxALL, 5); + + text_bing_val = new wxStaticText(this, wxID_ANY, _L("N/A"), wxDefaultPosition, wxDefaultSize, 0); + text_bing_val->Wrap(-1); + grid_sizer->Add(text_bing_val, 0, wxALL, 5); + + btn_iot = new wxButton(this, wxID_ANY, _L("Test HTTP"), wxDefaultPosition, wxDefaultSize, 0); + grid_sizer->Add(btn_iot, 0, wxEXPAND | wxALL, 5); + + text_iot_title = new wxStaticText(this, wxID_ANY, _L("Test HTTP Service:"), wxDefaultPosition, wxDefaultSize, 0); + text_iot_title->Wrap(-1); + grid_sizer->Add(text_iot_title, 0, wxALIGN_RIGHT | wxALL, 5); + + text_iot_value = new wxStaticText(this, wxID_ANY, _L("N/A"), wxDefaultPosition, wxDefaultSize, 0); + text_iot_value->Wrap(-1); + grid_sizer->Add(text_iot_value, 0, wxALL, 5); + + btn_oss = new wxButton(this, wxID_ANY, _L("Test storage"), wxDefaultPosition, wxDefaultSize, 0); + grid_sizer->Add(btn_oss, 0, wxEXPAND | wxALL, 5); + + text_oss_title = new wxStaticText(this, wxID_ANY, _L("Test Storage Upload:"), wxDefaultPosition, wxDefaultSize, 0); + text_oss_title->Wrap(-1); + grid_sizer->Add(text_oss_title, 0, wxALIGN_RIGHT | wxALL, 5); + + text_oss_value = new wxStaticText(this, wxID_ANY, _L("N/A"), wxDefaultPosition, wxDefaultSize, 0); + text_oss_value->Wrap(-1); + grid_sizer->Add(text_oss_value, 0, wxALL, 5); + + btn_oss_upgrade = new wxButton(this, wxID_ANY, _L("Test storage upgrade"), wxDefaultPosition, wxDefaultSize, 0); + grid_sizer->Add(btn_oss_upgrade, 0, wxEXPAND | wxALL, 5); + + text_oss_upgrade_title = new wxStaticText(this, wxID_ANY, _L("Test Storage Upgrade:"), wxDefaultPosition, wxDefaultSize, 0); + text_oss_upgrade_title->Wrap(-1); + grid_sizer->Add(text_oss_upgrade_title, 0, wxALIGN_RIGHT | wxALL, 5); + + text_oss_upgrade_value = new wxStaticText(this, wxID_ANY, _L("N/A"), wxDefaultPosition, wxDefaultSize, 0); + text_oss_upgrade_value->Wrap(-1); + grid_sizer->Add(text_oss_upgrade_value, 0, wxALL, 5); + + btn_oss_download = new wxButton(this, wxID_ANY, _L("Test storage download"), wxDefaultPosition, wxDefaultSize, 0); + grid_sizer->Add(btn_oss_download, 0, wxEXPAND | wxALL, 5); + + text_oss_download_title = new wxStaticText(this, wxID_ANY, _L("Test Storage Download:"), wxDefaultPosition, wxDefaultSize, 0); + text_oss_download_title->Wrap(-1); + grid_sizer->Add(text_oss_download_title, 0, wxALIGN_RIGHT | wxALL, 5); + + text_oss_download_value = new wxStaticText(this, wxID_ANY, _L("N/A"), wxDefaultPosition, wxDefaultSize, 0); + text_oss_download_value->Wrap(-1); + grid_sizer->Add(text_oss_download_value, 0, wxALL, 5); + + btn_oss_upload = new wxButton(this, wxID_ANY, _L("Test Storage Upload"), wxDefaultPosition, wxDefaultSize, 0); + grid_sizer->Add(btn_oss_upload, 0, wxEXPAND | wxALL, 5); + + text_oss_upload_title = new wxStaticText(this, wxID_ANY, _L("Test Storage Upload:"), wxDefaultPosition, wxDefaultSize, 0); + text_oss_upload_title->Wrap(-1); + grid_sizer->Add(text_oss_upload_title, 0, wxALIGN_RIGHT | wxALL, 5); + + text_oss_upload_value = new wxStaticText(this, wxID_ANY, _L("N/A"), wxDefaultPosition, wxDefaultSize, 0); + text_oss_upload_value->Wrap(-1); + grid_sizer->Add(text_oss_upload_value, 0, wxALL, 5); + + btn_oss_upload->Hide(); + text_oss_upload_title->Hide(); + text_oss_upload_value->Hide(); + + sizer->Add(grid_sizer, 1, wxEXPAND, 5); + + btn_link->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { + start_test_bambulab_thread(); + }); + + btn_bing->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { + start_test_bing_thread(); + }); + + btn_iot->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { + start_test_iot_thread(); + }); + + btn_oss->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { + start_test_oss_thread(); + }); + + btn_oss_upgrade->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { + start_test_oss_upgrade_thread(); + }); + + btn_oss_download->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { + start_test_oss_download_thread(); + }); + + return sizer; +} +wxBoxSizer* NetworkTestDialog::create_result_sizer(wxWindow* parent) +{ + auto sizer = new wxBoxSizer(wxVERTICAL); + text_result = new wxStaticText(this, wxID_ANY, _L("Log Info"), wxDefaultPosition, wxDefaultSize, 0); + text_result->Wrap(-1); + sizer->Add(text_result, 0, wxALL, 5); + + txt_log = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE); + sizer->Add(txt_log, 1, wxALL | wxEXPAND, 5); + return sizer; +} + +NetworkTestDialog::~NetworkTestDialog() +{ + ; +} + +void NetworkTestDialog::init_bind() +{ + Bind(EVT_UPDATE_RESULT, [this](wxCommandEvent& evt) { + if (evt.GetInt() == TEST_BAMBULAB_JOB) { + text_link_val->SetLabelText(evt.GetString()); + } else if (evt.GetInt() == TEST_BING_JOB) { + text_bing_val->SetLabelText(evt.GetString()); + } else if (evt.GetInt() == TEST_IOT_JOB) { + text_iot_value->SetLabelText(evt.GetString()); + } else if (evt.GetInt() == TEST_OSS_JOB) { + text_oss_value->SetLabelText(evt.GetString()); + } else if (evt.GetInt() == TEST_OSS_UPGRADE_JOB) { + text_oss_upgrade_value->SetLabelText(evt.GetString()); + } else if (evt.GetInt() == TEST_OSS_DOWNLOAD_JOB) { + text_oss_download_value->SetLabelText(evt.GetString()); + } else if (evt.GetInt() == TEST_OSS_UPLOAD_JOB) { + text_oss_upload_value->SetLabelText(evt.GetString()); + } + + std::time_t t = std::time(0); + std::tm* now_time = std::localtime(&t); + std::stringstream buf; + buf << std::put_time(now_time, "%a %b %d %H:%M:%S"); + wxString info = wxString::Format("%s:", buf.str()) + evt.GetString() + "\n"; + try { + txt_log->AppendText(info); + } + catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "Unkown Exception in print_log, exception=" << e.what(); + return; + } + catch (...) { + BOOST_LOG_TRIVIAL(error) << "Unkown Exception in print_log"; + return; + } + return; + }); + + Bind(wxEVT_CLOSE_WINDOW, &NetworkTestDialog::on_close, this); +} + +wxString NetworkTestDialog::get_os_info() +{ + int major = 0, minor = 0, micro = 0; + wxGetOsVersion(&major, &minor, µ); + std::string os_version = (boost::format("%1%.%2%.%3%") % major % minor % micro).str(); + wxString text_sys_version = wxGetOsDescription() + wxString::Format("%d.%d.%d", major, minor, micro); + return text_sys_version; +} + + +wxString NetworkTestDialog::get_dns_info() +{ + return NA_STR; +} + +void NetworkTestDialog::start_all_job() +{ + start_test_bing_thread(); + start_test_bambulab_thread(); + start_test_iot_thread(); + start_test_oss_thread(); + start_test_oss_upgrade_thread(); + start_test_oss_download_thread(); + start_test_ping_thread(); +} + +void NetworkTestDialog::start_all_job_sequence() +{ + m_sequence_job = new boost::thread([this] { + update_status(-1, "start_test_sequence"); + start_test_bing(); + if (m_closing) return; + start_test_bambulab(); + if (m_closing) return; + start_test_oss(); + if (m_closing) return; + start_test_oss_upgrade(); + if (m_closing) return; + start_test_oss_download(); + update_status(-1, "end_test_sequence"); + }); +} + +void NetworkTestDialog::start_test_bing() +{ + m_in_testing[TEST_BING_JOB] = true; + update_status(TEST_BING_JOB, "test bing start..."); + + std::string url = "http://www.bing.com/"; + Slic3r::Http http = Slic3r::Http::get(url); + + int result = -1; + http.timeout_max(10) + .on_complete([this, &result](std::string body, unsigned status) { + try { + if (status == 200) { + result = 0; + } + } + catch (...) { + ; + } + }) + .on_ip_resolve([this](std::string ip) { + wxString ip_report = wxString::Format("test bing ip resolved = %s", ip); + update_status(TEST_BING_JOB, ip_report); + }) + .on_error([this](std::string body, std::string error, unsigned int status) { + wxString info = wxString::Format("status=%u, body=%s, error=%s", status, body, error); + this->update_status(TEST_BING_JOB, "test bing failed"); + this->update_status(-1, info); + }).perform_sync(); + if (result == 0) { + update_status(TEST_BING_JOB, "test bing ok"); + } + m_in_testing[TEST_BING_JOB] = false; +} + +void NetworkTestDialog::start_test_bambulab() +{ + m_in_testing[TEST_BAMBULAB_JOB] = true; + update_status(TEST_BAMBULAB_JOB, "test bambulab start..."); + + std::string platform = "windows"; + +#ifdef __WINDOWS__ + platform = "windows"; +#endif +#ifdef __APPLE__ + platform = "macos"; +#endif +#ifdef __LINUX__ + platform = "linux"; +#endif + std::string query_params = (boost::format("?name=slicer&version=%1%&guide_version=%2%") + % VersionInfo::convert_full_version(SLIC3R_VERSION) + % VersionInfo::convert_full_version("0.0.0.1") + ).str(); + + AppConfig* app_config = wxGetApp().app_config; + std::string url = wxGetApp().get_http_url(app_config->get_country_code()) + query_params; + Slic3r::Http http = Slic3r::Http::get(url); + int result = -1; + http.header("accept", "application/json") + .timeout_max(10) + .on_complete([this, &result](std::string body, unsigned status) { + try { + if (status == 200) { + result = 0; + } + } + catch (...) { + ; + } + }) + .on_ip_resolve([this](std::string ip) { + wxString ip_report = wxString::Format("test bambulab ip resolved = %s", ip); + update_status(TEST_BAMBULAB_JOB, ip_report); + }) + .on_error([this](std::string body, std::string error, unsigned int status) { + wxString info = wxString::Format("status=%u, body=%s, error=%s", status, body, error); + this->update_status(TEST_BAMBULAB_JOB, "test bambulab failed"); + this->update_status(-1, info); + }).perform_sync(); + if (result == 0) { + update_status(TEST_BAMBULAB_JOB, "test bambulab ok"); + } + m_in_testing[TEST_BAMBULAB_JOB] = false; +} + +void NetworkTestDialog::start_test_iot() +{ + m_in_testing[TEST_IOT_JOB] = true; + update_status(TEST_IOT_JOB, "test http start..."); + NetworkAgent* agent = wxGetApp().getAgent(); + if (agent) { + unsigned int http_code; + std::string http_body; + if (!agent->is_user_login()) { + update_status(TEST_IOT_JOB, "please login first"); + } else { + int result = agent->get_user_print_info(&http_code, &http_body); + if (result == 0) { + update_status(TEST_IOT_JOB, "test http ok"); + } else { + update_status(TEST_IOT_JOB, "test http failed"); + wxString info = wxString::Format("test http failed, status = %u, error = %s", http_code, http_body); + update_status(-1, info); + } + } + } else { + update_status(TEST_IOT_JOB, "please install network module first"); + } + m_in_testing[TEST_IOT_JOB] = false; +} + +void NetworkTestDialog::start_test_oss() +{ + m_in_testing[TEST_OSS_JOB] = true; + update_status(TEST_OSS_JOB, "test storage start..."); + + std::string url = "http://upload-file.bambulab.com"; + + AppConfig* config = wxGetApp().app_config; + if (config) { + if (config->get_country_code() == "CN") + url = "http://upload-file.bambulab.cn"; + } + + Slic3r::Http http = Slic3r::Http::get(url); + + int result = -1; + http.timeout_max(15) + .on_complete([this, &result](std::string body, unsigned status) { + try { + if (status == 200) { + result = 0; + } + } + catch (...) { + ; + } + }) + .on_ip_resolve([this](std::string ip) { + wxString ip_report = wxString::Format("test oss ip resolved = %s", ip); + update_status(TEST_OSS_JOB, ip_report); + }) + .on_error([this, &result](std::string body, std::string error, unsigned int status) { + if (status == 403) { + result = 0; + } else { + wxString info = wxString::Format("status=%u, body=%s, error=%s", status, body, error); + this->update_status(TEST_OSS_JOB, "test storage failed"); + this->update_status(-1, info); + } + }).perform_sync(); + if (result == 0) { + update_status(TEST_OSS_JOB, "test storage ok"); + } + m_in_testing[TEST_OSS_JOB] = false; +} + +void NetworkTestDialog::start_test_oss_upgrade() +{ + m_in_testing[TEST_OSS_UPGRADE_JOB] = true; + update_status(TEST_OSS_UPGRADE_JOB, "test storage upgrade start..."); + + std::string url = "http://upgrade-file.bambulab.com"; + + AppConfig* config = wxGetApp().app_config; + if (config) { + if (config->get_country_code() == "CN") + url = "http://upgrade-file.bambulab.cn"; + } + + Slic3r::Http http = Slic3r::Http::get(url); + + int result = -1; + http.timeout_max(15) + .on_complete([this, &result](std::string body, unsigned status) { + try { + if (status == 200) { + result = 0; + } + } + catch (...) { + ; + } + }) + .on_ip_resolve([this](std::string ip) { + wxString ip_report = wxString::Format("test storage upgrade ip resolved = %s", ip); + update_status(TEST_OSS_UPGRADE_JOB, ip_report); + }) + .on_error([this, &result](std::string body, std::string error, unsigned int status) { + if (status == 403) { + result = 0; + } + else { + wxString info = wxString::Format("status=%u, body=%s, error=%s", status, body, error); + this->update_status(TEST_OSS_UPGRADE_JOB, "test storage upgrade failed"); + this->update_status(-1, info); + } + }).perform_sync(); + + if (result == 0) { + update_status(TEST_OSS_UPGRADE_JOB, "test storage upgrade ok"); + } + m_in_testing[TEST_OSS_UPGRADE_JOB] = false; +} + +void NetworkTestDialog::start_test_oss_download() +{ + int result = 0; + // get country_code + AppConfig* app_config = wxGetApp().app_config; + if (!app_config) { + update_status(TEST_OSS_DOWNLOAD_JOB, "app config is nullptr"); + return; + } + + m_in_testing[TEST_OSS_DOWNLOAD_JOB] = true; + update_status(TEST_OSS_DOWNLOAD_JOB, "test storage download start..."); + m_download_cancel = false; + // get temp path + fs::path target_file_path = (fs::temp_directory_path() / "test_storage_download.zip"); + fs::path tmp_path = target_file_path; + tmp_path += (boost::format(".%1%%2%") % get_current_pid() % ".tmp").str(); + + // get_url + std::string url = wxGetApp().get_plugin_url(app_config->get_country_code()); + std::string download_url; + Slic3r::Http http_url = Slic3r::Http::get(url); + update_status(-1, "[test_oss_download]: url=" + url); + + http_url.on_complete( + [&download_url](std::string body, unsigned status) { + try { + json j = json::parse(body); + std::string message = j["message"].get(); + + if (message == "success") { + json resource = j.at("resources"); + if (resource.is_array()) { + for (auto iter = resource.begin(); iter != resource.end(); iter++) { + Semver version; + std::string url; + std::string type; + std::string vendor; + std::string description; + for (auto sub_iter = iter.value().begin(); sub_iter != iter.value().end(); sub_iter++) { + if (boost::iequals(sub_iter.key(), "type")) { + type = sub_iter.value(); + BOOST_LOG_TRIVIAL(info) << "[test_storage_download]: get version of settings's type, " << sub_iter.value(); + } + else if (boost::iequals(sub_iter.key(), "version")) { + version = *(Semver::parse(sub_iter.value())); + } + else if (boost::iequals(sub_iter.key(), "description")) { + description = sub_iter.value(); + } + else if (boost::iequals(sub_iter.key(), "url")) { + url = sub_iter.value(); + } + } + BOOST_LOG_TRIVIAL(info) << "[test_storage_download]: get type " << type << ", version " << version.to_string() << ", url " << url; + download_url = url; + } + } + } + else { + BOOST_LOG_TRIVIAL(info) << "[test_storage_download]: get version of plugin failed, body=" << body; + } + } + catch (...) { + BOOST_LOG_TRIVIAL(error) << "[test_storage_download]: catch unknown exception"; + ; + } + }).on_error( + [&result, this](std::string body, std::string error, unsigned int status) { + BOOST_LOG_TRIVIAL(error) << "[test_storage_download] on_error: " << error << ", body = " << body; + wxString info = wxString::Format("status=%u, body=%s, error=%s", status, body, error); + this->update_status(TEST_OSS_DOWNLOAD_JOB, "test storage download failed"); + this->update_status(-1, info); + result = -1; + }).perform_sync(); + + if (result < 0) { + this->update_status(TEST_OSS_DOWNLOAD_JOB, "test storage download failed"); + m_in_testing[TEST_OSS_DOWNLOAD_JOB] = false; + return; + } + + if (download_url.empty()) { + BOOST_LOG_TRIVIAL(info) << "[test_oss_download]: no availaible plugin found for this app version: " << SLIC3R_VERSION; + this->update_status(TEST_OSS_DOWNLOAD_JOB, "test storage download failed"); + m_in_testing[TEST_OSS_DOWNLOAD_JOB] = false; + return; + } + if (m_download_cancel) { + this->update_status(TEST_OSS_DOWNLOAD_JOB, "test storage download canceled"); + m_in_testing[TEST_OSS_DOWNLOAD_JOB] = false; + return; + } + + bool cancel = false; + BOOST_LOG_TRIVIAL(info) << "[test_storage_download] get_url = " << download_url; + + // download + Slic3r::Http http = Slic3r::Http::get(download_url); + int reported_percent = 0; + http.on_progress( + [this, &result, &reported_percent](Slic3r::Http::Progress progress, bool& cancel) { + int percent = 0; + if (progress.dltotal != 0) { + percent = progress.dlnow * 100 / progress.dltotal; + } + if (percent - reported_percent >= 10) { + reported_percent = percent; + std::string download_progress_info = (boost::format("downloading %1%%%") % percent).str(); + this->update_status(TEST_OSS_DOWNLOAD_JOB, download_progress_info); + } + + BOOST_LOG_TRIVIAL(info) << "[test_storage_download] progress: " << reported_percent; + cancel = m_download_cancel; + + if (cancel) + result = -1; + }) + .on_complete([this, tmp_path, target_file_path](std::string body, unsigned status) { + BOOST_LOG_TRIVIAL(info) << "[test_storage_download] completed"; + bool cancel = false; + fs::fstream file(tmp_path, std::ios::out | std::ios::binary | std::ios::trunc); + file.write(body.c_str(), body.size()); + file.close(); + fs::rename(tmp_path, target_file_path); + this->update_status(TEST_OSS_DOWNLOAD_JOB, "test storage download ok"); + }) + .on_error([this, &result](std::string body, std::string error, unsigned int status) { + BOOST_LOG_TRIVIAL(error) << "[test_oss_download] downloading... on_error: " << error << ", body = " << body; + wxString info = wxString::Format("status=%u, body=%s, error=%s", status, body, error); + this->update_status(TEST_OSS_DOWNLOAD_JOB, "test storage download failed"); + this->update_status(-1, info); + result = -1; + }); + http.perform_sync(); + if (result < 0) { + this->update_status(TEST_OSS_DOWNLOAD_JOB, "test storage download failed"); + } else { + this->update_status(TEST_OSS_DOWNLOAD_JOB, "test storage download ok"); + } + m_in_testing[TEST_OSS_DOWNLOAD_JOB] = false; + return; +} + +void NetworkTestDialog::start_test_oss_upload() +{ + ; +} + +void NetworkTestDialog::start_test_ping_thread() +{ + test_job[TEST_PING_JOB] = new boost::thread([this] { + m_in_testing[TEST_PING_JOB] = true; + + m_in_testing[TEST_PING_JOB] = false; + }); +} + +void NetworkTestDialog::start_test_bing_thread() +{ + test_job[TEST_BING_JOB] = new boost::thread([this] { + start_test_bing(); + }); +} + +void NetworkTestDialog::start_test_bambulab_thread() +{ + if (m_in_testing[TEST_BAMBULAB_JOB]) return; + test_job[TEST_BAMBULAB_JOB] = new boost::thread([this] { + start_test_bambulab(); + }); +} + +void NetworkTestDialog::start_test_iot_thread() +{ + if (m_in_testing[TEST_IOT_JOB]) return; + test_job[TEST_IOT_JOB] = new boost::thread([this] { + start_test_iot(); + }); +} + +void NetworkTestDialog::start_test_oss_thread() +{ + test_job[TEST_OSS_JOB] = new boost::thread([this] { + start_test_oss(); + }); +} + +void NetworkTestDialog::start_test_oss_upgrade_thread() +{ + test_job[TEST_OSS_UPGRADE_JOB] = new boost::thread([this] { + start_test_oss_upgrade(); + }); +} + +void NetworkTestDialog::start_test_oss_download_thread() +{ + test_job[TEST_OSS_DOWNLOAD_JOB] = new boost::thread([this] { + start_test_oss_download(); + }); +} + +void NetworkTestDialog::start_test_oss_upload_thread() +{ + test_job[TEST_OSS_UPLOAD_JOB] = new boost::thread([this] { + start_test_oss_upload(); + }); +} + +void NetworkTestDialog::on_close(wxCloseEvent& event) +{ + m_download_cancel = true; + m_closing = true; + for (int i = 0; i < TEST_JOB_MAX; i++) { + if (test_job[i]) { + test_job[i]->join(); + test_job[i] = nullptr; + } + } + + event.Skip(); +} + + +wxString NetworkTestDialog::get_studio_version() +{ + return wxString(SLIC3R_VERSION); +} + +void NetworkTestDialog::set_default() +{ + for (int i = 0; i < TEST_JOB_MAX; i++) { + test_job[i] = nullptr; + m_in_testing[i] = false; + } + + m_sequence_job = nullptr; + + text_version_val->SetLabelText(get_studio_version()); + txt_sys_info_value->SetLabelText(get_os_info()); + txt_dns_info_value->SetLabelText(get_dns_info()); + text_link_val->SetLabelText(NA_STR); + text_bing_val->SetLabelText(NA_STR); + text_iot_value->SetLabelText(NA_STR); + text_oss_value->SetLabelText(NA_STR); + text_oss_upgrade_value->SetLabelText(NA_STR); + text_oss_download_value->SetLabelText(NA_STR); + text_oss_upload_value->SetLabelText(NA_STR); + //text_ping_value->SetLabelText(NA_STR); + m_download_cancel = false; + m_closing = false; +} + + +void NetworkTestDialog::on_dpi_changed(const wxRect &suggested_rect) +{ + ; +} + +void NetworkTestDialog::update_status(int job_id, wxString info) +{ + auto evt = new wxCommandEvent(EVT_UPDATE_RESULT, this->GetId()); + evt->SetString(info); + evt->SetInt(job_id); + wxQueueEvent(this, evt); +} + + +} // namespace GUI +} // namespace Slic3r + + diff --git a/src/slic3r/GUI/NetworkTestDialog.hpp b/src/slic3r/GUI/NetworkTestDialog.hpp new file mode 100644 index 0000000000..a9b93796c4 --- /dev/null +++ b/src/slic3r/GUI/NetworkTestDialog.hpp @@ -0,0 +1,138 @@ +#ifndef slic3r_GUI_NetworkTestDialog_hpp_ +#define slic3r_GUI_NetworkTestDialog_hpp_ + +#include +#include + +#include "GUI_Utils.hpp" +#include "wxExtensions.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Slic3r { +namespace GUI { + +enum TestJob { + TEST_BING_JOB = 0, + TEST_BAMBULAB_JOB = 1, + TEST_IOT_JOB = 2, + TEST_OSS_JOB = 3, + TEST_OSS_UPGRADE_JOB = 4, + TEST_OSS_DOWNLOAD_JOB = 5, + TEST_OSS_UPLOAD_JOB = 6, + TEST_PING_JOB = 7, + TEST_JOB_MAX = 8 +}; + +class NetworkTestDialog : public DPIDialog +{ +protected: + wxButton* btn_start; + wxButton* btn_start_sequence; + wxButton* btn_download_log; + wxStaticText* text_basic_info; + wxStaticText* text_version_title; + wxStaticText* text_version_val; + wxStaticText* txt_sys_info_title; + wxStaticText* txt_sys_info_value; + wxStaticText* txt_dns_info_title; + wxStaticText* txt_dns_info_value; + wxButton* btn_link; + wxStaticText* text_link_title; + wxStaticText* text_link_val; + wxButton* btn_bing; + wxStaticText* text_bing_title; + wxStaticText* text_bing_val; + wxButton* btn_iot; + wxStaticText* text_iot_title; + wxStaticText* text_iot_value; + wxButton* btn_oss; + wxStaticText* text_oss_title; + wxStaticText* text_oss_value; + wxButton* btn_oss_upgrade; + wxStaticText* text_oss_upgrade_title; + wxStaticText* text_oss_upgrade_value; + wxButton* btn_oss_download; + wxStaticText* text_oss_download_title; + wxStaticText* text_oss_download_value; + wxButton* btn_oss_upload; + wxStaticText* text_oss_upload_title; + wxStaticText* text_oss_upload_value; + wxStaticText* text_ping_title; + wxStaticText* text_ping_value; + wxStaticText* text_result; + wxTextCtrl* txt_log; + + wxBoxSizer* create_top_sizer(wxWindow* parent); + wxBoxSizer* create_info_sizer(wxWindow* parent); + wxBoxSizer* create_content_sizer(wxWindow* parent); + wxBoxSizer* create_result_sizer(wxWindow* parent); + + boost::thread* test_job[TEST_JOB_MAX]; + boost::thread* m_sequence_job { nullptr }; + bool m_in_testing[TEST_JOB_MAX]; + bool m_download_cancel = false; + bool m_closing = false; + + void init_bind(); + +public: + NetworkTestDialog(wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxEmptyString, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize(605, 375), long style = wxDEFAULT_DIALOG_STYLE); + + ~NetworkTestDialog(); + + void on_dpi_changed(const wxRect& suggested_rect); + + void set_default(); + wxString get_studio_version(); + wxString get_os_info(); + wxString get_dns_info(); + + void start_all_job(); + void start_all_job_sequence(); + void start_test_bing_thread(); + void start_test_bambulab_thread(); + void start_test_iot_thread(); + void start_test_oss_thread(); + void start_test_oss_upgrade_thread(); + void start_test_oss_download_thread(); + void start_test_oss_upload_thread(); + void start_test_ping_thread(); + + void start_test_bing(); + void start_test_bambulab(); + void start_test_iot(); + void start_test_oss(); + void start_test_oss_upgrade(); + void start_test_oss_download(); + void start_test_oss_upload(); + + void on_close(wxCloseEvent& event); + + void update_status(int job_id, wxString info); +}; + +} // namespace GUI +} // namespace Slic3r + +#endif diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 5a921c0aef..736b40fe17 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -20,6 +20,11 @@ #include "GUI_App.hpp" +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include + static constexpr float GAP_WIDTH = 10.0f; static constexpr float SPACE_RIGHT_PANEL = 10.0f; static constexpr float FADING_OUT_DURATION = 2.0f; @@ -580,8 +585,17 @@ void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& img void NotificationManager::PopNotification::bbl_render_left_sign(ImGuiWrapper &imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { ImDrawList *draw_list = ImGui::GetWindowDrawList(); - draw_list->AddRectFilled(ImVec2(win_pos_x - win_size_x, win_pos_y), ImVec2(win_pos_x - win_size_x + m_WindowRadius * 2, win_pos_y + win_size_y),ImGui::GetColorU32(ImVec4(m_CurrentColor.x, m_CurrentColor.y, m_CurrentColor.z,m_current_fade_opacity)), m_WindowRadius); - draw_list->AddRectFilled(ImVec2(win_pos_x - win_size_x-m_WindowRadius, win_pos_y), ImVec2(win_pos_x - win_size_x + 10, win_pos_y + win_size_y),ImGui::GetColorU32(ImVec4(m_CurrentColor.x, m_CurrentColor.y, m_CurrentColor.z, m_current_fade_opacity)), 0); + + ImVec2 round_rect_pos = ImVec2(win_pos_x - win_size_x, win_pos_y); + ImVec2 round_rect_size = ImVec2(m_WindowRadius * 2, win_size_y); + + ImVec2 rect_pos = round_rect_pos + ImVec2(m_WindowRadius, 0); + ImVec2 rect_size = ImVec2(m_WindowRadius + 2 * wxGetApp().plater()->get_current_canvas3D()->get_scale(), win_size_y); + + ImU32 clr = ImGui::GetColorU32(ImVec4(m_CurrentColor.x, m_CurrentColor.y, m_CurrentColor.z, m_current_fade_opacity)); + + draw_list->AddRectFilled(round_rect_pos, round_rect_pos + round_rect_size, clr, m_WindowRadius); + draw_list->AddRectFilled(rect_pos, rect_pos + rect_size, clr, 0); } void NotificationManager::PopNotification::render_left_sign(ImGuiWrapper& imgui) diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 12aa398b69..73e2847898 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -333,18 +333,6 @@ void ObjectDataViewModelNode::SetPlateIdx(const int& idx) m_plate_idx = idx; } -// BBS -bool ObjectDataViewModelNode::IsTimelapseWipeTower() const -{ - if (m_model_object == nullptr) - return false; - - if (!(m_type & itObject) && !(m_type & itInstance)) - return false; - - return m_model_object->is_timelapse_wipe_tower; -} - void ObjectDataViewModelNode::UpdateExtruderAndColorIcon(wxString extruder /*= ""*/) { if (m_type == itVolume && m_volume_type != ModelVolumeType::MODEL_PART && m_volume_type != ModelVolumeType::PARAMETER_MODIFIER) diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index 24aa993686..749edae4b4 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -227,7 +227,6 @@ public: bool HasSupportPainting() const { return m_support_enable; } bool IsActionEnabled() const { return m_action_enable; } void UpdateExtruderAndColorIcon(wxString extruder = ""); - bool IsTimelapseWipeTower() const; // use this function only for childrens void AssignAllVal(ObjectDataViewModelNode& from_node) diff --git a/src/slic3r/GUI/ParamsPanel.cpp b/src/slic3r/GUI/ParamsPanel.cpp index 13574f3f42..ed1d4a718e 100644 --- a/src/slic3r/GUI/ParamsPanel.cpp +++ b/src/slic3r/GUI/ParamsPanel.cpp @@ -67,7 +67,7 @@ TipsDialog::TipsDialog(wxWindow *parent, const wxString &title) m_confirm->SetTextColor(wxColour(255, 255, 255)); m_confirm->SetSize(TIPS_DIALOG_BUTTON_SIZE); m_confirm->SetMinSize(TIPS_DIALOG_BUTTON_SIZE); - m_confirm->SetCornerRadius(12); + m_confirm->SetCornerRadius(FromDIP(12)); m_confirm->Bind(wxEVT_LEFT_DOWN, &TipsDialog::on_ok, this); m_sizer_right->Add(m_confirm, 0, wxALL, FromDIP(5)); @@ -210,8 +210,7 @@ ParamsPanel::ParamsPanel( wxWindow* parent, wxWindowID id, const wxPoint& pos, c m_process_icon = new ScalableButton(m_top_panel, wxID_ANY, "process"); - m_title_label = new Label(Label::Body_14, _L("Process"), m_top_panel); - m_title_label->Wrap( -1 ); + m_title_label = new Label(m_top_panel, _L("Process")); //int width, height; // BBS: new layout @@ -222,8 +221,7 @@ ParamsPanel::ParamsPanel( wxWindow* parent, wxWindowID id, const wxPoint& pos, c m_tips_arrow = new wxStaticBitmap(m_top_panel, wxID_ANY, m_tips_arrow_icon); m_tips_arrow->Hide(); - m_title_view = new Label(Label::Body_14, _L("Advance"), m_top_panel); - m_title_view->Wrap( -1 ); + m_title_view = new Label(m_top_panel, _L("Advance")); m_mode_view = new SwitchButton(m_top_panel, wxID_ABOUT); // BBS: new layout diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index 8e240b04e2..1dd4eddc34 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -1103,8 +1103,12 @@ Vec3d PartPlate::estimate_wipe_tower_size(const double w, const double wipe_volu } wipe_tower_size(2) = max_height; + const DynamicPrintConfig &dconfig = wxGetApp().preset_bundle->prints.get_edited_preset().config; + const ConfigOption* option = dconfig.option("timelapse_no_toolhead"); + bool timelapse_enabled = option ? option->getBool() : false; + double depth = wipe_volume * (plate_extruders.size() - 1) / (layer_height * w); - if (depth > EPSILON) { + if (timelapse_enabled || depth > EPSILON) { float min_wipe_tower_depth = 0.f; auto iter = WipeTower::min_depth_per_height.begin(); while (iter != WipeTower::min_depth_per_height.end()) { @@ -1142,26 +1146,6 @@ Vec3d PartPlate::estimate_wipe_tower_size(const double w, const double wipe_volu return wipe_tower_size; } -double PartPlate::estimate_timelapse_wipe_tower_height(int *highest_extruder_id) const -{ - double max_height = 0; - int highest_extruder = 0; - - // auto m_model = wxGetApp().plater()->get_obj - for (int obj_idx = 0; obj_idx < m_model->objects.size(); obj_idx++) { - if (!contain_instance_totally(obj_idx, 0)) continue; - if (m_model->objects[obj_idx]->is_timelapse_wipe_tower) continue; - - BoundingBoxf3 bbox = m_model->objects[obj_idx]->bounding_box(); - if (bbox.size().z() > max_height) { - max_height = bbox.size().z(); - highest_extruder = m_model->objects[obj_idx]->config.opt_int("extruder"); - } - } - if (highest_extruder_id) *highest_extruder_id = highest_extruder; - return max_height; -} - bool PartPlate::operator<(PartPlate& plate) const { int index = plate.get_index(); @@ -1636,33 +1620,6 @@ void PartPlate::move_instances_to(PartPlate& left_plate, PartPlate& right_plate, return; } -//can add timelapse object -bool PartPlate::can_add_timelapse_object() -{ - bool result = true; - - if (obj_to_instance_set.size() == 0) - return false; - - for (std::set>::iterator it = obj_to_instance_set.begin(); it != obj_to_instance_set.end(); ++it) - { - int obj_id = it->first; - - if (obj_id >= m_model->objects.size()) - continue; - - ModelObject* object = m_model->objects[obj_id]; - - if (object->is_timelapse_wipe_tower) - { - result = false; - break; - } - } - - return result; -} - void PartPlate::generate_logo_polygon(ExPolygon &logo_polygon) { if (m_shape.size() == 4) diff --git a/src/slic3r/GUI/PartPlate.hpp b/src/slic3r/GUI/PartPlate.hpp index 8dbff320a9..4bfc35d6c7 100644 --- a/src/slic3r/GUI/PartPlate.hpp +++ b/src/slic3r/GUI/PartPlate.hpp @@ -238,7 +238,6 @@ public: Vec3d get_origin() { return m_origin; } Vec3d estimate_wipe_tower_size(const double w, const double wipe_volume) const; - double estimate_timelapse_wipe_tower_height(int* highest_extruder_id=NULL) const; std::vector get_extruders() const; /* instance related operations*/ @@ -278,9 +277,6 @@ public: //move instances to left or right PartPlate void move_instances_to(PartPlate& left_plate, PartPlate& right_plate, BoundingBoxf3* bounding_box = nullptr); - //can add timelapse object - bool can_add_timelapse_object(); - /*rendering related functions*/ const Pointfs& get_shape() const { return m_shape; } bool set_shape(const Pointfs& shape, const Pointfs& exclude_areas, Vec2d position, float height_to_lid, float height_to_rod); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 6fc7fba619..ff1c568572 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -451,10 +451,7 @@ Sidebar::Sidebar(Plater *parent) p->m_panel_printer_title->SetBackgroundColor2(0xF1F1F1); p->m_printer_icon = new ScalableButton(p->m_panel_printer_title, wxID_ANY, "printer"); - p->m_text_printer_settings = new wxStaticText(p->m_panel_printer_title, wxID_ANY, _L("Printer"), wxDefaultPosition, wxDefaultSize, 0); - p->m_text_printer_settings->Wrap(-1); - p->m_text_printer_settings->SetFont(Label::Body_14); - p->m_text_printer_settings->SetBackgroundColour(0xF1F1F1); + p->m_text_printer_settings = new Label(p->m_panel_printer_title, _L("Printer")); p->m_printer_setting = new ScalableButton(p->m_panel_printer_title, wxID_ANY, "settings"); p->m_printer_setting->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { @@ -539,7 +536,7 @@ Sidebar::Sidebar(Plater *parent) m_bed_type_list->Select(0); bed_type_sizer->Add(bed_type_title, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, FromDIP(10)); - bed_type_sizer->Add(m_bed_type_list, 1, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL | wxEXPAND, FromDIP(10)); + bed_type_sizer->Add(m_bed_type_list, 1, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(10)); vsizer_printer->Add(bed_type_sizer, 0, wxEXPAND | wxTOP, FromDIP(5)); p->m_panel_printer_content->SetSizer(vsizer_printer); @@ -557,10 +554,7 @@ Sidebar::Sidebar(Plater *parent) wxBoxSizer* bSizer39; bSizer39 = new wxBoxSizer( wxHORIZONTAL ); p->m_filament_icon = new ScalableButton(p->m_panel_filament_title, wxID_ANY, "filament"); - p->m_staticText_filament_settings = new wxStaticText( p->m_panel_filament_title, wxID_ANY, _L("Filament"), wxDefaultPosition, wxDefaultSize, 0 ); - p->m_staticText_filament_settings->Wrap( -1 ); - p->m_staticText_filament_settings->SetFont(Label::Body_14); - p->m_staticText_filament_settings->SetBackgroundColour(0xF1F1F1); + p->m_staticText_filament_settings = new Label(p->m_panel_filament_title, _L("Filament")); bSizer39->Add(p->m_filament_icon, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, FromDIP(10)); bSizer39->Add( p->m_staticText_filament_settings, 0, wxALIGN_CENTER ); bSizer39->Add(FromDIP(10), 0, 0, 0, 0); @@ -806,7 +800,7 @@ void Sidebar::init_filament_combo(PlaterPresetComboBox **combo, const int filame auto side = filament_idx % 2; auto /***/sizer_filaments = this->p->sizer_filaments->GetItem(side)->GetSizer(); if (side == 1 && filament_idx > 1) sizer_filaments->Remove(filament_idx / 2); - sizer_filaments->Add(combo_and_btn_sizer, 1, wxALIGN_CENTER | wxEXPAND); + sizer_filaments->Add(combo_and_btn_sizer, 1, wxEXPAND); if (side == 0) { sizer_filaments = this->p->sizer_filaments->GetItem(1)->GetSizer(); sizer_filaments->AddStretchSpacer(1); @@ -1808,7 +1802,6 @@ struct Plater::priv // Sets m_bed.m_polygon to limit the object placement. //BBS: add bed exclude area void set_bed_shape(const Pointfs& shape, const Pointfs& exclude_areas, const double printable_height, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false); - bool can_add_timelapse_wt() const; bool can_delete() const; bool can_delete_all() const; @@ -3720,8 +3713,10 @@ void Plater::priv::process_validation_warning(StringObjectException const &warni auto action_fn = (mo || !warning.opt_key.empty()) ? [id = mo ? mo->id() : 0, opt = warning.opt_key](wxEvtHandler *) { auto & objects = wxGetApp().model().objects; auto iter = id.id ? std::find_if(objects.begin(), objects.end(), [id](auto o) { return o->id() == id; }) : objects.end(); - if (iter != objects.end()) + if (iter != objects.end()) { + wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor); wxGetApp().obj_list()->select_items({{*iter, nullptr}}); + } if (!opt.empty()) { if (iter != objects.end()) wxGetApp().params_panel()->switch_to_object(); @@ -4802,7 +4797,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) if (preset_type == Preset::TYPE_FILAMENT) { wxGetApp().preset_bundle->set_filament_preset(idx, preset_name); - //wxGetApp().get_tab(preset_type)->select_preset(preset_name); + wxGetApp().plater()->update_project_dirty_from_presets(); } bool select_preset = !combo->selection_is_changed_according_to_physical_printers(); @@ -5991,16 +5986,6 @@ void Plater::priv::set_bed_shape(const Pointfs& shape, const Pointfs& exclude_ar } } -bool Plater::priv::can_add_timelapse_wt() const { - const DynamicPrintConfig &dconfig = wxGetApp().preset_bundle->prints.get_edited_preset().config; - const ConfigOption* option = dconfig.option("timelapse_no_toolhead"); - bool timelapse_enabled = option?option->getBool():false; - - PartPlate* curr_plate = q->get_partplate_list().get_curr_plate(); - - return timelapse_enabled && curr_plate->can_add_timelapse_object(); -} - bool Plater::priv::can_delete() const { return !get_selection().is_empty() && !get_selection().is_wipe_tower(); @@ -7031,7 +7016,7 @@ ProjectDropDialog::ProjectDropDialog(const std::string &filename) m_confirm->SetTextColor(wxColour(255, 255, 255)); m_confirm->SetSize(PROJECT_DROP_DIALOG_BUTTON_SIZE); m_confirm->SetMinSize(PROJECT_DROP_DIALOG_BUTTON_SIZE); - m_confirm->SetCornerRadius(12); + m_confirm->SetCornerRadius(FromDIP(12)); m_confirm->Bind(wxEVT_LEFT_DOWN, &ProjectDropDialog::on_select_ok, this); m_sizer_right->Add(m_confirm, 0, wxALL, 5); @@ -7039,7 +7024,7 @@ ProjectDropDialog::ProjectDropDialog(const std::string &filename) m_cancel->SetTextColor(wxColour(107, 107, 107)); m_cancel->SetSize(PROJECT_DROP_DIALOG_BUTTON_SIZE); m_cancel->SetMinSize(PROJECT_DROP_DIALOG_BUTTON_SIZE); - m_cancel->SetCornerRadius(12); + m_cancel->SetCornerRadius(FromDIP(12)); m_cancel->Bind(wxEVT_LEFT_DOWN, &ProjectDropDialog::on_select_cancel, this); m_sizer_right->Add(m_cancel, 0, wxALL, 5); @@ -7272,7 +7257,7 @@ bool Plater::load_files(const wxArrayString& filenames) } } - Plater::TakeSnapshot snapshot(this, snapshot_label); + //Plater::TakeSnapshot snapshot(this, snapshot_label); //load_files(normal_paths, LoadStrategy::LoadModel); // BBS: check file types @@ -7305,10 +7290,11 @@ bool Plater::load_files(const wxArrayString& filenames) open_3mf_file(normal_paths[0]); break; - case LoadFilesType::SingleOther: + case LoadFilesType::SingleOther: { + Plater::TakeSnapshot snapshot(this, snapshot_label); if (load_files(normal_paths, LoadStrategy::LoadModel, false).empty()) { res = false; } break; - + } case LoadFilesType::Multiple3MF: first_file = std::vector{normal_paths[0]}; for (auto i = 0; i < normal_paths.size(); i++) { @@ -7319,9 +7305,11 @@ bool Plater::load_files(const wxArrayString& filenames) if (load_files(other_file, LoadStrategy::LoadModel).empty()) { res = false; } break; - case LoadFilesType::MultipleOther: - if (load_files(normal_paths, LoadStrategy::LoadModel, true).empty()) { res = false; } + case LoadFilesType::MultipleOther: { + Plater::TakeSnapshot snapshot(this, snapshot_label); + if (load_files(normal_paths, LoadStrategy::LoadModel, true).empty()) { res = false; } break; + } case LoadFilesType::Multiple3MFOther: for (const auto &path : normal_paths) { @@ -7374,15 +7362,8 @@ bool Plater::open_3mf_file(const fs::path &file_path) if (load_type == LoadType::Unknown) return false; - struct AllowSnapshots { - AllowSnapshots(Plater *plater) : m_plater(plater) { m_plater->allow_snapshots(); } - ~AllowSnapshots() { m_plater->suppress_snapshots(); } - Plater *m_plater; - }; switch (load_type) { case LoadType::OpenProject: { - // remove snapshot taken by load_files and add_file - AllowSnapshots as(this); if (wxGetApp().can_load_project()) load_project(from_path(file_path)); break; @@ -7433,8 +7414,6 @@ void Plater::add_file() snapshot_label += encode_path(paths[i].filename().string().c_str()); } - Plater::TakeSnapshot snapshot(this, snapshot_label); - // BBS: check file types auto loadfiles_type = LoadFilesType::NoFile; auto amf_files_count = get_3mf_file_count(paths); @@ -7455,10 +7434,11 @@ void Plater::add_file() open_3mf_file(paths[0]); break; - case LoadFilesType::SingleOther: + case LoadFilesType::SingleOther: { + Plater::TakeSnapshot snapshot(this, snapshot_label); if (!load_files(paths, LoadStrategy::LoadModel, false).empty()) { wxGetApp().mainframe->update_title(); } break; - + } case LoadFilesType::Multiple3MF: first_file = std::vector{paths[0]}; for (auto i = 0; i < paths.size(); i++) { @@ -7469,12 +7449,13 @@ void Plater::add_file() if (!load_files(other_file, LoadStrategy::LoadModel).empty()) { wxGetApp().mainframe->update_title(); } break; - case LoadFilesType::MultipleOther: + case LoadFilesType::MultipleOther: { + Plater::TakeSnapshot snapshot(this, snapshot_label); if (!load_files(paths, LoadStrategy::LoadModel, true).empty()) { wxGetApp().mainframe->update_title(); } break; - + } case LoadFilesType::Multiple3MFOther: for (const auto &path : paths) { if (wxString(encode_path(path.filename().string().c_str())).EndsWith("3mf")) { @@ -9947,7 +9928,6 @@ void Plater::show_status_message(std::string s) BOOST_LOG_TRIVIAL(trace) << "show_status_message:" << s; } -bool Plater::can_add_timelapse_wt() const { return p->can_add_timelapse_wt(); } // BBS bool Plater::can_delete() const { return p->can_delete(); } bool Plater::can_delete_all() const { return p->can_delete_all(); } bool Plater::can_add_model() const { return !is_background_process_slicing(); } @@ -9963,6 +9943,8 @@ bool Plater::can_split_to_volumes() const { return p->can_split_to_volumes(); } bool Plater::can_arrange() const { return p->can_arrange(); } bool Plater::can_paste_from_clipboard() const { + if (!IsShown() || !p->is_view3D_shown()) return false; + const Selection& selection = p->view3D->get_canvas3d()->get_selection(); const Selection::Clipboard& clipboard = selection.get_clipboard(); @@ -9992,6 +9974,9 @@ bool Plater::can_cut_to_clipboard() const bool Plater::can_copy_to_clipboard() const { + if (!IsShown() || !p->is_view3D_shown()) + return false; + if (is_selection_empty()) return false; @@ -10002,8 +9987,8 @@ bool Plater::can_copy_to_clipboard() const return true; } -bool Plater::can_undo() const { return p->undo_redo_stack().has_undo_snapshot(); } -bool Plater::can_redo() const { return p->undo_redo_stack().has_redo_snapshot(); } +bool Plater::can_undo() const { return IsShown() && p->is_view3D_shown() && p->undo_redo_stack().has_undo_snapshot(); } +bool Plater::can_redo() const { return IsShown() && p->is_view3D_shown() && p->undo_redo_stack().has_redo_snapshot(); } bool Plater::can_reload_from_disk() const { return p->can_reload_from_disk(); } //BBS bool Plater::can_fillcolor() const { return p->can_fillcolor(); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 98741587c9..78b8f535bf 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -418,7 +418,6 @@ public: //BBS: void fill_color(int extruder_id); - bool can_add_timelapse_wt() const; bool can_delete() const; bool can_delete_all() const; diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 87647ec76f..0e290030e0 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -284,7 +284,6 @@ wxBoxSizer *PreferencesDialog::create_item_loglevel_combobox(wxString title, wxW std::vector::iterator iter; for (iter = vlist.begin(); iter != vlist.end(); iter++) { combobox->Append(*iter); } - m_sizer_combox->Add(combobox, 0, wxALIGN_CENTER, 0); auto severity_level = app_config->get("severity_level"); if (!severity_level.empty()) { combobox->SetValue(severity_level); } diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.cpp b/src/slic3r/GUI/ProjectDirtyStateManager.cpp index 1f228f4460..0db5e6f2df 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.cpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.cpp @@ -30,7 +30,11 @@ void ProjectDirtyStateManager::update_from_presets() if (!app.plater()->get_project_filename().IsEmpty()) { for (const auto &[type, name] : app.get_selected_presets()) { if (type == Preset::Type::TYPE_FILAMENT) { - m_presets_dirty |= m_initial_filament_presets != wxGetApp().preset_bundle->filament_presets; + m_presets_dirty |= m_initial_filament_presets_names != wxGetApp().preset_bundle->filament_presets; + if (ConfigOption *color_option = wxGetApp().preset_bundle->project_config.option("filament_colour")) { + auto colors = static_cast(color_option->clone()); + m_presets_dirty |= m_initial_filament_presets_colors != colors->values; + } } else { m_presets_dirty |= !m_initial_presets[type].empty() && m_initial_presets[type] != name; } @@ -57,7 +61,11 @@ void ProjectDirtyStateManager::reset_initial_presets() GUI_App &app = wxGetApp(); for (const auto &[type, name] : app.get_selected_presets()) { if (type == Preset::Type::TYPE_FILAMENT) { - m_initial_filament_presets = wxGetApp().preset_bundle->filament_presets; + m_initial_filament_presets_names = wxGetApp().preset_bundle->filament_presets; + if (ConfigOption *color_option = wxGetApp().preset_bundle->project_config.option("filament_colour")) { + auto colors = static_cast(color_option->clone()); + m_initial_filament_presets_colors = colors->values; + } } else { m_initial_presets[type] = name; } diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.hpp b/src/slic3r/GUI/ProjectDirtyStateManager.hpp index 203c870760..f17772199f 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.hpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.hpp @@ -33,7 +33,8 @@ private: DynamicPrintConfig m_initial_project_config; // filament preset independent of the m_initial_presets - std::vector m_initial_filament_presets; + std::vector m_initial_filament_presets_names; // all filament preset type name + std::vector m_initial_filament_presets_colors; // all filament preset color }; } // namespace GUI diff --git a/src/slic3r/GUI/ReleaseNote.cpp b/src/slic3r/GUI/ReleaseNote.cpp index e363323bda..9115182cb5 100644 --- a/src/slic3r/GUI/ReleaseNote.cpp +++ b/src/slic3r/GUI/ReleaseNote.cpp @@ -79,9 +79,9 @@ void ReleaseNoteDialog::on_dpi_changed(const wxRect &suggested_rect) { } -void ReleaseNoteDialog::update_release_note(std::string release_note, std::string version) +void ReleaseNoteDialog::update_release_note(wxString release_note, std::string version) { - m_text_up_info->SetLabel(wxString::Format("version %s update information :", version)); + m_text_up_info->SetLabel(wxString::Format(_L("version %s update information :"), version)); wxBoxSizer * sizer_text_release_note = new wxBoxSizer(wxVERTICAL); auto m_staticText_release_note = new wxStaticText(m_scrollwindw_release_note, wxID_ANY, release_note, wxDefaultPosition, wxDefaultSize, 0); m_staticText_release_note->Wrap(FromDIP(530)); @@ -147,6 +147,7 @@ UpdateVersionDialog::UpdateVersionDialog(wxWindow *parent) m_butto_ok->SetFont(Label::Body_12); m_butto_ok->SetSize(wxSize(FromDIP(58), FromDIP(24))); m_butto_ok->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_butto_ok->SetCornerRadius(FromDIP(12)); m_butto_ok->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) { EndModal(wxID_YES); @@ -158,6 +159,7 @@ UpdateVersionDialog::UpdateVersionDialog(wxWindow *parent) m_button_cancel->SetFont(Label::Body_12); m_button_cancel->SetSize(wxSize(FromDIP(58), FromDIP(24))); m_button_cancel->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_cancel->SetCornerRadius(FromDIP(12)); m_button_cancel->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) { EndModal(wxID_NO); @@ -188,7 +190,7 @@ void UpdateVersionDialog::on_dpi_changed(const wxRect &suggested_rect) { void UpdateVersionDialog::update_version_info(wxString release_note, wxString version) { - m_text_up_info->SetLabel(wxString::Format("Click to download new version in default browser: %s", version)); + m_text_up_info->SetLabel(wxString::Format(_L("Click to download new version in default browser: %s"), version)); wxBoxSizer *sizer_text_release_note = new wxBoxSizer(wxVERTICAL); auto m_staticText_release_note = new wxStaticText(m_scrollwindw_release_note, wxID_ANY, release_note, wxDefaultPosition, wxDefaultSize, 0); m_staticText_release_note->Wrap(FromDIP(530)); diff --git a/src/slic3r/GUI/ReleaseNote.hpp b/src/slic3r/GUI/ReleaseNote.hpp index 6dc53abbba..1e0945f812 100644 --- a/src/slic3r/GUI/ReleaseNote.hpp +++ b/src/slic3r/GUI/ReleaseNote.hpp @@ -43,7 +43,7 @@ public: ~ReleaseNoteDialog(); void on_dpi_changed(const wxRect &suggested_rect) override; - void update_release_note(std::string release_note, std::string version); + void update_release_note(wxString release_note, std::string version); wxStaticText * m_text_up_info{nullptr}; wxScrolledWindow *m_scrollwindw_release_note {nullptr}; diff --git a/src/slic3r/GUI/SavePresetDialog.cpp b/src/slic3r/GUI/SavePresetDialog.cpp index 3f5822c7ee..71d594a256 100644 --- a/src/slic3r/GUI/SavePresetDialog.cpp +++ b/src/slic3r/GUI/SavePresetDialog.cpp @@ -339,7 +339,7 @@ void SavePresetDialog::build(std::vector types, std::string suffix m_confirm->SetBorderColor(wxColour(0, 174, 66)); m_confirm->SetTextColor(wxColour(255, 255, 255)); m_confirm->SetMinSize(SAVE_PRESET_DIALOG_BUTTON_SIZE); - m_confirm->SetCornerRadius(12); + m_confirm->SetCornerRadius(FromDIP(12)); m_confirm->Bind(wxEVT_BUTTON, &SavePresetDialog::accept, this); btns->Add(m_confirm, 0, wxEXPAND, 0); @@ -350,7 +350,7 @@ void SavePresetDialog::build(std::vector types, std::string suffix m_cancel = new Button(this, _L("Cancel")); m_cancel->SetMinSize(SAVE_PRESET_DIALOG_BUTTON_SIZE); m_cancel->SetTextColor(wxColour(107, 107, 107)); - m_cancel->SetCornerRadius(12); + m_cancel->SetCornerRadius(FromDIP(12)); m_cancel->Bind(wxEVT_BUTTON, &SavePresetDialog::on_select_cancel, this); btns->Add(m_cancel, 0, wxEXPAND, 0); diff --git a/src/slic3r/GUI/SelectMachine.cpp b/src/slic3r/GUI/SelectMachine.cpp index 7d3b6b04d3..62481fb110 100644 --- a/src/slic3r/GUI/SelectMachine.cpp +++ b/src/slic3r/GUI/SelectMachine.cpp @@ -194,7 +194,7 @@ void MachineObjectPanel::doRender(wxDC &dc) dc.SetFont(Label::Body_13); dc.SetBackgroundMode(wxTRANSPARENT); dc.SetTextForeground(SELECT_MACHINE_GREY900); - wxString dev_name; + wxString dev_name = ""; if (m_info) { dev_name = from_u8(m_info->dev_name); } @@ -367,6 +367,8 @@ SelectMachinePopup::SelectMachinePopup(wxWindow *parent) Bind(EVT_DISSMISS_MACHINE_LIST, &SelectMachinePopup::on_dissmiss_win, this); } +SelectMachinePopup::~SelectMachinePopup() { delete m_refresh_timer; } + void SelectMachinePopup::Popup(wxWindow *WXUNUSED(focus)) { BOOST_LOG_TRIVIAL(trace) << "get_print_info: start"; @@ -537,6 +539,7 @@ void SelectMachinePopup::update_other_devices() } for (int j = i; j < m_other_list_machine_panel.size(); j++) { + m_other_list_machine_panel[j]->mPanel->update_machine_info(nullptr); m_other_list_machine_panel[j]->mPanel->Hide(); } m_sizer_other_devices->Layout(); @@ -653,6 +656,7 @@ void SelectMachinePopup::update_user_devices() } for (int j = i; j < m_user_list_machine_panel.size(); j++) { + m_user_list_machine_panel[j]->mPanel->update_machine_info(nullptr); m_user_list_machine_panel[j]->mPanel->Hide(); } //m_sizer_my_devices->Layout(); @@ -897,7 +901,7 @@ SelectMachineDialog::SelectMachineDialog(Plater *plater) m_button_ensure->SetTextColor(*wxWHITE); m_button_ensure->SetSize(SELECT_MACHINE_DIALOG_BUTTON_SIZE); m_button_ensure->SetMinSize(SELECT_MACHINE_DIALOG_BUTTON_SIZE); - m_button_ensure->SetCornerRadius(FromDIP(10)); + m_button_ensure->SetCornerRadius(FromDIP(12)); m_button_ensure->Bind(wxEVT_BUTTON, &SelectMachineDialog::on_ok, this); m_sizer_pcont->Add(m_button_ensure, 0, wxEXPAND | wxBOTTOM, FromDIP(10)); @@ -1954,9 +1958,9 @@ void SelectMachineDialog::Enable_Send_Button(bool en) void SelectMachineDialog::on_dpi_changed(const wxRect &suggested_rect) { m_button_refresh->SetMinSize(SELECT_MACHINE_DIALOG_BUTTON_SIZE); - m_button_refresh->SetCornerRadius(FromDIP(10)); + m_button_refresh->SetCornerRadius(FromDIP(12)); m_button_ensure->SetMinSize(SELECT_MACHINE_DIALOG_BUTTON_SIZE); - m_button_ensure->SetCornerRadius(FromDIP(10)); + m_button_ensure->SetCornerRadius(FromDIP(12)); m_status_bar->msw_rescale(); Fit(); Refresh(); @@ -2178,7 +2182,7 @@ bool SelectMachineDialog::Show(bool show) SelectMachineDialog::~SelectMachineDialog() { - ; + delete m_refresh_timer; } EditDevNameDialog::EditDevNameDialog(Plater *plater /*= nullptr*/) @@ -2212,7 +2216,7 @@ EditDevNameDialog::EditDevNameDialog(Plater *plater /*= nullptr*/) m_button_confirm->SetTextColor(wxColour(255, 255, 255)); m_button_confirm->SetSize(wxSize(FromDIP(72), FromDIP(24))); m_button_confirm->SetMinSize(wxSize(FromDIP(72), FromDIP(24))); - m_button_confirm->SetCornerRadius(12); + m_button_confirm->SetCornerRadius(FromDIP(12)); m_button_confirm->Bind(wxEVT_BUTTON, &EditDevNameDialog::on_edit_name, this); m_sizer_main->Add(m_button_confirm, 0, wxALIGN_CENTER_HORIZONTAL | wxTOP, FromDIP(10)); diff --git a/src/slic3r/GUI/SelectMachine.hpp b/src/slic3r/GUI/SelectMachine.hpp index 7143f188df..0c8f24d730 100644 --- a/src/slic3r/GUI/SelectMachine.hpp +++ b/src/slic3r/GUI/SelectMachine.hpp @@ -181,7 +181,7 @@ class SelectMachinePopup : public wxPopupTransientWindow { public: SelectMachinePopup(wxWindow *parent); - ~SelectMachinePopup() {} + ~SelectMachinePopup(); // wxPopupTransientWindow virtual methods are all overridden to log them virtual void Popup(wxWindow *focus = NULL) wxOVERRIDE; @@ -359,7 +359,7 @@ protected: void reset_ams_material(); void update_show_status(); - wxTimer *m_refresh_timer; + wxTimer *m_refresh_timer { nullptr }; std::shared_ptr m_print_job; diff --git a/src/slic3r/GUI/StatusPanel.cpp b/src/slic3r/GUI/StatusPanel.cpp index a36349fede..e93e2d7b5b 100644 --- a/src/slic3r/GUI/StatusPanel.cpp +++ b/src/slic3r/GUI/StatusPanel.cpp @@ -249,8 +249,8 @@ wxBoxSizer *StatusBasePanel::create_monitoring_page() m_media_play_ctrl = new MediaPlayCtrl(this, m_media_ctrl, wxDefaultPosition, wxSize(-1, FromDIP(40))); - sizer->Add(m_media_ctrl, 1, wxEXPAND | wxALIGN_CENTER_HORIZONTAL | wxALL, 0); - sizer->Add(m_media_play_ctrl, 0, wxEXPAND | wxALIGN_CENTER_HORIZONTAL | wxALL, 0); + sizer->Add(m_media_ctrl, 1, wxEXPAND | wxALL, 0); + sizer->Add(m_media_play_ctrl, 0, wxEXPAND | wxALL, 0); // media_ctrl_panel->SetSizer(bSizer_monitoring); // media_ctrl_panel->Layout(); // @@ -306,7 +306,7 @@ wxBoxSizer *StatusBasePanel::create_project_task_page(wxWindow *parent) bSizer_task_name->Add(m_staticText_subtask_value, 1, wxALL | wxEXPAND, 0); bSizer_task_name->Add(m_printing_stage_value, 1, wxALL | wxEXPAND, 0); - bSizer_subtask_info->Add(bSizer_task_name, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND, FromDIP(5)); + bSizer_subtask_info->Add(bSizer_task_name, 1, wxEXPAND, FromDIP(5)); /* wxFlexGridSizer *fgSizer_task = new wxFlexGridSizer(2, 2, 0, 0); fgSizer_task->AddGrowableCol(0); @@ -330,7 +330,7 @@ wxBoxSizer *StatusBasePanel::create_project_task_page(wxWindow *parent) //m_panel_progress->SetMinSize(wxSize(FromDIP(574), -1)); //m_panel_progress->SetMaxSize(wxSize(FromDIP(600), -1)); - m_sizer_progressbar->Add(m_gauge_progress, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND, 0); + m_sizer_progressbar->Add(m_gauge_progress, 1, wxALIGN_CENTER_VERTICAL, 0); //fgSizer_task->Add(m_panel_progress, 0, wxALIGN_CENTER_VERTICAL | wxEXPAND, 0); wxBoxSizer *bSizer_task_btn = new wxBoxSizer(wxHORIZONTAL); @@ -564,10 +564,11 @@ wxBoxSizer *StatusBasePanel::create_temp_control(wxWindow *parent) wxWindowID nozzle_id = wxWindow::NewControlId(); m_tempCtrl_nozzle = new TempInput(parent, nozzle_id, TEMP_BLANK_STR, TEMP_BLANK_STR, wxString("monitor_nozzle_temp"), wxString("monitor_nozzle_temp_active"), - wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER | wxBORDER_NONE); + wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER); m_tempCtrl_nozzle->SetMinSize(TEMP_CTRL_MIN_SIZE); m_tempCtrl_nozzle->SetMinTemp(nozzle_temp_range[0]); m_tempCtrl_nozzle->SetMaxTemp(nozzle_temp_range[1]); + m_tempCtrl_nozzle->SetBorderWidth(FromDIP(2)); m_tempCtrl_nozzle->SetTextColor(StateColor(std::make_pair(DISCONNECT_TEXT_COL, (int) StateColor::Disabled), std::make_pair(NORMAL_TEXT_COL, (int) StateColor::Normal))); m_tempCtrl_nozzle->SetBorderColor(StateColor(std::make_pair(*wxWHITE, (int) StateColor::Disabled), std::make_pair(BUTTON_HOVER_COL, (int) StateColor::Focused), std::make_pair(BUTTON_HOVER_COL, (int) StateColor::Hovered), std::make_pair(*wxWHITE, (int) StateColor::Normal))); @@ -581,10 +582,11 @@ wxBoxSizer *StatusBasePanel::create_temp_control(wxWindow *parent) wxWindowID bed_id = wxWindow::NewControlId(); m_tempCtrl_bed = new TempInput(parent, bed_id, TEMP_BLANK_STR, TEMP_BLANK_STR, wxString("monitor_bed_temp"), wxString("monitor_bed_temp_active"), wxDefaultPosition, - wxDefaultSize, wxALIGN_CENTER | wxBORDER_NONE); + wxDefaultSize, wxALIGN_CENTER); m_tempCtrl_bed->SetMinTemp(bed_temp_range[0]); m_tempCtrl_bed->SetMaxTemp(bed_temp_range[1]); m_tempCtrl_bed->SetMinSize(TEMP_CTRL_MIN_SIZE); + m_tempCtrl_bed->SetBorderWidth(FromDIP(2)); m_tempCtrl_bed->SetTextColor(StateColor(std::make_pair(DISCONNECT_TEXT_COL, (int) StateColor::Disabled), std::make_pair(NORMAL_TEXT_COL, (int) StateColor::Normal))); m_tempCtrl_bed->SetBorderColor(StateColor(std::make_pair(*wxWHITE, (int) StateColor::Disabled), std::make_pair(BUTTON_HOVER_COL, (int) StateColor::Focused), std::make_pair(BUTTON_HOVER_COL, (int) StateColor::Hovered), std::make_pair(*wxWHITE, (int) StateColor::Normal))); @@ -596,9 +598,10 @@ wxBoxSizer *StatusBasePanel::create_temp_control(wxWindow *parent) wxWindowID frame_id = wxWindow::NewControlId(); m_tempCtrl_frame = new TempInput(parent, frame_id, TEMP_BLANK_STR, TEMP_BLANK_STR, wxString("monitor_frame_temp"), wxString("monitor_frame_temp"), wxDefaultPosition, - wxDefaultSize, wxALIGN_CENTER | wxBORDER_NONE); + wxDefaultSize, wxALIGN_CENTER); m_tempCtrl_frame->SetReadOnly(true); m_tempCtrl_frame->SetMinSize(TEMP_CTRL_MIN_SIZE); + m_tempCtrl_frame->SetBorderWidth(FromDIP(2)); m_tempCtrl_frame->SetTextColor(StateColor(std::make_pair(DISCONNECT_TEXT_COL, (int) StateColor::Disabled), std::make_pair(NORMAL_TEXT_COL, (int) StateColor::Normal))); m_tempCtrl_frame->SetBorderColor(StateColor(std::make_pair(*wxWHITE, (int) StateColor::Disabled), std::make_pair(BUTTON_HOVER_COL, (int) StateColor::Focused), std::make_pair(BUTTON_HOVER_COL, (int) StateColor::Hovered), std::make_pair(*wxWHITE, (int) StateColor::Normal))); @@ -609,7 +612,7 @@ wxBoxSizer *StatusBasePanel::create_temp_control(wxWindow *parent) wxBoxSizer *m_misc_ctrl_sizer = create_misc_control(parent); - sizer->Add(m_misc_ctrl_sizer, 0, wxEXPAND | wxALIGN_CENTER_VERTICAL, 0); + sizer->Add(m_misc_ctrl_sizer, 0, wxEXPAND, 0); return sizer; } @@ -620,10 +623,11 @@ wxBoxSizer *StatusBasePanel::create_misc_control(wxWindow *parent) wxBoxSizer *line_sizer = new wxBoxSizer(wxHORIZONTAL); /* create speed control */ - m_switch_speed = new ImageSwitchButton(parent, m_bitmap_speed_active, m_bitmap_speed, wxBORDER_NONE); + m_switch_speed = new ImageSwitchButton(parent, m_bitmap_speed_active, m_bitmap_speed); m_switch_speed->SetLabels(_L("100%"), _L("100%")); m_switch_speed->SetMinSize(MISC_BUTTON_SIZE); m_switch_speed->SetPadding(FromDIP(3)); + m_switch_speed->SetBorderWidth(FromDIP(2)); m_switch_speed->SetFont(Label::Head_13); m_switch_speed->SetTextColor(StateColor(std::make_pair(DISCONNECT_TEXT_COL, (int) StateColor::Disabled), std::make_pair(NORMAL_TEXT_COL, (int) StateColor::Normal))); m_switch_speed->SetValue(false); @@ -635,10 +639,11 @@ wxBoxSizer *StatusBasePanel::create_misc_control(wxWindow *parent) line_sizer->Add(line, 0, wxEXPAND | wxTOP | wxBOTTOM, 4); /* create lamp control */ - m_switch_lamp = new ImageSwitchButton(parent, m_bitmap_lamp_on, m_bitmap_lamp_off, wxBORDER_NONE); + m_switch_lamp = new ImageSwitchButton(parent, m_bitmap_lamp_on, m_bitmap_lamp_off); m_switch_lamp->SetLabels(_L("Lamp"), _L("Lamp")); m_switch_lamp->SetMinSize(MISC_BUTTON_SIZE); m_switch_lamp->SetPadding(FromDIP(3)); + m_switch_lamp->SetBorderWidth(FromDIP(2)); m_switch_lamp->SetFont(Label::Head_13); m_switch_lamp->SetTextColor(StateColor(std::make_pair(DISCONNECT_TEXT_COL, (int) StateColor::Disabled), std::make_pair(NORMAL_TEXT_COL, (int) StateColor::Normal))); line_sizer->Add(m_switch_lamp, 1, wxALIGN_CENTER | wxALL, 0); @@ -649,11 +654,12 @@ wxBoxSizer *StatusBasePanel::create_misc_control(wxWindow *parent) sizer->Add(line, 0, wxEXPAND | wxLEFT | wxRIGHT, 12); line_sizer = new wxBoxSizer(wxHORIZONTAL); - m_switch_nozzle_fan = new ImageSwitchButton(parent, m_bitmap_fan_on, m_bitmap_fan_off, wxBORDER_NONE); + m_switch_nozzle_fan = new ImageSwitchButton(parent, m_bitmap_fan_on, m_bitmap_fan_off); m_switch_nozzle_fan->SetMinSize(MISC_BUTTON_SIZE); m_switch_nozzle_fan->SetValue(false); m_switch_nozzle_fan->SetLabels(_L("Part Cooling"), _L("Part Cooling")); m_switch_nozzle_fan->SetPadding(FromDIP(3)); + m_switch_nozzle_fan->SetBorderWidth(FromDIP(2)); m_switch_nozzle_fan->SetFont(SWITCH_FONT); m_switch_nozzle_fan->SetTextColor(StateColor(std::make_pair(DISCONNECT_TEXT_COL, (int) StateColor::Disabled), std::make_pair(NORMAL_FAN_TEXT_COL, (int) StateColor::Normal))); @@ -662,10 +668,11 @@ wxBoxSizer *StatusBasePanel::create_misc_control(wxWindow *parent) line->SetLineColour(STATIC_BOX_LINE_COL); line_sizer->Add(line, 0, wxEXPAND | wxTOP | wxBOTTOM, 4); - m_switch_printing_fan = new ImageSwitchButton(parent, m_bitmap_fan_on, m_bitmap_fan_off, wxBORDER_NONE); + m_switch_printing_fan = new ImageSwitchButton(parent, m_bitmap_fan_on, m_bitmap_fan_off); m_switch_printing_fan->SetValue(false); m_switch_printing_fan->SetMinSize(MISC_BUTTON_SIZE); m_switch_printing_fan->SetPadding(FromDIP(3)); + m_switch_printing_fan->SetBorderWidth(FromDIP(2)); m_switch_printing_fan->SetFont(SWITCH_FONT); m_switch_printing_fan->SetLabels(_L("Aux Cooling"), _L("Aux Cooling")); m_switch_printing_fan->SetTextColor( @@ -751,7 +758,7 @@ wxBoxSizer *StatusBasePanel::create_bed_control(wxWindow *parent) m_bpButton_z_10->SetMinSize(Z_BUTTON_SIZE); m_bpButton_z_10->SetCornerRadius(0); - bSizer_z_ctrl->Add(m_bpButton_z_10, 0, wxEXPAND | wxALL | wxALIGN_CENTER_HORIZONTAL, 0); + bSizer_z_ctrl->Add(m_bpButton_z_10, 0, wxEXPAND | wxALL, 0); m_bpButton_z_1 = new Button(panel, wxString(" 1"), "monitor_bed_up", 0, FromDIP(15)); m_bpButton_z_1->SetFont(::Label::Body_13); @@ -761,7 +768,7 @@ wxBoxSizer *StatusBasePanel::create_bed_control(wxWindow *parent) m_bpButton_z_1->SetMinSize(Z_BUTTON_SIZE); m_bpButton_z_1->SetTextColor(StateColor(std::make_pair(DISCONNECT_TEXT_COL, (int) StateColor::Disabled), std::make_pair(NORMAL_TEXT_COL, (int) StateColor::Normal))); - bSizer_z_ctrl->Add(m_bpButton_z_1, 0, wxEXPAND | wxALL | wxALIGN_CENTER_HORIZONTAL, 0); + bSizer_z_ctrl->Add(m_bpButton_z_1, 0, wxEXPAND | wxALL, 0); bSizer_z_ctrl->Add(0, FromDIP(6), 0, wxEXPAND, 0); @@ -773,7 +780,7 @@ wxBoxSizer *StatusBasePanel::create_bed_control(wxWindow *parent) m_bpButton_z_down_1->SetMinSize(Z_BUTTON_SIZE); m_bpButton_z_down_1->SetTextColor(StateColor(std::make_pair(DISCONNECT_TEXT_COL, (int) StateColor::Disabled), std::make_pair(NORMAL_TEXT_COL, (int) StateColor::Normal))); - bSizer_z_ctrl->Add(m_bpButton_z_down_1, 0, wxEXPAND | wxALL | wxALIGN_CENTER_HORIZONTAL, 0); + bSizer_z_ctrl->Add(m_bpButton_z_down_1, 0, wxEXPAND | wxALL, 0); m_bpButton_z_down_10 = new Button(panel, wxString("10"), "monitor_bed_down", 0, FromDIP(15)); m_bpButton_z_down_10->SetFont(::Label::Body_13); @@ -783,7 +790,7 @@ wxBoxSizer *StatusBasePanel::create_bed_control(wxWindow *parent) m_bpButton_z_down_10->SetMinSize(Z_BUTTON_SIZE); m_bpButton_z_down_10->SetTextColor(StateColor(std::make_pair(DISCONNECT_TEXT_COL, (int) StateColor::Disabled), std::make_pair(NORMAL_TEXT_COL, (int) StateColor::Normal))); - bSizer_z_ctrl->Add(m_bpButton_z_down_10, 0, wxEXPAND | wxALL | wxALIGN_CENTER_HORIZONTAL, 0); + bSizer_z_ctrl->Add(m_bpButton_z_down_10, 0, wxEXPAND | wxALL, 0); bSizer_z_ctrl->Add(0, FromDIP(16), 0, wxEXPAND, 0); @@ -850,7 +857,7 @@ wxBoxSizer *StatusBasePanel::create_extruder_control(wxWindow *parent) m_button_unload->SetTextColor(abort_text); m_button_unload->SetFont(Label::Body_10); m_button_unload->SetMinSize(wxSize(-1, FromDIP(24))); - m_button_unload->SetCornerRadius(FromDIP(10)); + m_button_unload->SetCornerRadius(FromDIP(12)); bSizer_e_ctrl->Add(0, 0, 1, wxEXPAND, 0); bSizer_e_ctrl->Add(m_button_unload, 0, wxALIGN_CENTER_HORIZONTAL| wxTOP|wxBOTTOM, FromDIP(5)); @@ -1012,7 +1019,6 @@ StatusPanel::StatusPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, co Bind(EVT_AMS_REFRESH_RFID, &StatusPanel::on_ams_refresh_rfid, this); Bind(EVT_AMS_ON_SELECTED, &StatusPanel::on_ams_selected, this); Bind(EVT_AMS_ON_FILAMENT_EDIT, &StatusPanel::on_filament_edit, this); - Bind(EVT_UPDATE_ERROR_MESSAGE, &StatusPanel::on_update_error_message, this); m_switch_speed->Connect(wxEVT_LEFT_DOWN, wxCommandEventHandler(StatusPanel::on_switch_speed), NULL, this); m_calibration_btn->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_start_calibration), NULL, this); @@ -1059,7 +1065,7 @@ void StatusPanel::init_scaled_buttons() m_button_clean->SetMinSize(wxSize(FromDIP(48), FromDIP(24))); m_button_clean->SetCornerRadius(FromDIP(12)); m_button_unload->SetMinSize(wxSize(-1, FromDIP(24))); - m_button_unload->SetCornerRadius(FromDIP(10)); + m_button_unload->SetCornerRadius(FromDIP(12)); m_bpButton_z_10->SetMinSize(Z_BUTTON_SIZE); m_bpButton_z_10->SetCornerRadius(0); m_bpButton_z_1->SetMinSize(Z_BUTTON_SIZE); @@ -1239,9 +1245,9 @@ void StatusPanel::update(MachineObject *obj) m_machine_ctrl_panel->Thaw(); } -void StatusPanel::on_update_error_message(wxCommandEvent &event) +void StatusPanel::show_error_message(wxString msg) { - m_error_text->SetLabel(event.GetString()); + m_error_text->SetLabel(msg); m_staticline->Show(); m_panel_error_txt->Show(); } @@ -1257,13 +1263,18 @@ void StatusPanel::update_error_message() } if (before_error_code != obj->print_error) { - get_error_message_thread = new boost::thread(Slic3r::create_thread([&] { - std::string message = show_error_message(obj->print_error); - wxCommandEvent event(EVT_UPDATE_ERROR_MESSAGE); - event.SetString(wxString(message)); - event.SetEventObject(this); - wxPostEvent(this, event); - })); + if (wxGetApp().get_hms_query()) { + char buf[32]; + ::sprintf(buf, "%08X", obj->print_error); + std::string print_error_str = std::string(buf); + if (print_error_str.size() > 4) { + print_error_str.insert(4, " "); + } + wxString error_msg = wxString::Format("%s[%s]", + wxGetApp().get_hms_query()->query_print_error_msg(obj->print_error), + print_error_str); + show_error_message(error_msg); + } before_error_code = obj->print_error; } } diff --git a/src/slic3r/GUI/StatusPanel.hpp b/src/slic3r/GUI/StatusPanel.hpp index 128f3cf45d..202902d9fb 100644 --- a/src/slic3r/GUI/StatusPanel.hpp +++ b/src/slic3r/GUI/StatusPanel.hpp @@ -27,7 +27,7 @@ #include "Widgets/ProgressBar.hpp" #include "Widgets/ImageSwitchButton.hpp" #include "Widgets/AMSControl.hpp" -#include "UpdateErrorMessage.hpp" +#include "HMS.hpp" #include "Widgets/wxStaticText2.hpp" class StepIndicator; @@ -268,7 +268,7 @@ protected: void on_subtask_pause_resume(wxCommandEvent &event); void on_subtask_abort(wxCommandEvent &event); void on_subtask_clean(wxCommandEvent &event); - void on_update_error_message(wxCommandEvent &event); + void show_error_message(wxString msg); void error_info_reset(); /* axis control */ @@ -352,7 +352,6 @@ public: long last_ams_version { -1 }; std::vector last_stage_list_info; - boost::thread * get_error_message_thread{nullptr}; bool is_stage_list_info_changed(MachineObject* obj); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index b6632ab329..509768743c 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -267,7 +267,7 @@ void Tab::create_preset_tab() m_search_input->SetFont(wxGetApp().bold_font()); search_sizer->Add(new wxWindow(m_search_item, wxID_ANY, wxDefaultPosition, wxSize(0, 0)), 0, wxEXPAND | wxLEFT, 16); - search_sizer->Add(m_search_input, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALL, wxGetApp().em_unit() / 2); + search_sizer->Add(m_search_input, 1, wxEXPAND | wxALL, wxGetApp().em_unit() / 2); search_sizer->Add(new wxWindow(m_search_input, wxID_ANY, wxDefaultPosition, wxSize(0, 0)), 0, wxEXPAND | wxLEFT, 16); @@ -335,7 +335,7 @@ void Tab::create_preset_tab() // BBS: model config if (m_presets_choice) { m_presets_choice->Reparent(m_top_panel); - m_top_sizer->Add(m_presets_choice, 1, wxLEFT | wxRIGHT | wxEXPAND | wxALIGN_CENTER_VERTICAL, 10); + m_top_sizer->Add(m_presets_choice, 1, wxLEFT | wxRIGHT | wxEXPAND, 10); } else { m_top_sizer->AddSpacer(10); m_top_sizer->AddStretchSpacer(1); @@ -353,7 +353,7 @@ void Tab::create_preset_tab() m_top_sizer->Add( m_search_item, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT , 8 ); if (dynamic_cast(this) == nullptr) { - m_static_title = new Label(Label::Body_12, _L("Advance"), m_top_panel); + m_static_title = new Label(m_top_panel, Label::Body_12, _L("Advance")); m_static_title->Wrap( -1 ); // BBS: open this tab by select first m_static_title->Bind(wxEVT_LEFT_UP, [this](auto& e) { @@ -1375,11 +1375,9 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) if (opt_key == "enable_prime_tower" || opt_key == "single_extruder_multi_material" || opt_key == "extruders_count" ) update_wiping_button_visibility(); - //popup message dialog when first selected - if (opt_key == "timelapse_no_toolhead" && boost::any_cast(value)) - show_timelapse_warning_dialog(); - - + // reload scene to update timelapse wipe tower + if (opt_key == "timelapse_no_toolhead") + wxGetApp().plater()->update(); // BBS #if 0 diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index a54810e7c1..04382ec14d 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -953,7 +953,7 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection *dependent_ } (*btn)->SetMinSize(UNSAVE_CHANGE_DIALOG_BUTTON_SIZE); - (*btn)->SetCornerRadius(12); + (*btn)->SetCornerRadius(FromDIP(12)); (*btn)->Bind(wxEVT_BUTTON, [this, close_act, dependent_presets](wxEvent &) { bool save_names_and_types = close_act == Action::Save || (close_act == Action::Transfer && ActionButtons::KEEP & m_buttons); diff --git a/src/slic3r/GUI/UpdateErrorMessage.cpp b/src/slic3r/GUI/UpdateErrorMessage.cpp deleted file mode 100644 index aa4a785686..0000000000 --- a/src/slic3r/GUI/UpdateErrorMessage.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include "UpdateErrorMessage.hpp" - - - -namespace Slic3r { -namespace GUI { - -wxDEFINE_EVENT(EVT_UPDATE_ERROR_MESSAGE, wxCommandEvent); - -std::string show_error_message(int error_code) -{ - char buf[64]; - std::string result_str = ""; - std::sprintf(buf,"%08X",error_code); - std::string hms_host = wxGetApp().app_config->get_hms_host(); - std::string get_lang = wxGetApp().app_config->get_langauge_code(); - - std::string url = (boost::format("https://%1%/query.php?lang=%2%&e=%3%") - %hms_host - %get_lang - %buf).str(); - - Slic3r::Http http = Slic3r::Http::get(url); - http.header("accept", "application/json") - .timeout_max(10) - .on_complete([get_lang, &result_str](std::string body, unsigned status) { - try { - json j = json::parse(body); - if (j.contains("result")) { - if (j["result"].get() == 0) { - if (j.contains("data")) { - json jj = j["data"]; - if (jj.contains("device_error")) { - if (jj["device_error"].contains(get_lang)) { - if (jj["device_error"][get_lang].size() > 0) { - if (!jj["device_error"][get_lang][0]["intro"].empty() || !jj["device_error"][get_lang][0]["ecode"].empty()) { - std::string error_info = jj["device_error"][get_lang][0]["intro"].get(); - std::string error_code = jj["device_error"][get_lang][0]["ecode"].get(); - error_code.insert(4, " "); - result_str = from_u8(error_info).ToStdString() + "[" + error_code + "]"; - } - } - } - } - } - } - } - } catch (...) { - ; - } - }) - .on_error([](std::string body, std::string error, unsigned status) { - BOOST_LOG_TRIVIAL(trace) << boost::format("[BBL ErrorMessage]: status=%1%, error=%2%, body=%3%") % status % error % body; - }).perform_sync(); - - return result_str; -} - -} -} \ No newline at end of file diff --git a/src/slic3r/GUI/UpdateErrorMessage.hpp b/src/slic3r/GUI/UpdateErrorMessage.hpp deleted file mode 100644 index 3b19f576cd..0000000000 --- a/src/slic3r/GUI/UpdateErrorMessage.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef slic3r_UpdateErrorMessage_hpp_ -#define slic3r_UpdateErrorMessage_hpp_ - -#include "GUI_App.hpp" -#include "GUI.hpp" -#include "I18N.hpp" -#include "Widgets/Label.hpp" -#include "Widgets/Button.hpp" -#include "Widgets/StepCtrl.hpp" -#include "BitmapCache.hpp" -#include "slic3r/Utils/Http.hpp" -#include "libslic3r/Thread.hpp" - -namespace Slic3r { -namespace GUI { - - -std::string show_error_message(int error_code); - - -wxDECLARE_EVENT(EVT_UPDATE_ERROR_MESSAGE, wxCommandEvent); -} -} - - -#endif \ No newline at end of file diff --git a/src/slic3r/GUI/UpgradePanel.cpp b/src/slic3r/GUI/UpgradePanel.cpp index 6cebb7bc2b..7d19dd934e 100644 --- a/src/slic3r/GUI/UpgradePanel.cpp +++ b/src/slic3r/GUI/UpgradePanel.cpp @@ -76,7 +76,7 @@ MachineInfoPanel::MachineInfoPanel(wxWindow* parent, wxWindowID id, const wxPoin m_staticText_ver = new wxStaticText(this, wxID_ANY, _L("Version:"), wxDefaultPosition, wxDefaultSize, 0); m_staticText_ver->Wrap(-1); m_staticText_ver->SetFont(Label::Head_14); - m_ota_ver_sizer->Add(m_staticText_ver, 0, wxALIGN_RIGHT | wxALL, FromDIP(5)); + m_ota_ver_sizer->Add(m_staticText_ver, 0, wxALL, FromDIP(5)); m_ota_info_sizer->Add(m_ota_ver_sizer, 0, wxEXPAND, 0); @@ -519,18 +519,18 @@ void MachineInfoPanel::on_show_release_note(wxMouseEvent &event) if (!dev) return; - std::string next_version_release_note = ""; - std::string now_version_release_note = ""; + wxString next_version_release_note; + wxString now_version_release_note; std::string version_number = ""; for (auto iter : m_obj->firmware_list) { if (iter.version == m_obj->ota_new_version_number) { version_number = m_obj->ota_new_version_number; - next_version_release_note = iter.description; + next_version_release_note = wxString::FromUTF8(iter.description); } if (iter.version == m_obj->get_ota_version()) { version_number = m_obj->get_ota_version(); - now_version_release_note = iter.description; + now_version_release_note = wxString::FromUTF8(iter.description); } } @@ -563,7 +563,7 @@ UpgradePanel::UpgradePanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, m_scrolledWindow->SetSizerAndFit(m_machine_list_sizer); - m_main_sizer->Add(m_scrolledWindow, 1, wxALIGN_CENTER_HORIZONTAL | wxEXPAND, 0); + m_main_sizer->Add(m_scrolledWindow, 1, wxEXPAND, 0); this->SetSizerAndFit(m_main_sizer); @@ -679,7 +679,7 @@ bool UpgradePanel::Show(bool show) auto m_staticText_ams_ver = new wxStaticText(this, wxID_ANY, _L("Version:"), wxDefaultPosition, wxDefaultSize, 0); m_staticText_ams_ver->Wrap(-1); m_staticText_ams_ver->SetFont(Label::Head_14); - m_ams_ver_sizer->Add(m_staticText_ams_ver, 0, wxALIGN_RIGHT | wxALL, FromDIP(5)); + m_ams_ver_sizer->Add(m_staticText_ams_ver, 0, wxALL, FromDIP(5)); m_staticText_ams_ver_val = new wxStaticText(this, wxID_ANY, "-", wxDefaultPosition, wxDefaultSize, 0); m_staticText_ams_ver_val->Wrap(-1); diff --git a/src/slic3r/GUI/WebGuideDialog.cpp b/src/slic3r/GUI/WebGuideDialog.cpp index 0f9f55a8f1..6d3afca61b 100644 --- a/src/slic3r/GUI/WebGuideDialog.cpp +++ b/src/slic3r/GUI/WebGuideDialog.cpp @@ -854,7 +854,7 @@ int GuideFrame::LoadProfile() //BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", will load config from %1%.") % bbl_bundle_path; m_ProfileJson = json::parse("{}"); m_ProfileJson["model"] = json::array(); - m_ProfileJson["machine"] = json::array(); + m_ProfileJson["machine"] = json::object(); m_ProfileJson["filament"] = json::object(); m_ProfileJson["process"] = json::array(); @@ -999,27 +999,205 @@ void StringReplace(string &strBase, string strSrc, string strDes) } +//int GuideFrame::LoadProfileFamily(std::string strVendor, std::string strFilePath) +//{ +// //wxString strFolder = strFilePath.BeforeLast(boost::filesystem::path::preferred_separator); +// boost::filesystem::path file_path(strFilePath); +// boost::filesystem::path vendor_dir = boost::filesystem::absolute(file_path.parent_path()/ strVendor).make_preferred(); +// BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", vendor path %1%.")% vendor_dir.string(); +// try { +// +// //wxLogMessage("GUIDE: json_path1 %s", w2s(strFilePath)); +// +// std::string contents; +// LoadFile(strFilePath, contents); +// //wxLogMessage("GUIDE: json_path1 content: %s", contents); +// json jLocal=json::parse(contents); +// //wxLogMessage("GUIDE: json_path1 Loaded"); +// +// // BBS:models +// json pmodels = jLocal["machine_model_list"]; +// int nsize = pmodels.size(); +// +// BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", got %1% machine models")%nsize; +// +// for (int n = 0; n < nsize; n++) { +// json OneModel = pmodels.at(n); +// +// OneModel["model"] = OneModel["name"]; +// OneModel.erase("name"); +// +// std::string s1 = OneModel["model"]; +// std::string s2 = OneModel["sub_path"]; +// +// boost::filesystem::path sub_path = boost::filesystem::absolute(vendor_dir / s2).make_preferred(); +// std::string sub_file = sub_path.string(); +// +// //wxLogMessage("GUIDE: json_path2 %s", w2s(ModelFilePath)); +// LoadFile(sub_file, contents); +// //wxLogMessage("GUIDE: json_path2 content: %s", contents); +// json pm=json::parse(contents); +// //wxLogMessage("GUIDE: json_path2 loaded"); +// +// OneModel["vendor"] = strVendor; +// std::string NozzleOpt = pm["nozzle_diameter"]; +// StringReplace(NozzleOpt, " ", ""); +// OneModel["nozzle_diameter"] = NozzleOpt; +// OneModel["materials"] = pm["default_materials"]; +// +// //wxString strCoverPath = wxString::Format("%s\\%s\\%s_cover.png", strFolder, strVendor, std::string(s1.mb_str())); +// std::string cover_file = s1+"_cover.png"; +// boost::filesystem::path cover_path = boost::filesystem::absolute(vendor_dir / cover_file).make_preferred(); +// OneModel["cover"] = cover_path.string(); +// +// OneModel["nozzle_selected"] = ""; +// +// m_ProfileJson["model"].push_back(OneModel); +// } +// +// // BBS:Machine +// json pmachine = jLocal["machine_list"]; +// nsize = pmachine.size(); +// BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", got %1% machines")%nsize; +// for (int n = 0; n < nsize; n++) { +// json OneMachine = pmachine.at(n); +// +// std::string s1 = OneMachine["name"]; +// std::string s2 = OneMachine["sub_path"]; +// +// //wxString ModelFilePath = wxString::Format("%s\\%s\\%s", strFolder, strVendor, s2); +// boost::filesystem::path sub_path = boost::filesystem::absolute(vendor_dir / s2).make_preferred(); +// std::string sub_file = sub_path.string(); +// LoadFile(sub_file, contents); +// json pm = json::parse(contents); +// +// std::string strInstant = pm["instantiation"]; +// if (strInstant.compare("true") == 0) { +// OneMachine["model"] = pm["printer_model"]; +// +// m_ProfileJson["machine"].push_back(OneMachine); +// } +// } +// +// // BBS:Filament +// json pFilament = jLocal["filament_list"]; +// nsize = pFilament.size(); +// +// int nFalse = 0; +// int nModel = 0; +// int nFinish = 0; +// BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", got %1% filaments")%nsize; +// for (int n = 0; n < nsize; n++) { +// json OneFF = pFilament.at(n); +// +// std::string s1 = OneFF["name"]; +// std::string s2 = OneFF["sub_path"]; +// +// if (!m_ProfileJson["filament"].contains(s1)) +// { +// //wxString ModelFilePath = wxString::Format("%s\\%s\\%s", strFolder, strVendor, s2); +// boost::filesystem::path sub_path = boost::filesystem::absolute(vendor_dir / s2).make_preferred(); +// std::string sub_file = sub_path.string(); +// LoadFile(sub_file, contents); +// json pm = json::parse(contents); +// +// std::string strInstant = pm["instantiation"]; +// if (strInstant == "true") { +// std::string sV; +// std::string sT; +// +// int nRet = GetFilamentInfo(sub_file, sV, sT); +// if (nRet != 0) continue; +// +// OneFF["vendor"] = sV; +// OneFF["type"] = sT; +// +// OneFF["models"] = ""; +// OneFF["selected"] = 0; +// } +// else +// continue; +// +// } else { +// OneFF = m_ProfileJson["filament"][s1]; +// } +// +// std::string vModel = ""; +// int nm = m_ProfileJson["model"].size(); +// int bFind = 0; +// for (int m = 0; m < nm; m++) { +// std::string strFF = m_ProfileJson["model"][m]["materials"]; +// strFF = (boost::format(";%1%;")%strFF).str(); +// std::string strTT = (boost::format(";%1%;")%s1).str(); +// if (strFF.find(strTT) != std::string::npos) { +// std::string sModel = m_ProfileJson["model"][m]["model"]; +// +// vModel = (boost::format("%1%[%2%]")%vModel %sModel).str(); +// bFind = 1; +// } +// } +// +// OneFF["models"] = vModel; +// +// m_ProfileJson["filament"][s1] = OneFF; +// } +// +// //process +// json pProcess = jLocal["process_list"]; +// nsize = pProcess.size(); +// BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", got %1% processes")%nsize; +// for (int n = 0; n < nsize; n++) { +// json OneProcess = pProcess.at(n); +// +// std::string s2 = OneProcess["sub_path"]; +// //wxString ModelFilePath = wxString::Format("%s\\%s\\%s", strFolder, strVendor, s2); +// boost::filesystem::path sub_path = boost::filesystem::absolute(vendor_dir / s2).make_preferred(); +// std::string sub_file = sub_path.string(); +// LoadFile(sub_file, contents); +// json pm = json::parse(contents); +// +// std::string bInstall = pm["instantiation"]; +// if (bInstall == "true") +// { +// m_ProfileJson["process"].push_back(OneProcess); +// } +// } +// +// } +// catch(nlohmann::detail::parse_error &err) { +// BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": parse "<Hide(); } -void WebViewPanel::OnFreshLoginStatus(wxTimerEvent &event) { - Slic3r::GUI::wxGetApp().get_login_info(); +void WebViewPanel::OnFreshLoginStatus(wxTimerEvent &event) +{ + auto mainframe = Slic3r::GUI::wxGetApp().mainframe; + if (mainframe && mainframe->m_webview == this) + Slic3r::GUI::wxGetApp().get_login_info(); } void WebViewPanel::SendRecentList(wxString const &sequence_id) diff --git a/src/slic3r/GUI/Widgets/AMSControl.cpp b/src/slic3r/GUI/Widgets/AMSControl.cpp index a1e52f50f9..dbe74760d8 100644 --- a/src/slic3r/GUI/Widgets/AMSControl.cpp +++ b/src/slic3r/GUI/Widgets/AMSControl.cpp @@ -1345,7 +1345,7 @@ AMSControl::AMSControl(wxWindow *parent, wxWindowID id, const wxPoint &pos, cons m_panel_can = new StaticBox(amswin, wxID_ANY, wxDefaultPosition, AMS_CANS_SIZE, wxBORDER_NONE); m_panel_can->SetMinSize(AMS_CANS_SIZE); - m_panel_can->SetCornerRadius(10); + m_panel_can->SetCornerRadius(FromDIP(10)); m_panel_can->SetBackgroundColor(AMS_CONTROL_DEF_BLOCK_BK_COLOUR); m_sizer_cans = new wxBoxSizer(wxHORIZONTAL); @@ -1532,7 +1532,7 @@ AMSControl::AMSControl(wxWindow *parent, wxWindowID id, const wxPoint &pos, cons m_button_calibration_again->SetBorderColor(AMS_CONTROL_BRAND_COLOUR); m_button_calibration_again->SetTextColor(AMS_CONTROL_WHITE_COLOUR); m_button_calibration_again->SetMinSize(AMS_CONTRO_CALIBRATION_BUTTON_SIZE); - m_button_calibration_again->SetCornerRadius(12); + m_button_calibration_again->SetCornerRadius(FromDIP(12)); m_button_calibration_again->Bind(wxEVT_LEFT_DOWN, &AMSControl::on_clibration_again_click, this); sizer_button->Add(m_button_calibration_again, 0, wxALL, 5); @@ -1542,7 +1542,7 @@ AMSControl::AMSControl(wxWindow *parent, wxWindowID id, const wxPoint &pos, cons m_button_calibration_cancel->SetBorderColor(AMS_CONTROL_GRAY700); m_button_calibration_cancel->SetTextColor(AMS_CONTROL_GRAY800); m_button_calibration_cancel->SetMinSize(AMS_CONTRO_CALIBRATION_BUTTON_SIZE); - m_button_calibration_cancel->SetCornerRadius(12); + m_button_calibration_cancel->SetCornerRadius(FromDIP(12)); m_button_calibration_cancel->Bind(wxEVT_LEFT_DOWN, &AMSControl::on_clibration_cancel_click, this); sizer_button->Add(m_button_calibration_cancel, 0, wxALL, 5); @@ -1575,9 +1575,9 @@ AMSControl::AMSControl(wxWindow *parent, wxWindowID id, const wxPoint &pos, cons void AMSControl::init_scaled_buttons() { m_button_extruder_feed->SetMinSize(wxSize(-1, FromDIP(24))); - m_button_extruder_feed->SetCornerRadius(FromDIP(11)); + m_button_extruder_feed->SetCornerRadius(FromDIP(12)); m_button_extruder_back->SetMinSize(wxSize(-1, FromDIP(24))); - m_button_extruder_back->SetCornerRadius(FromDIP(11)); + m_button_extruder_back->SetCornerRadius(FromDIP(12)); m_button_ams_setting->SetMinSize(wxSize(-1, FromDIP(33))); m_button_ams_setting->SetCornerRadius(FromDIP(12)); } diff --git a/src/slic3r/GUI/Widgets/AxisCtrlButton.cpp b/src/slic3r/GUI/Widgets/AxisCtrlButton.cpp index 58fbe736f7..fbfe7e6a75 100644 --- a/src/slic3r/GUI/Widgets/AxisCtrlButton.cpp +++ b/src/slic3r/GUI/Widgets/AxisCtrlButton.cpp @@ -3,7 +3,7 @@ #include -static const wxColour bd = wxColour(0x00AE42); +static const wxColour bd = wxColour(0, 174, 66); static const wxColour BUTTON_BG_COL = wxColour(238, 238, 238); static const wxColour BUTTON_IN_BG_COL = wxColour(206, 206, 206); static const wxColour blank_bg = wxColour(0xFFFFFF); @@ -21,7 +21,7 @@ END_EVENT_TABLE() #define OUTER_SIZE FromDIP(105) #define INNER_SIZE FromDIP(58) #define HOME_SIZE FromDIP(23) -#define BLANK_SIZE FromDIP(23) +#define BLANK_SIZE FromDIP(24) #define GAP_SIZE FromDIP(4) AxisCtrlButton::AxisCtrlButton(wxWindow *parent, ScalableBitmap &icon, long stlye) diff --git a/src/slic3r/GUI/Widgets/Button.cpp b/src/slic3r/GUI/Widgets/Button.cpp index 7caa98a6fb..8773e0315c 100644 --- a/src/slic3r/GUI/Widgets/Button.cpp +++ b/src/slic3r/GUI/Widgets/Button.cpp @@ -253,7 +253,12 @@ void Button::keyDownUp(wxKeyEvent &event) GetEventHandler()->ProcessEvent(evt); return; } - event.Skip(); + if (event.GetEventType() == wxEVT_KEY_DOWN && + (event.GetKeyCode() == WXK_TAB || event.GetKeyCode() == WXK_LEFT || event.GetKeyCode() == WXK_RIGHT + || event.GetKeyCode() == WXK_UP || event.GetKeyCode() == WXK_DOWN)) + HandleAsNavigationKey(event); + else + event.Skip(); } void Button::sendButtonEvent() @@ -271,17 +276,10 @@ WXLRESULT Button::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) if (nMsg == WM_KEYDOWN) { wxKeyEvent event(CreateKeyEvent(wxEVT_KEY_DOWN, wParam, lParam)); switch (wParam) { - case WXK_RETURN: { + case WXK_RETURN: { // WXK_RETURN key is handled by default button GetEventHandler()->ProcessEvent(event); return 0; } - case WXK_TAB: - case WXK_LEFT: - case WXK_RIGHT: - case WXK_UP: - case WXK_DOWN: - if (HandleAsNavigationKey(event)) - return 0; } } return wxWindow::MSWWindowProc(nMsg, wParam, lParam); diff --git a/src/slic3r/GUI/Widgets/CheckBox.cpp b/src/slic3r/GUI/Widgets/CheckBox.cpp index e3858480e4..4ca1ca5ba0 100644 --- a/src/slic3r/GUI/Widgets/CheckBox.cpp +++ b/src/slic3r/GUI/Widgets/CheckBox.cpp @@ -43,9 +43,16 @@ void CheckBox::SetHalfChecked(bool value) void CheckBox::Rescale() { - m_on.msw_rescale(); - m_off.msw_rescale(); - SetSize(m_on.GetBmpSize()); + m_on.msw_rescale(); + m_half.msw_rescale(); + m_off.msw_rescale(); + m_on_disabled.msw_rescale(); + m_half_disabled.msw_rescale(); + m_off_disabled.msw_rescale(); + m_on_focused.msw_rescale(); + m_half_focused.msw_rescale(); + m_off_focused.msw_rescale(); + SetSize(m_on.GetBmpSize()); update(); } diff --git a/src/slic3r/GUI/Widgets/ComboBox.cpp b/src/slic3r/GUI/Widgets/ComboBox.cpp index ec416ea045..c15ade211c 100644 --- a/src/slic3r/GUI/Widgets/ComboBox.cpp +++ b/src/slic3r/GUI/Widgets/ComboBox.cpp @@ -206,7 +206,7 @@ void ComboBox::DoSetItemClientData(unsigned int n, void *data) void ComboBox::mouseDown(wxMouseEvent &event) { - //SetFocus(); + SetFocus(); if (drop_down) { drop.Hide(); } else if (drop.HasDismissLongTime()) { @@ -230,7 +230,8 @@ void ComboBox::mouseWheelMoved(wxMouseEvent &event) } } -void ComboBox::keyDown(wxKeyEvent& event) { +void ComboBox::keyDown(wxKeyEvent& event) +{ switch (event.GetKeyCode()) { case WXK_RETURN: case WXK_SPACE: diff --git a/src/slic3r/GUI/Widgets/ComboBox.hpp b/src/slic3r/GUI/Widgets/ComboBox.hpp index a15cd9ae14..0a8e018dce 100644 --- a/src/slic3r/GUI/Widgets/ComboBox.hpp +++ b/src/slic3r/GUI/Widgets/ComboBox.hpp @@ -4,8 +4,8 @@ #include "TextInput.hpp" #include "DropDown.hpp" -#define CB_NO_DROP_ICON 0x1000000 -#define CB_NO_TEXT 0x2000000 +#define CB_NO_DROP_ICON DD_NO_CHECK_ICON +#define CB_NO_TEXT DD_NO_TEXT class ComboBox : public wxWindowWithItems { diff --git a/src/slic3r/GUI/Widgets/DropDown.cpp b/src/slic3r/GUI/Widgets/DropDown.cpp index f588864c18..8095969edb 100644 --- a/src/slic3r/GUI/Widgets/DropDown.cpp +++ b/src/slic3r/GUI/Widgets/DropDown.cpp @@ -354,7 +354,7 @@ void DropDown::autoPosition() } if (GetPosition().y > pos.y) { // may exceed - auto drect = wxDisplay(wxDisplay::GetFromWindow(GetParent())).GetGeometry(); + auto drect = wxDisplay(GetParent()).GetGeometry(); if (GetPosition().y + size.y + 10 > drect.GetBottom()) { if (use_content_width && texts.size() <= 15) size.x += 6; size.y = drect.GetBottom() - GetPosition().y - 10; diff --git a/src/slic3r/GUI/Widgets/DropDown.hpp b/src/slic3r/GUI/Widgets/DropDown.hpp index 03f91a05a9..60a404b5de 100644 --- a/src/slic3r/GUI/Widgets/DropDown.hpp +++ b/src/slic3r/GUI/Widgets/DropDown.hpp @@ -5,9 +5,9 @@ #include "../wxExtensions.hpp" #include "StateHandler.hpp" -#define DD_NO_CHECK_ICON 0x1000000 -#define DD_NO_TEXT 0x2000000 -#define DD_STYLE_MASK 0x3000000 +#define DD_NO_CHECK_ICON 0x0001 +#define DD_NO_TEXT 0x0002 +#define DD_STYLE_MASK 0x0003 wxDECLARE_EVENT(EVT_DISMISS, wxCommandEvent); diff --git a/src/slic3r/GUI/Widgets/ImageSwitchButton.cpp b/src/slic3r/GUI/Widgets/ImageSwitchButton.cpp index 8eb32aa8d8..af8c015fae 100644 --- a/src/slic3r/GUI/Widgets/ImageSwitchButton.cpp +++ b/src/slic3r/GUI/Widgets/ImageSwitchButton.cpp @@ -20,20 +20,18 @@ static const wxColour DEFAULT_PRESS_COL = wxColour(238, 238, 238); ImageSwitchButton::ImageSwitchButton(wxWindow *parent, ScalableBitmap &img_on, ScalableBitmap &img_off, long style) : text_color(std::make_pair(0x6B6B6B, (int) StateColor::Disabled), std::make_pair(*wxBLACK, (int) StateColor::Normal)) - , state_handler(this) { + radius = 0; m_padding = 0; m_on = img_on; m_off = img_off; - bg_color = StateColor(std::make_pair(DEFAULT_PRESS_COL, (int) StateColor::Pressed), - std::make_pair(*wxWHITE, (int) StateColor::Normal)); - border_color = StateColor(std::make_pair(DEFAULT_HOVER_COL, (int) StateColor::Hovered)); + background_color = StateColor(std::make_pair(*wxWHITE, (int) StateColor::Disabled), std::make_pair(DEFAULT_PRESS_COL, (int) StateColor::Pressed), + std::make_pair(*wxWHITE, (int) StateColor::Normal)); + border_color = StateColor(std::make_pair(*wxWHITE, (int) StateColor::Disabled), std::make_pair(DEFAULT_HOVER_COL, (int) StateColor::Focused), + std::make_pair(DEFAULT_HOVER_COL, (int) StateColor::Hovered), std::make_pair(*wxWHITE, (int) StateColor::Normal)); StaticBox::Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, style); - state_handler.attach({&bg_color}); - state_handler.attach({&border_color}); - state_handler.update_binds(); messureSize(); Refresh(); } @@ -64,19 +62,6 @@ void ImageSwitchButton::SetTextColor(StateColor const &color) Refresh(); } -void ImageSwitchButton::SetBorderColor(StateColor const &color) -{ - border_color = color; - messureSize(); - Refresh(); -} - -void ImageSwitchButton::SetBgColor(StateColor const &color) { - bg_color = color; - messureSize(); - Refresh(); -} - void ImageSwitchButton::SetValue(bool value) { m_on_off = value; @@ -110,16 +95,6 @@ void ImageSwitchButton::render(wxDC& dc) int states = state_handler.states(); wxSize size = GetSize(); - if (pressedDown) { - dc.SetBrush(bg_color.colorForStates(StateColor::Pressed)); - dc.DrawRectangle(wxRect(0, 0, size.x, size.y)); - } - - if (hover) { - dc.SetPen(border_color.colorForStates(StateColor::Hovered)); - dc.DrawRectangle(wxRect(0, 0, size.x, size.y)); - } - wxSize szIcon; wxSize szContent = textSize; ScalableBitmap &icon = GetValue() ? m_on : m_off; diff --git a/src/slic3r/GUI/Widgets/ImageSwitchButton.hpp b/src/slic3r/GUI/Widgets/ImageSwitchButton.hpp index e28f13e138..4cf12abd7f 100644 --- a/src/slic3r/GUI/Widgets/ImageSwitchButton.hpp +++ b/src/slic3r/GUI/Widgets/ImageSwitchButton.hpp @@ -17,8 +17,6 @@ public: void SetLabels(wxString const & lbl_on, wxString const & lbl_off); void SetImages(ScalableBitmap &img_on, ScalableBitmap &img_off); void SetTextColor(StateColor const &color); - void SetBorderColor(StateColor const &color); - void SetBgColor(StateColor const &color); void SetValue(bool value); void SetPadding(int padding); @@ -50,9 +48,6 @@ private: wxString labels[2]; StateColor text_color; - StateColor bg_color; - StateColor border_color; - StateHandler state_handler; }; #endif // !slic3r_GUI_SwitchButton_hpp_ diff --git a/src/slic3r/GUI/Widgets/Label.cpp b/src/slic3r/GUI/Widgets/Label.cpp index 6f7d1b9766..93ec8c2a9d 100644 --- a/src/slic3r/GUI/Widgets/Label.cpp +++ b/src/slic3r/GUI/Widgets/Label.cpp @@ -63,12 +63,11 @@ wxSize Label::split_lines(wxDC &dc, int width, const wxString &text, wxString &m return dc.GetMultiLineTextExtent(multiline_text); } -Label::Label(wxString const &text, wxWindow *parent) : Label(Body_16, text, parent) {} +Label::Label(wxWindow *parent, wxString const &text) : Label(parent, Body_14, text) {} -Label::Label(wxFont const &font, wxWindow *parent) : Label(font, "", parent) {} - -Label::Label(wxFont const &font, wxString const &text, wxWindow *parent) : wxStaticText(parent, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, 0) +Label::Label(wxWindow *parent, wxFont const &font, wxString const &text) + : wxStaticText(parent, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, 0) { - SetBackgroundColour(StaticBox::GetParentBackgroundColor(parent)); SetFont(font); + SetBackgroundColour(StaticBox::GetParentBackgroundColor(parent)); } diff --git a/src/slic3r/GUI/Widgets/Label.hpp b/src/slic3r/GUI/Widgets/Label.hpp index c1ffe913d5..d82451ee85 100644 --- a/src/slic3r/GUI/Widgets/Label.hpp +++ b/src/slic3r/GUI/Widgets/Label.hpp @@ -6,11 +6,9 @@ class Label : public wxStaticText { public: - Label(wxString const& text, wxWindow* parent = NULL); + Label(wxWindow *parent, wxString const &text = {}); - Label(wxFont const& font, wxWindow* parent = NULL); - - Label(wxFont const& font, wxString const& text, wxWindow* parent = NULL); + Label(wxWindow *parent, wxFont const &font, wxString const &text = {}); public: static wxFont Head_24; diff --git a/src/slic3r/GUI/Widgets/ProgressDialog.cpp b/src/slic3r/GUI/Widgets/ProgressDialog.cpp index 45dfc49714..e6860504e8 100644 --- a/src/slic3r/GUI/Widgets/ProgressDialog.cpp +++ b/src/slic3r/GUI/Widgets/ProgressDialog.cpp @@ -221,6 +221,7 @@ bool ProgressDialog::Create(const wxString &title, const wxString &message, int m_button_cancel = new Button(this, _L("Cancel")); m_button_cancel->SetTextColor(PROGRESSDIALOG_GREY_700); m_button_cancel->SetMinSize(PROGRESSDIALOG_CANCEL_BUTTON_SIZE); + m_button_cancel->SetCornerRadius(PROGRESSDIALOG_CANCEL_BUTTON_SIZE.y / 2); m_button_cancel->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &event) { if (m_state == Finished) { event.Skip(); diff --git a/src/slic3r/GUI/Widgets/SideTools.cpp b/src/slic3r/GUI/Widgets/SideTools.cpp index f614b4e32f..dfffe1ffb1 100644 --- a/src/slic3r/GUI/Widgets/SideTools.cpp +++ b/src/slic3r/GUI/Widgets/SideTools.cpp @@ -36,7 +36,7 @@ namespace Slic3r { namespace GUI { this->Bind(wxEVT_LEFT_UP, &SideTools::on_mouse_left_up, this); } -SideTools::~SideTools() {} +SideTools::~SideTools() { delete m_intetval_timer; } void SideTools::set_none_printer_mode() { diff --git a/src/slic3r/GUI/Widgets/SpinInput.cpp b/src/slic3r/GUI/Widgets/SpinInput.cpp index e54a5db00c..958bc9423c 100644 --- a/src/slic3r/GUI/Widgets/SpinInput.cpp +++ b/src/slic3r/GUI/Widgets/SpinInput.cpp @@ -61,18 +61,7 @@ void SpinInput::Create(wxWindow *parent, text_ctrl->SetBackgroundColour(background_color.colorForStates(state_handler.states())); text_ctrl->SetForegroundColour(text_color.colorForStates(state_handler.states())); text_ctrl->SetInitialSize(text_ctrl->GetBestSize()); - text_ctrl->Bind(wxEVT_SET_FOCUS, [this](auto &e) { - e.SetId(GetId()); - ProcessEventLocally(e); - }); - text_ctrl->Bind(wxEVT_ENTER_WINDOW, [this](auto &e) { - e.SetId(GetId()); - ProcessEventLocally(e); - }); - text_ctrl->Bind(wxEVT_LEAVE_WINDOW, [this](auto &e) { - e.SetId(GetId()); - ProcessEventLocally(e); - }); + state_handler.attach_child(text_ctrl); text_ctrl->Bind(wxEVT_KILL_FOCUS, &SpinInput::onTextLostFocus, this); text_ctrl->Bind(wxEVT_TEXT_ENTER, &SpinInput::onTextEnter, this); text_ctrl->Bind(wxEVT_KEY_DOWN, &SpinInput::keyPressed, this); @@ -281,6 +270,7 @@ void SpinInput::onTextLostFocus(wxEvent &event) // pass to outer event.SetId(GetId()); ProcessEventLocally(event); + e.Skip(); } void SpinInput::onTextEnter(wxCommandEvent &event) diff --git a/src/slic3r/GUI/Widgets/StateHandler.cpp b/src/slic3r/GUI/Widgets/StateHandler.cpp index 20e826390b..72616ed8fd 100644 --- a/src/slic3r/GUI/Widgets/StateHandler.cpp +++ b/src/slic3r/GUI/Widgets/StateHandler.cpp @@ -5,24 +5,44 @@ wxDEFINE_EVENT(EVT_ENABLE_CHANGED, wxCommandEvent); StateHandler::StateHandler(wxWindow * owner) : owner_(owner) { + owner_->PushEventHandler(this); if (owner->IsEnabled()) states_ |= Enabled; if (owner->HasFocus()) states_ |= Focused; } +StateHandler::~StateHandler() { owner_->RemoveEventHandler(this); } + void StateHandler::attach(StateColor const &color) { colors_.push_back(&color); } + void StateHandler::attach(std::vector const & colors) { colors_.insert(colors_.end(), colors.begin(), colors.end()); } +void StateHandler::attach_child(wxWindow *child) +{ + auto ch = new StateHandler(this, child); + children_.emplace_back(ch); + ch->update_binds(); + states2_ |= ch->states(); +} + +void StateHandler::remove_child(wxWindow *child) +{ + children_.erase(std::remove_if(children_.begin(), children_.end(), + [child](auto &c) { return c->owner_ == child; }), children_.end()); + states2_ = 0; + for (auto & c : children_) states2_ |= c->states(); +} + void StateHandler::update_binds() { - int bind_states = 0; + int bind_states = parent_ ? (parent_->bind_states_ & ~Enabled) : 0; for (auto c : colors_) { bind_states |= c->states(); } @@ -35,45 +55,68 @@ void StateHandler::update_binds() int s = states[i]; if (diff & s) { if (bind_states & s) { - owner_->Bind(events[i], &StateHandler::changed, this); + Bind(events[i], &StateHandler::changed, this); if (events2[i]) - owner_->Bind(events2[i], &StateHandler::changed, this); + Bind(events2[i], &StateHandler::changed, this); } else { - owner_->Unbind(events[i], &StateHandler::changed, this); + Unbind(events[i], &StateHandler::changed, this); if (events2[i]) owner_->Unbind(events2[i], &StateHandler::changed, this); } } } bind_states_ = bind_states; - owner_->Refresh(); + for (auto &c : children_) c->update_binds(); } -void StateHandler::changed(wxEvent & event) +StateHandler::StateHandler(StateHandler *parent, wxWindow *owner) + : StateHandler(owner) +{ + states_ &= ~Enabled; + parent_ = parent; +} + +void StateHandler::changed(wxEvent &event) { event.Skip(); wxEventType events[] = {EVT_ENABLE_CHANGED, wxEVT_CHECKBOX, wxEVT_SET_FOCUS, wxEVT_ENTER_WINDOW, wxEVT_LEFT_DOWN}; wxEventType events2[] = {{0}, {0}, wxEVT_KILL_FOCUS, wxEVT_LEAVE_WINDOW, wxEVT_LEFT_UP}; - int old = states2_ | states_; + int old = states_; // some events are from another window (ex: text_ctrl of TextInput), save state in states2_ to avoid conflicts - int & states = event.GetEventObject() == owner_ ? states_ : states2_; for (int i = 0; i < 5; ++i) { if (events2[i]) { if (event.GetEventType() == events[i]) { - states |= 1 << i; + states_ |= 1 << i; break; } else if (event.GetEventType() == events2[i]) { - states &= ~(1 << i); + states_ &= ~(1 << i); break; } } else { if (event.GetEventType() == events[i]) { - states ^= (1 << i); + states_ ^= (1 << i); break; } } } - if (old != (states2_ | states_)) - owner_->Refresh(); + if (old != states_ && (old | states2_) != (states_ | states2_)) { + if (parent_) + parent_->changed(states_ | states2_); + else + owner_->Refresh(); + } +} + +void StateHandler::changed(int) +{ + int old = states2_; + states2_ = 0; + for (auto &c : children_) states2_ |= c->states(); + if (old != states2_ && (old | states_) != (states_ | states2_)) { + if (parent_) + parent_->changed(states_ | states2_); + else + owner_->Refresh(); + } } diff --git a/src/slic3r/GUI/Widgets/StateHandler.hpp b/src/slic3r/GUI/Widgets/StateHandler.hpp index 669ed20e39..9ef155c7db 100644 --- a/src/slic3r/GUI/Widgets/StateHandler.hpp +++ b/src/slic3r/GUI/Widgets/StateHandler.hpp @@ -26,24 +26,36 @@ public: public: StateHandler(wxWindow * owner); + ~StateHandler(); + public: void attach(StateColor const & color); void attach(std::vector const & colors); + void attach_child(wxWindow *child); + + void remove_child(wxWindow *child); + void update_binds(); int states() const { return states_ | states2_; } private: - void changed(wxEvent & event); + StateHandler(StateHandler * parent, wxWindow *owner); + + void changed(wxEvent &event); + + void changed(int state2); private: wxWindow * owner_; std::vector colors_; int bind_states_ = 0; int states_ = 0; - int states2_ = 0; + int states2_ = 0; // from children + std::vector> children_; + StateHandler * parent_ = nullptr; }; #endif // !slic3r_GUI_StateHandler_hpp_ diff --git a/src/slic3r/GUI/Widgets/TabCtrl.cpp b/src/slic3r/GUI/Widgets/TabCtrl.cpp index b2b7602726..223038e853 100644 --- a/src/slic3r/GUI/Widgets/TabCtrl.cpp +++ b/src/slic3r/GUI/Widgets/TabCtrl.cpp @@ -5,7 +5,8 @@ wxDEFINE_EVENT( wxEVT_TAB_SEL_CHANGED, wxCommandEvent ); BEGIN_EVENT_TABLE(TabCtrl, StaticBox) -// catch paint events +EVT_KEY_DOWN(TabCtrl::keyDown) + END_EVENT_TABLE() /* @@ -201,6 +202,16 @@ void TabCtrl::DoSetSize(int x, int y, int width, int height, int sizeFlags) relayout(); } +#ifdef __WIN32__ + +WXLRESULT TabCtrl::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) +{ + if (nMsg == WM_GETDLGCODE) { return DLGC_WANTARROWS; } + return wxWindow::MSWWindowProc(nMsg, wParam, lParam); +} + +#endif + void TabCtrl::relayout() { int offset = 10; @@ -243,11 +254,28 @@ void TabCtrl::relayout() void TabCtrl::buttonClicked(wxCommandEvent &event) { - auto btn = event.GetEventObject(); + SetFocus(); + auto btn = event.GetEventObject(); auto iter = std::find(btns.begin(), btns.end(), btn); SelectItem(iter == btns.end() ? -1 : iter - btns.begin()); } +void TabCtrl::keyDown(wxKeyEvent &event) +{ + switch (event.GetKeyCode()) { + case WXK_UP: + case WXK_DOWN: + case WXK_LEFT: + case WXK_RIGHT: + if ((event.GetKeyCode() == WXK_UP || event.GetKeyCode() == WXK_LEFT) && GetSelection() > 0) { + SelectItem(GetSelection() - 1); + } else if ((event.GetKeyCode() == WXK_DOWN || event.GetKeyCode() == WXK_RIGHT) && GetSelection() + 1 < btns.size()) { + SelectItem(GetSelection() + 1); + } + break; + } +} + void TabCtrl::doRender(wxDC& dc) { wxSize size = GetSize(); @@ -256,9 +284,9 @@ void TabCtrl::doRender(wxDC& dc) auto x1 = btns[sel]->GetPosition().x; auto x2 = x1 + btns[sel]->GetSize().x; - const int BS = border_width / 2; const int BS2 = (1 + border_width) / 2; #if 0 + const int BS = border_width / 2; x1 -= TAB_BUTTON_SPACE; x2 += TAB_BUTTON_SPACE; dc.SetPen(wxPen(border_color.colorForStates(states), border_width)); dc.SetBrush(*wxTRANSPARENT_BRUSH); @@ -275,7 +303,7 @@ void TabCtrl::doRender(wxDC& dc) dc.SetPen(wxPen(border_color.colorForStates(states), border_width)); dc.DrawLine(0, size.y - BS2, size.x, size.y - BS2); wxColor c(0x42AE00); - dc.SetPen(wxPen(c, 0)); + dc.SetPen(wxPen(c, 1)); dc.SetBrush(c); dc.DrawRoundedRectangle(x1 - radius, size.y - BS2 - border_width * 3, x2 + radius * 2 - x1, border_width * 3, radius); #endif diff --git a/src/slic3r/GUI/Widgets/TabCtrl.hpp b/src/slic3r/GUI/Widgets/TabCtrl.hpp index 92b7f1cd30..a25f332fb3 100644 --- a/src/slic3r/GUI/Widgets/TabCtrl.hpp +++ b/src/slic3r/GUI/Widgets/TabCtrl.hpp @@ -65,9 +65,14 @@ public: private: virtual void DoSetSize(int x, int y, int width, int height, int sizeFlags = wxSIZE_AUTO); +#ifdef __WIN32__ + WXLRESULT MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) override; +#endif + void relayout(); void buttonClicked(wxCommandEvent & event); + void keyDown(wxKeyEvent &event); void doRender(wxDC & dc) override; diff --git a/src/slic3r/GUI/Widgets/TempInput.cpp b/src/slic3r/GUI/Widgets/TempInput.cpp index 02602ada54..d31fa9f0a1 100644 --- a/src/slic3r/GUI/Widgets/TempInput.cpp +++ b/src/slic3r/GUI/Widgets/TempInput.cpp @@ -19,18 +19,14 @@ END_EVENT_TABLE() TempInput::TempInput() - : state_handler(this) - , border_color(std::make_pair(*wxWHITE, (int) StateColor::Disabled), - std::make_pair(0x00AE42, (int) StateColor::Focused), - std::make_pair(0x00AE42, (int) StateColor::Hovered), - std::make_pair(*wxWHITE, (int) StateColor::Normal)) - , label_color(std::make_pair(wxColour(0xAC,0xAC,0xAC), (int) StateColor::Disabled),std::make_pair(0x323A3D, (int) StateColor::Normal)) + : label_color(std::make_pair(wxColour(0xAC,0xAC,0xAC), (int) StateColor::Disabled),std::make_pair(0x323A3D, (int) StateColor::Normal)) , text_color(std::make_pair(wxColour(0xAC,0xAC,0xAC), (int) StateColor::Disabled), std::make_pair(0x6B6B6B, (int) StateColor::Normal)) - , background_color(std::make_pair(*wxWHITE, (int) StateColor::Disabled), - std::make_pair(*wxWHITE, (int) StateColor::Normal)) { hover = false; radius = 0; + border_color = StateColor(std::make_pair(*wxWHITE, (int) StateColor::Disabled), std::make_pair(0x00AE42, (int) StateColor::Focused), std::make_pair(0x00AE42, (int) StateColor::Hovered), + std::make_pair(*wxWHITE, (int) StateColor::Normal)); + background_color = StateColor(std::make_pair(*wxWHITE, (int) StateColor::Disabled), std::make_pair(*wxWHITE, (int) StateColor::Normal)); SetFont(Label::Body_12); } @@ -44,41 +40,34 @@ TempInput::TempInput(wxWindow *parent, int type, wxString text, wxString label, void TempInput::Create(wxWindow *parent, wxString text, wxString label, wxString normal_icon, wxString actice_icon, const wxPoint &pos, const wxSize &size, long style) { - wxWindow::Create(parent, wxID_ANY, pos, size, style); + StaticBox::Create(parent, wxID_ANY, pos, size, style); wxWindow::SetLabel(label); style &= ~wxALIGN_CENTER_HORIZONTAL; - state_handler.attach({&border_color, &text_color, &background_color}); + state_handler.attach({&label_color, &text_color}); state_handler.update_binds(); - text_ctrl = new wxTextCtrl(this, wxID_ANY, text, {5, 5}, wxDefaultSize, wxTE_PROCESS_ENTER | wxBORDER_NONE, wxTextValidator(wxFILTER_NUMERIC), wxTextCtrlNameStr); text_ctrl->SetMaxLength(3); - + state_handler.attach_child(text_ctrl); text_ctrl->Bind(wxEVT_SET_FOCUS, [this](auto &e) { e.SetId(GetId()); ProcessEventLocally(e); - - //enter input mode + e.Skip(); + if (m_read_only) return; + // enter input mode auto temp = text_ctrl->GetValue(); if (temp.length() > 0 && temp[0] == (0x5f)) { text_ctrl->SetValue(wxEmptyString); } - if (wdialog != nullptr) { wdialog->Dismiss(); } }); text_ctrl->Bind(wxEVT_ENTER_WINDOW, [this](auto &e) { - if (m_read_only) {SetCursor(wxCURSOR_ARROW);} - e.SetId(GetId()); - ProcessEventLocally(e); - }); - text_ctrl->Bind(wxEVT_LEAVE_WINDOW, [this](auto &e) { - e.SetId(GetId()); - ProcessEventLocally(e); + if (m_read_only) { SetCursor(wxCURSOR_ARROW); } }); text_ctrl->Bind(wxEVT_KILL_FOCUS, [this](auto &e) { - OnEdit(); e.SetId(GetId()); ProcessEventLocally(e); - + e.Skip(); + OnEdit(); auto temp = text_ctrl->GetValue(); if (temp.ToStdString().empty()) { text_ctrl->SetValue(wxString("_")); @@ -102,9 +91,6 @@ void TempInput::Create(wxWindow *parent, wxString text, wxString label, wxString }); text_ctrl->Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent &e) { OnEdit(); - e.SetId(GetId()); - ProcessEventLocally(e); - auto temp = text_ctrl->GetValue(); if (temp.ToStdString().empty()) return; if (!AllisNum(temp.ToStdString())) return; @@ -271,12 +257,6 @@ void TempInput::SetMaxTemp(int temp) { max_temp = temp; } void TempInput::SetMinTemp(int temp) { min_temp = temp; } -void TempInput::SetCornerRadius(double radius) -{ - this->radius = radius; - Refresh(); -} - void TempInput::SetLabel(const wxString &label) { wxWindow::SetLabel(label); @@ -284,12 +264,6 @@ void TempInput::SetLabel(const wxString &label) Refresh(); } -void TempInput::SetBorderColor(StateColor const &color) -{ - border_color = color; - state_handler.update_binds(); -} - void TempInput::SetTextColor(StateColor const &color) { text_color = color; @@ -302,12 +276,6 @@ void TempInput::SetLabelColor(StateColor const &color) state_handler.update_binds(); } -void TempInput::SetBackgroundColor(StateColor const &color) -{ - background_color = color; - state_handler.update_binds(); -} - void TempInput::Rescale() { if (this->normal_icon.bmp().IsOk()) this->normal_icon.msw_rescale(); @@ -393,18 +361,18 @@ void TempInput::paintEvent(wxPaintEvent &evt) */ void TempInput::render(wxDC &dc) { + StaticBox::render(dc); int states = state_handler.states(); wxSize size = GetSize(); bool align_right = GetWindowStyle() & wxRIGHT; if (warning_mode) { - dc.SetPen(wxPen(wxColour(255, 111, 0))); + border_color = wxColour(255, 111, 0); } else { - dc.SetPen(wxPen(border_color.colorForStates(states))); + border_color = StateColor(std::make_pair(*wxWHITE, (int) StateColor::Disabled), std::make_pair(0x00AE42, (int) StateColor::Focused), + std::make_pair(0x00AE42, (int) StateColor::Hovered), std::make_pair(*wxWHITE, (int) StateColor::Normal)); } - dc.SetBrush(wxBrush(background_color.colorForStates(states))); - dc.DrawRoundedRectangle(0, 0, size.x, size.y, radius); dc.SetBrush(*wxTRANSPARENT_BRUSH); // start draw wxPoint pt = {padding_left, 0}; diff --git a/src/slic3r/GUI/Widgets/TempInput.hpp b/src/slic3r/GUI/Widgets/TempInput.hpp index 1d1484a3f2..7829b10824 100644 --- a/src/slic3r/GUI/Widgets/TempInput.hpp +++ b/src/slic3r/GUI/Widgets/TempInput.hpp @@ -1,16 +1,15 @@ #ifndef slic3r_GUI_TempInput_hpp_ #define slic3r_GUI_TempInput_hpp_ -#include #include "../wxExtensions.hpp" -#include "StateHandler.hpp" +#include +#include "StaticBox.hpp" wxDECLARE_EVENT(wxCUSTOMEVT_SET_TEMP_FINISH, wxCommandEvent); -class TempInput : public wxWindow +class TempInput : public wxNavigationEnabled { bool hover; - double radius; bool m_read_only{false}; wxSize labelSize; @@ -18,11 +17,8 @@ class TempInput : public wxWindow ScalableBitmap actice_icon; ScalableBitmap degree_icon; - StateHandler state_handler; StateColor label_color; StateColor text_color; - StateColor border_color; - StateColor background_color; wxTextCtrl * text_ctrl; wxStaticText *warning_text; @@ -93,18 +89,12 @@ public: wxString GetTagTemp() { return text_ctrl->GetValue(); } wxString GetCurrTemp() { return GetLabel(); } - void SetCornerRadius(double radius); - void SetLabel(const wxString &label); - void SetBorderColor(StateColor const &color); - void SetTextColor(StateColor const &color); void SetLabelColor(StateColor const &color); - void SetBackgroundColor(StateColor const &color); - virtual void Rescale(); virtual bool Enable(bool enable = true) override; diff --git a/src/slic3r/GUI/Widgets/TextInput.cpp b/src/slic3r/GUI/Widgets/TextInput.cpp index 446948e543..abed10c94e 100644 --- a/src/slic3r/GUI/Widgets/TextInput.cpp +++ b/src/slic3r/GUI/Widgets/TextInput.cpp @@ -60,22 +60,12 @@ void TextInput::Create(wxWindow * parent, text_ctrl->SetInitialSize(text_ctrl->GetBestSize()); text_ctrl->SetBackgroundColour(background_color.colorForStates(state_handler.states())); text_ctrl->SetForegroundColour(text_color.colorForStates(state_handler.states())); - text_ctrl->Bind(wxEVT_SET_FOCUS, [this](auto &e) { - e.SetId(GetId()); - ProcessEventLocally(e); - }); - text_ctrl->Bind(wxEVT_ENTER_WINDOW, [this](auto &e) { - e.SetId(GetId()); - ProcessEventLocally(e); - }); - text_ctrl->Bind(wxEVT_LEAVE_WINDOW, [this](auto &e) { - e.SetId(GetId()); - ProcessEventLocally(e); - }); + state_handler.attach_child(text_ctrl); text_ctrl->Bind(wxEVT_KILL_FOCUS, [this](auto &e) { OnEdit(); e.SetId(GetId()); ProcessEventLocally(e); + e.Skip(); }); text_ctrl->Bind(wxEVT_TEXT_ENTER, [this](auto &e) { OnEdit(); diff --git a/src/slic3r/GUI/WipeTowerDialog.cpp b/src/slic3r/GUI/WipeTowerDialog.cpp index e2f552e97f..4a7b7d5a80 100644 --- a/src/slic3r/GUI/WipeTowerDialog.cpp +++ b/src/slic3r/GUI/WipeTowerDialog.cpp @@ -97,7 +97,7 @@ wxBoxSizer* WipingDialog::create_btn_sizer(long flags) if (flags & wxRESET) { Button* calc_btn = new Button(this, _L("Auto-Calc")); calc_btn->SetMinSize(wxSize(FromDIP(75), FromDIP(24))); - calc_btn->SetCornerRadius(12); + calc_btn->SetCornerRadius(FromDIP(12)); calc_btn->SetBackgroundColor(ok_btn_bg); calc_btn->SetBorderColor(ok_btn_bd); calc_btn->SetTextColor(ok_btn_text); @@ -109,7 +109,7 @@ wxBoxSizer* WipingDialog::create_btn_sizer(long flags) if (flags & wxOK) { Button* ok_btn = new Button(this, _L("OK")); ok_btn->SetMinSize(BTN_SIZE); - ok_btn->SetCornerRadius(12); + ok_btn->SetCornerRadius(FromDIP(12)); ok_btn->SetBackgroundColor(ok_btn_bg); ok_btn->SetBorderColor(ok_btn_bd); ok_btn->SetTextColor(ok_btn_text); @@ -120,7 +120,7 @@ wxBoxSizer* WipingDialog::create_btn_sizer(long flags) if (flags & wxCANCEL) { Button* cancel_btn = new Button(this, _L("Cancel")); cancel_btn->SetMinSize(BTN_SIZE); - cancel_btn->SetCornerRadius(12); + cancel_btn->SetCornerRadius(FromDIP(12)); cancel_btn->SetBackgroundColor(cancel_btn_bg); cancel_btn->SetBorderColor(cancel_btn_bd_); cancel_btn->SetTextColor(cancel_btn_text); @@ -318,7 +318,7 @@ WipingPanel::WipingPanel(wxWindow* parent, const std::vector& matrix, con param_sizer->Add(m_flush_multiplier_ebox); param_sizer->AddStretchSpacer(1); - m_sizer_advanced->Add(param_sizer, 0, wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM, 10); + m_sizer_advanced->Add(param_sizer, 0, wxTOP | wxBOTTOM, 10); } m_page_advanced->Hide(); diff --git a/src/slic3r/GUI/wxMediaCtrl2.cpp b/src/slic3r/GUI/wxMediaCtrl2.cpp index 133163bb28..f79d2ee29f 100644 --- a/src/slic3r/GUI/wxMediaCtrl2.cpp +++ b/src/slic3r/GUI/wxMediaCtrl2.cpp @@ -1,5 +1,10 @@ #include "wxMediaCtrl2.h" #include "I18N.hpp" +#include "GUI_App.hpp" +#ifdef __WIN32__ +#include +#include +#endif wxMediaCtrl2::wxMediaCtrl2(wxWindow *parent) { @@ -24,11 +29,15 @@ void wxMediaCtrl2::Load(wxURI url) { #ifdef __WIN32__ if (m_imp == nullptr) { - auto res = wxMessageBox(_L("Windows Media Player is required for this task! Shall I take you to the guide page of 'Get Windows Media Player'?"), _L("Error"), wxOK | wxCANCEL); - if (res == wxOK) { - wxString url = "https://support.microsoft.com/en-au/windows/get-windows-media-player-81718e0d-cfce-25b1-aee3-94596b658287"; - wxExecute("cmd /c start " + url, wxEXEC_HIDE_CONSOLE); - } + Slic3r::GUI::wxGetApp().CallAfter([] { + auto res = wxMessageBox(_L("Windows Media Player is required for this task! Do you want to enable 'Windows Media Player' for your operation system?"), _L("Error"), wxOK | wxCANCEL); + if (res == wxOK) { + wxString url = IsWindows10OrGreater() + ? "ms-settings:optionalfeatures?activationSource=SMC-Article-14209" + : "https://support.microsoft.com/en-au/windows/get-windows-media-player-81718e0d-cfce-25b1-aee3-94596b658287"; + wxExecute("cmd /c start " + url, wxEXEC_HIDE_CONSOLE); + } + }); m_error = 2; wxMediaEvent event(wxEVT_MEDIA_STATECHANGED); event.SetId(GetId()); @@ -36,6 +45,23 @@ void wxMediaCtrl2::Load(wxURI url) wxPostEvent(this, event); return; } + { + wxRegKey key(wxRegKey::HKCR, "CLSID\\{233E64FB-2041-4A6C-AFAB-FF9BCF83E7AA}\\InProcServer32"); + wxString path = key.QueryDefaultValue(); + wxRegKey key2(wxRegKey::HKCR, "bambu"); + wxString clsid; + key2.QueryRawValue("Source Filter", clsid); + if (!wxFile::Exists(path) || clsid != L"{233E64FB-2041-4A6C-AFAB-FF9BCF83E7AA}") { + wxMessageBox(_L("Missing BambuSource component registered for media playing! Please re-install BambuStutio or seek after-sales help."), _L("Error"), + wxOK); + m_error = 3; + wxMediaEvent event(wxEVT_MEDIA_STATECHANGED); + event.SetId(GetId()); + event.SetEventObject(this); + wxPostEvent(this, event); + return; + } + } url = wxURI(url.BuildURI().append("&hwnd=").append( boost::lexical_cast(GetHandle()))); #endif diff --git a/src/slic3r/Utils/MacDarkMode.hpp b/src/slic3r/Utils/MacDarkMode.hpp index 92d355fcb2..7cdc4e6f73 100644 --- a/src/slic3r/Utils/MacDarkMode.hpp +++ b/src/slic3r/Utils/MacDarkMode.hpp @@ -13,7 +13,6 @@ extern void set_miniaturizable(void * window); void WKWebView_evaluateJavaScript(void * web, wxString const & script, void (*callback)(wxString const &)); void set_title_colour_after_set_title(); void initGestures(void * view, wxEvtHandler * handler); - #endif diff --git a/src/slic3r/Utils/MacDarkMode.mm b/src/slic3r/Utils/MacDarkMode.mm index 36889160f5..e28067fc13 100644 --- a/src/slic3r/Utils/MacDarkMode.mm +++ b/src/slic3r/Utils/MacDarkMode.mm @@ -1,4 +1,6 @@ #import "MacDarkMode.hpp" +#include "../GUI/Widgets/Label.hpp" + #include "wx/osx/core/cfstring.h" #import @@ -74,7 +76,7 @@ void WKWebView_evaluateJavaScript(void * web, wxString const & script, void (*ca } }]; } - + } } @@ -178,6 +180,17 @@ void WKWebView_evaluateJavaScript(void * web, wxString const & script, void (*ca @end +/* Font for wxTextCtrl */ + +@implementation NSTableHeaderCell (Font) + +- (NSFont*) font +{ + return Label::sysFont(13).OSXGetNSFont(); +} + +@end + /* remove focused border for wxTextCtrl */ @implementation NSTextField (FocusRing) diff --git a/src/slic3r/Utils/bambu_networking.hpp b/src/slic3r/Utils/bambu_networking.hpp index ffdef3f7ae..da246a10b7 100644 --- a/src/slic3r/Utils/bambu_networking.hpp +++ b/src/slic3r/Utils/bambu_networking.hpp @@ -36,7 +36,7 @@ namespace BBL { #define BAMBU_NETWORK_LIBRARY "bambu_networking" #define BAMBU_NETWORK_AGENT_NAME "bambu_network_agent" -#define BAMBU_NETWORK_AGENT_VERSION "01.01.00.10" +#define BAMBU_NETWORK_AGENT_VERSION "01.01.01.02" //iot preset type strings