From e9613e971d4b46462295c231c90d5cb165526cdf Mon Sep 17 00:00:00 2001 From: SoftFever Date: Fri, 17 Feb 2023 23:09:18 +0800 Subject: [PATCH] 1.4.5 features (#319) * Changes: Improve precise wall Port PS2.6 overhang slowdown feature Implement overhang fan for new overhang slowdown algo Add option to switch between classic/new overhang slowdown implementation Set Arachne as default engine Small adjustment of temp calibration range turn off small perimeter by default Small UI tweaks Change default top_surface_pattern to monotonic Fine tune jerk Signed-off-by: SoftFever * Disable optimizations for RelWithDebInfo Signed-off-by: SoftFever * fix an issue that max volumetirc/vfa calibration can't send to print Signed-off-by: SoftFever #322 * fix build errors Signed-off-by: SoftFever --------- Signed-off-by: SoftFever --- .clang-format | 166 +++ .github/workflows/build_win.yml | 12 +- CMakeLists.txt | 34 +- build_relwithdebinfo.bat | 11 +- resources/profiles/Anker.json | 2 +- .../Anker/process/fdm_process_common.json | 2 +- resources/profiles/Anycubic.json | 2 +- .../Anycubic/process/fdm_process_common.json | 2 +- resources/profiles/Artillery.json | 2 +- .../Artillery/process/fdm_process_common.json | 2 +- resources/profiles/Prusa.json | 2 +- .../Prusa/process/fdm_process_common.json | 2 +- resources/profiles/Ratrig.json | 2 +- .../process/fdm_process_ratrig_common.json | 2 +- resources/profiles/Snapmaker.json | 2 +- .../Snapmaker/process/fdm_process_common.json | 2 +- resources/profiles/Tronxy.json | 2 +- .../Tronxy/process/fdm_process_common.json | 2 +- .../process/fdm_process_tronxy_common.json | 2 +- resources/profiles/Voron.json | 2 +- .../process/fdm_process_voron_common.json | 2 +- src/libslic3r/AABBTreeIndirect.hpp | 58 + src/libslic3r/AABBTreeLines.hpp | 438 ++++-- src/libslic3r/CMakeLists.txt | 5 + src/libslic3r/ExPolygon.hpp | 85 ++ src/libslic3r/ExtrusionEntity.hpp | 7 +- src/libslic3r/GCode.cpp | 303 ++-- src/libslic3r/GCode.hpp | 6 + src/libslic3r/GCode/ExtrusionProcessor.hpp | 329 +++++ src/libslic3r/GCodeWriter.cpp | 11 +- src/libslic3r/GCodeWriter.hpp | 15 +- src/libslic3r/Line.hpp | 392 +++-- src/libslic3r/PerimeterGenerator.cpp | 24 +- src/libslic3r/Point.hpp | 12 + src/libslic3r/Preset.cpp | 3 +- src/libslic3r/PrincipalComponents2D.cpp | 140 ++ src/libslic3r/PrincipalComponents2D.hpp | 24 + src/libslic3r/Print.hpp | 1 - src/libslic3r/PrintConfig.cpp | 27 +- src/libslic3r/PrintConfig.hpp | 2 + src/libslic3r/PrintObject.cpp | 3 +- src/libslic3r/SupportSpotsGenerator.cpp | 1267 +++++++++++++++++ src/libslic3r/SupportSpotsGenerator.hpp | 159 +++ src/slic3r/GUI/ConfigManipulation.cpp | 24 +- src/slic3r/GUI/GUI_Factories.cpp | 2 +- src/slic3r/GUI/MainFrame.cpp | 4 +- src/slic3r/GUI/Tab.cpp | 10 +- src/slic3r/GUI/calib_dlg.cpp | 8 +- 48 files changed, 3182 insertions(+), 434 deletions(-) create mode 100644 .clang-format create mode 100644 src/libslic3r/GCode/ExtrusionProcessor.hpp create mode 100644 src/libslic3r/PrincipalComponents2D.cpp create mode 100644 src/libslic3r/PrincipalComponents2D.hpp create mode 100644 src/libslic3r/SupportSpotsGenerator.cpp create mode 100644 src/libslic3r/SupportSpotsGenerator.hpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000..e8e14f5c0c --- /dev/null +++ b/.clang-format @@ -0,0 +1,166 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: None +AlignConsecutiveAssignments: None +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: None +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortEnumsOnASingleLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: true +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +StatementAttributeLikeMacros: + - Q_EMIT +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentCaseLabels: false +IndentCaseBlocks: false +IndentGotoLabels: true +IndentPPDirectives: None +IndentExternBlock: AfterExternBlock +IndentRequires: false +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertTrailingCommas: None +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PenaltyIndentedWhitespace: 0 +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SortJavaStaticImport: Before +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceAroundPointerQualifiers: Default +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +BitFieldColonSpacing: Both +Standard: Latest +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE + - NS_SWIFT_NAME + - CF_SWIFT_NAME +... + diff --git a/.github/workflows/build_win.yml b/.github/workflows/build_win.yml index e504d511e7..5095ef3f5d 100644 --- a/.github/workflows/build_win.yml +++ b/.github/workflows/build_win.yml @@ -46,13 +46,13 @@ jobs: working-directory: ${{ github.workspace }} run: .\build_release.bat studio - - name: pack app - working-directory: ${{ github.workspace }}/build - shell: cmd - run: '"C:/Program Files/7-Zip/7z.exe" a -tzip BambuStudio-SoftFever_dev_build.zip ${{ github.workspace }}/build/BambuStudio-SoftFever' + # - name: pack app + # working-directory: ${{ github.workspace }}/build + # shell: cmd + # run: '"C:/Program Files/7-Zip/7z.exe" a -tzip BambuStudio-SoftFever_dev_build.zip ${{ github.workspace }}/build/BambuStudio-SoftFever' - name: Upload artifacts uses: actions/upload-artifact@v3 with: - name: BambuStudio-SoftFever_Win64 - path: ${{ github.workspace }}/build/BambuStudio-SoftFever_dev_build.zip \ No newline at end of file + name: BambuStudio-SoftFever_Win64_nightly + path: ${{ github.workspace }}/build/BambuStudio-SoftFever \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e40101517..ccb79bc223 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -313,20 +313,30 @@ if (SLIC3R_PROFILE) add_definitions(-DSLIC3R_PROFILE) endif () -# Disable optimization even with debugging on. -if (0) - message(STATUS "Perl compiled without optimization. Disabling optimization for the BambuStudio build.") - message("Old CMAKE_CXX_FLAGS_RELEASE: ${CMAKE_CXX_FLAGS_RELEASE}") - message("Old CMAKE_CXX_FLAGS_RELWITHDEBINFO: ${CMAKE_CXX_FLAGS_RELEASE}") - message("Old CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS_RELEASE}") - set(CMAKE_CXX_FLAGS_RELEASE "/MD /Od /Zi /EHsc /DWIN32 /DTBB_USE_ASSERT") - set(CMAKE_C_FLAGS_RELEASE "/MD /Od /Zi /DWIN32 /DTBB_USE_ASSERT") - set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "/MD /Od /Zi /EHsc /DWIN32 /DTBB_USE_ASSERT") - set(CMAKE_C_FLAGS_RELWITHDEBINFO "/MD /Od /Zi /DWIN32 /DTBB_USE_ASSERT") - set(CMAKE_CXX_FLAGS "/MD /Od /Zi /EHsc /DWIN32 /DTBB_USE_ASSERT") - set(CMAKE_C_FLAGS "/MD /Od /Zi /DWIN32 /DTBB_USE_ASSERT") +# Disable optimization for RelWithDebInfo +if(CMAKE_C_FLAGS_RELWITHDEBINFO MATCHES "/O2") + string(REGEX REPLACE "/O2" "/Od" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") endif() +if(CMAKE_CXX_FLAGS_RELWITHDEBINFO MATCHES "/O2") + string(REGEX REPLACE "/O2" "/Od" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") +endif() + +if(MSVC) + if(CMAKE_C_FLAGS_RELWITHDEBINFO MATCHES "/Ob1") + string(REGEX REPLACE "/Ob1" "/Ob0" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") + endif() + if(CMAKE_CXX_FLAGS_RELWITHDEBINFO MATCHES "/Ob1") + string(REGEX REPLACE "/Ob1" "/Ob0" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") + endif() + + set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} /RTC1") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /RTC1") +endif() + +list(REMOVE_DUPLICATES CMAKE_C_FLAGS_RELWITHDEBINFO) +list(REMOVE_DUPLICATES CMAKE_CXX_FLAGS_RELWITHDEBINFO) + # Find and configure boost if(SLIC3R_STATIC) # Use static boost libraries. diff --git a/build_relwithdebinfo.bat b/build_relwithdebinfo.bat index 6152424628..2f3e4887e0 100644 --- a/build_relwithdebinfo.bat +++ b/build_relwithdebinfo.bat @@ -3,12 +3,21 @@ cd deps mkdir build cd build set DEPS=%CD%/BambuStudio_dep +if "%1"=="studio" ( + GOTO :studio +) +echo "building deps.." cmake ../ -G "Visual Studio 16 2019" -DDESTDIR="%CD%/BambuStudio_dep" -DCMAKE_BUILD_TYPE=Release cmake --build . --config Release --target ALL_BUILD -- -m +if "%1"=="deps" exit /b 0 + +:studio +echo "building studio..." cd %WP% mkdir build cd build -cmake .. -G "Visual Studio 16 2019" -DBBL_RELEASE_TO_PUBLIC=1 -DCMAKE_PREFIX_PATH="%DEPS%/usr/local" -DCMAKE_INSTALL_PREFIX="./BambuStudio-SoftFever" -DCMAKE_BUILD_TYPE=RelWithDebInfo -DWIN10SDK_PATH="C:/Program Files (x86)/Windows Kits/10/Include/10.0.19041.0" + +cmake .. -G "Visual Studio 16 2019" -DBBL_RELEASE_TO_PUBLIC=1 -DCMAKE_PREFIX_PATH="%DEPS%/usr/local" -DCMAKE_INSTALL_PREFIX="./BambuStudio-SoftFever" -DCMAKE_BUILD_TYPE=Release -DWIN10SDK_PATH="C:/Program Files (x86)/Windows Kits/10/Include/10.0.19041.0" cmake --build . --config RelWithDebInfo --target ALL_BUILD -- -m @REM cmake --build . --target install --config RelWithDebInfo \ No newline at end of file diff --git a/resources/profiles/Anker.json b/resources/profiles/Anker.json index 65811614ff..1b00b79db8 100644 --- a/resources/profiles/Anker.json +++ b/resources/profiles/Anker.json @@ -1,6 +1,6 @@ { "name": "Anker", - "version": "01.04.00.01", + "version": "01.04.04.00", "force_update": "0", "description": "Anker configurations", "machine_model_list": [ diff --git a/resources/profiles/Anker/process/fdm_process_common.json b/resources/profiles/Anker/process/fdm_process_common.json index 7541bb41a0..189f78f5d7 100644 --- a/resources/profiles/Anker/process/fdm_process_common.json +++ b/resources/profiles/Anker/process/fdm_process_common.json @@ -81,7 +81,7 @@ "tree_support_wall_count": "0", "tree_support_with_infill": "0", "detect_thin_wall": "0", - "top_surface_pattern": "monotonicline", + "top_surface_pattern": "monotonic", "top_surface_line_width": "0.4", "top_shell_thickness": "0.8", "enable_prime_tower": "1", diff --git a/resources/profiles/Anycubic.json b/resources/profiles/Anycubic.json index 3744416477..1f64da3e12 100644 --- a/resources/profiles/Anycubic.json +++ b/resources/profiles/Anycubic.json @@ -1,6 +1,6 @@ { "name": "Anycubic", - "version": "01.04.00.01", + "version": "01.04.04.00", "force_update": "0", "description": "Anycubic configurations", "machine_model_list": [ diff --git a/resources/profiles/Anycubic/process/fdm_process_common.json b/resources/profiles/Anycubic/process/fdm_process_common.json index 7541bb41a0..189f78f5d7 100644 --- a/resources/profiles/Anycubic/process/fdm_process_common.json +++ b/resources/profiles/Anycubic/process/fdm_process_common.json @@ -81,7 +81,7 @@ "tree_support_wall_count": "0", "tree_support_with_infill": "0", "detect_thin_wall": "0", - "top_surface_pattern": "monotonicline", + "top_surface_pattern": "monotonic", "top_surface_line_width": "0.4", "top_shell_thickness": "0.8", "enable_prime_tower": "1", diff --git a/resources/profiles/Artillery.json b/resources/profiles/Artillery.json index 96803ef573..45d9681d53 100644 --- a/resources/profiles/Artillery.json +++ b/resources/profiles/Artillery.json @@ -1,6 +1,6 @@ { "name": "Artillery", - "version": "01.04.00.01", + "version": "01.04.04.00", "force_update": "0", "description": "Artillery configurations", "machine_model_list": [ diff --git a/resources/profiles/Artillery/process/fdm_process_common.json b/resources/profiles/Artillery/process/fdm_process_common.json index 7541bb41a0..189f78f5d7 100644 --- a/resources/profiles/Artillery/process/fdm_process_common.json +++ b/resources/profiles/Artillery/process/fdm_process_common.json @@ -81,7 +81,7 @@ "tree_support_wall_count": "0", "tree_support_with_infill": "0", "detect_thin_wall": "0", - "top_surface_pattern": "monotonicline", + "top_surface_pattern": "monotonic", "top_surface_line_width": "0.4", "top_shell_thickness": "0.8", "enable_prime_tower": "1", diff --git a/resources/profiles/Prusa.json b/resources/profiles/Prusa.json index b28bec9410..bf039a5b57 100644 --- a/resources/profiles/Prusa.json +++ b/resources/profiles/Prusa.json @@ -1,6 +1,6 @@ { "name": "Prusa", - "version": "01.04.00.11", + "version": "01.04.04.00", "force_update": "0", "description": "Prusa configurations", "machine_model_list": [ diff --git a/resources/profiles/Prusa/process/fdm_process_common.json b/resources/profiles/Prusa/process/fdm_process_common.json index 18068c061f..c8f387015d 100644 --- a/resources/profiles/Prusa/process/fdm_process_common.json +++ b/resources/profiles/Prusa/process/fdm_process_common.json @@ -81,7 +81,7 @@ "tree_support_wall_count": "0", "tree_support_with_infill": "0", "detect_thin_wall": "0", - "top_surface_pattern": "monotonicline", + "top_surface_pattern": "monotonic", "top_surface_line_width": "0.4", "top_shell_thickness": "0.8", "enable_prime_tower": "1", diff --git a/resources/profiles/Ratrig.json b/resources/profiles/Ratrig.json index e02e4201a7..0b24dbb458 100644 --- a/resources/profiles/Ratrig.json +++ b/resources/profiles/Ratrig.json @@ -1,6 +1,6 @@ { "name": "RatRig", - "version": "01.04.00.03", + "version": "01.04.04.00", "force_update": "0", "description": "RatRig configurations", "machine_model_list": [ diff --git a/resources/profiles/Ratrig/process/fdm_process_ratrig_common.json b/resources/profiles/Ratrig/process/fdm_process_ratrig_common.json index f80d37cbc0..60058dc715 100644 --- a/resources/profiles/Ratrig/process/fdm_process_ratrig_common.json +++ b/resources/profiles/Ratrig/process/fdm_process_ratrig_common.json @@ -83,7 +83,7 @@ "tree_support_branch_angle": "45", "tree_support_wall_count": "0", "detect_thin_wall": "0", - "top_surface_pattern": "monotonicline", + "top_surface_pattern": "monotonic", "top_surface_line_width": "0.4", "top_shell_layers": "3", "top_shell_thickness": "0.8", diff --git a/resources/profiles/Snapmaker.json b/resources/profiles/Snapmaker.json index 5f871cc6b4..48ee753b4b 100644 --- a/resources/profiles/Snapmaker.json +++ b/resources/profiles/Snapmaker.json @@ -1,6 +1,6 @@ { "name": "Snapmaker", - "version": "01.04.00.01", + "version": "01.04.04.01", "force_update": "0", "description": "Snapmaker configurations", "machine_model_list": [ diff --git a/resources/profiles/Snapmaker/process/fdm_process_common.json b/resources/profiles/Snapmaker/process/fdm_process_common.json index 7541bb41a0..189f78f5d7 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_common.json +++ b/resources/profiles/Snapmaker/process/fdm_process_common.json @@ -81,7 +81,7 @@ "tree_support_wall_count": "0", "tree_support_with_infill": "0", "detect_thin_wall": "0", - "top_surface_pattern": "monotonicline", + "top_surface_pattern": "monotonic", "top_surface_line_width": "0.4", "top_shell_thickness": "0.8", "enable_prime_tower": "1", diff --git a/resources/profiles/Tronxy.json b/resources/profiles/Tronxy.json index 9a6717b8c4..141f9a8097 100644 --- a/resources/profiles/Tronxy.json +++ b/resources/profiles/Tronxy.json @@ -1,6 +1,6 @@ { "name": "Tronxy", - "version": "01.04.10.0", + "version": "01.04.10.1", "force_update": "0", "description": "Tronxy configurations", "machine_model_list": [ diff --git a/resources/profiles/Tronxy/process/fdm_process_common.json b/resources/profiles/Tronxy/process/fdm_process_common.json index 76e86e4d92..9ef52a7cca 100644 --- a/resources/profiles/Tronxy/process/fdm_process_common.json +++ b/resources/profiles/Tronxy/process/fdm_process_common.json @@ -79,7 +79,7 @@ "tree_support_branch_angle": "45", "tree_support_wall_count": "0", "detect_thin_wall": "0", - "top_surface_pattern": "monotonicline", + "top_surface_pattern": "monotonic", "top_surface_line_width": "0.4", "top_shell_layers": "3", "top_shell_thickness": "0.8", diff --git a/resources/profiles/Tronxy/process/fdm_process_tronxy_common.json b/resources/profiles/Tronxy/process/fdm_process_tronxy_common.json index 8e140ab9ba..5c99e18c16 100644 --- a/resources/profiles/Tronxy/process/fdm_process_tronxy_common.json +++ b/resources/profiles/Tronxy/process/fdm_process_tronxy_common.json @@ -80,7 +80,7 @@ "tree_support_branch_angle": "45", "tree_support_wall_count": "0", "detect_thin_wall": "0", - "top_surface_pattern": "monotonicline", + "top_surface_pattern": "monotonic", "top_surface_line_width": "0.4", "top_shell_layers": "3", "top_shell_thickness": "0.8", diff --git a/resources/profiles/Voron.json b/resources/profiles/Voron.json index db6ae9233c..1b8cfc0337 100644 --- a/resources/profiles/Voron.json +++ b/resources/profiles/Voron.json @@ -1,6 +1,6 @@ { "name": "Voron", - "version": "01.04.01.00", + "version": "01.04.04.00", "force_update": "0", "description": "Voron configurations", "machine_model_list": [ diff --git a/resources/profiles/Voron/process/fdm_process_voron_common.json b/resources/profiles/Voron/process/fdm_process_voron_common.json index 6f195c1278..0f8e924e77 100644 --- a/resources/profiles/Voron/process/fdm_process_voron_common.json +++ b/resources/profiles/Voron/process/fdm_process_voron_common.json @@ -84,7 +84,7 @@ "tree_support_branch_angle": "45", "tree_support_wall_count": "0", "detect_thin_wall": "0", - "top_surface_pattern": "monotonicline", + "top_surface_pattern": "monotonic", "top_surface_line_width": "0.4", "top_shell_layers": "3", "top_shell_thickness": "0.8", diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp index 9b9c886ecc..3958c95f97 100644 --- a/src/libslic3r/AABBTreeIndirect.hpp +++ b/src/libslic3r/AABBTreeIndirect.hpp @@ -607,6 +607,37 @@ namespace detail { return up_sqr_d; } + template + static inline void indexed_primitives_within_distance_squared_recurisve(const IndexedPrimitivesDistancerType &distancer, + size_t node_idx, + Scalar squared_distance_limit, + std::vector &found_primitives_indices) + { + const auto &node = distancer.tree.node(node_idx); + assert(node.is_valid()); + if (node.is_leaf()) { + Scalar sqr_dist; + distancer.closest_point_to_origin(node.idx, sqr_dist); + if (sqr_dist < squared_distance_limit) { found_primitives_indices.push_back(node.idx); } + } else { + size_t left_node_idx = node_idx * 2 + 1; + size_t right_node_idx = left_node_idx + 1; + const auto &node_left = distancer.tree.node(left_node_idx); + const auto &node_right = distancer.tree.node(right_node_idx); + assert(node_left.is_valid()); + assert(node_right.is_valid()); + + if (node_left.bbox.squaredExteriorDistance(distancer.origin) < squared_distance_limit) { + indexed_primitives_within_distance_squared_recurisve(distancer, left_node_idx, squared_distance_limit, + found_primitives_indices); + } + if (node_right.bbox.squaredExteriorDistance(distancer.origin) < squared_distance_limit) { + indexed_primitives_within_distance_squared_recurisve(distancer, right_node_idx, squared_distance_limit, + found_primitives_indices); + } + } + } + } // namespace detail // Build a balanced AABB Tree over an indexed triangles set, balancing the tree @@ -793,6 +824,33 @@ inline bool is_any_triangle_in_radius( return hit_point.allFinite(); } +// Returns all triangles within the given radius limit +template +inline std::vector all_triangles_in_radius( + // Indexed triangle set - 3D vertices. + const std::vector &vertices, + // Indexed triangle set - triangular faces, references to vertices. + const std::vector &faces, + // AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices. + const TreeType &tree, + // Point to which the distances on the indexed triangle set is searched for. + const VectorType &point, + //Square of maximum distance in which triangles are searched for + typename VectorType::Scalar max_distance_squared) +{ + auto distancer = detail::IndexedTriangleSetDistancer + { vertices, faces, tree, point }; + + if(tree.empty()) + { + return {}; + } + + std::vector found_triangles{}; + detail::indexed_primitives_within_distance_squared_recurisve(distancer, size_t(0), max_distance_squared, found_triangles); + return found_triangles; +} + // Traverse the tree and return the index of an entity whose bounding box // contains a given point. Returns size_t(-1) when the point is outside. diff --git a/src/libslic3r/AABBTreeLines.hpp b/src/libslic3r/AABBTreeLines.hpp index 7b9595419a..5678a7b8c0 100644 --- a/src/libslic3r/AABBTreeLines.hpp +++ b/src/libslic3r/AABBTreeLines.hpp @@ -1,112 +1,372 @@ #ifndef SRC_LIBSLIC3R_AABBTREELINES_HPP_ #define SRC_LIBSLIC3R_AABBTREELINES_HPP_ -#include "libslic3r/Point.hpp" -#include "libslic3r/EdgeGrid.hpp" +#include "Point.hpp" +#include "Utils.hpp" +#include "libslic3r.h" #include "libslic3r/AABBTreeIndirect.hpp" #include "libslic3r/Line.hpp" +#include +#include +#include +#include namespace Slic3r { - namespace AABBTreeLines { -namespace detail { + namespace detail { -template -struct IndexedLinesDistancer { - using LineType = ALineType; - using TreeType = ATreeType; - using VectorType = AVectorType; - using ScalarType = typename VectorType::Scalar; + template + struct IndexedLinesDistancer { + using LineType = ALineType; + using TreeType = ATreeType; + using VectorType = AVectorType; + using ScalarType = typename VectorType::Scalar; - const std::vector &lines; - const TreeType &tree; + const std::vector& lines; + const TreeType& tree; - const VectorType origin; + const VectorType origin; - inline VectorType closest_point_to_origin(size_t primitive_index, - ScalarType &squared_distance) { - VectorType nearest_point; - const LineType &line = lines[primitive_index]; - squared_distance = line_alg::distance_to_squared(line, origin, &nearest_point); - return nearest_point; - } -}; + inline VectorType closest_point_to_origin(size_t primitive_index, ScalarType& squared_distance) const + { + Vec nearest_point; + const LineType& line = lines[primitive_index]; + squared_distance = line_alg::distance_to_squared(line, origin.template cast(), &nearest_point); + return nearest_point.template cast(); + } + }; -} - -// Build a balanced AABB Tree over a vector of float lines, balancing the tree -// on centroids of the lines. -// Epsilon is applied to the bounding boxes of the AABB Tree to cope with numeric inaccuracies -// during tree traversal. -template -inline AABBTreeIndirect::Tree<2, typename LineType::Scalar> build_aabb_tree_over_indexed_lines( - const std::vector &lines, - //FIXME do we want to apply an epsilon? - const float eps = 0) + // returns number of intersections of ray starting in ray_origin and following the specified coordinate line with lines in tree + // first number is hits in positive direction of ray, second number hits in negative direction. returns neagtive numbers when ray_origin is + // on some line exactly. + template + inline std::tuple coordinate_aligned_ray_hit_count(size_t node_idx, + const TreeType& tree, + const std::vector& lines, + const VectorType& ray_origin) { - using TreeType = AABBTreeIndirect::Tree<2, typename LineType::Scalar>; -// using CoordType = typename TreeType::CoordType; - using VectorType = typename TreeType::VectorType; - using BoundingBox = typename TreeType::BoundingBox; + static constexpr int other_coordinate = (coordinate + 1) % 2; + using Scalar = typename LineType::Scalar; + using Floating = typename std::conditional::value, Scalar, double>::type; + const auto& node = tree.node(node_idx); + assert(node.is_valid()); + if (node.is_leaf()) { + const LineType& line = lines[node.idx]; + if (ray_origin[other_coordinate] < std::min(line.a[other_coordinate], line.b[other_coordinate]) || ray_origin[other_coordinate] >= std::max(line.a[other_coordinate], line.b[other_coordinate])) { + // the second inequality is nonsharp for a reason + // without it, we may count contour border twice when the lines meet exactly at the spot of intersection. this prevents is + return { 0, 0 }; + } - struct InputType { - size_t idx() const { - return m_idx; - } - const BoundingBox& bbox() const { - return m_bbox; - } - const VectorType& centroid() const { - return m_centroid; + Scalar line_max = std::max(line.a[coordinate], line.b[coordinate]); + Scalar line_min = std::min(line.a[coordinate], line.b[coordinate]); + if (ray_origin[coordinate] > line_max) { + return { 1, 0 }; + } else if (ray_origin[coordinate] < line_min) { + return { 0, 1 }; + } else { + // find intersection of ray with line + // that is when ( line.a + t * (line.b - line.a) )[other_coordinate] == ray_origin[other_coordinate] + // t = ray_origin[oc] - line.a[oc] / (line.b[oc] - line.a[oc]); + // then we want to get value of intersection[ coordinate] + // val_c = line.a[c] + t * (line.b[c] - line.a[c]); + // Note that ray and line may overlap, when (line.b[oc] - line.a[oc]) is zero + // In that case, we return negative number + Floating distance_oc = line.b[other_coordinate] - line.a[other_coordinate]; + Floating t = (ray_origin[other_coordinate] - line.a[other_coordinate]) / distance_oc; + Floating val_c = line.a[coordinate] + t * (line.b[coordinate] - line.a[coordinate]); + if (ray_origin[coordinate] > val_c) { + return { 1, 0 }; + } else if (ray_origin[coordinate] < val_c) { + return { 0, 1 }; + } else { // ray origin is on boundary + return { -1, -1 }; + } + } + } else { + int intersections_above = 0; + int intersections_below = 0; + size_t left_node_idx = node_idx * 2 + 1; + size_t right_node_idx = left_node_idx + 1; + const auto& node_left = tree.node(left_node_idx); + const auto& node_right = tree.node(right_node_idx); + assert(node_left.is_valid()); + assert(node_right.is_valid()); + + if (node_left.bbox.min()[other_coordinate] <= ray_origin[other_coordinate] && node_left.bbox.max()[other_coordinate] >= ray_origin[other_coordinate]) { + auto [above, below] = coordinate_aligned_ray_hit_count(left_node_idx, tree, lines, + ray_origin); + if (above < 0 || below < 0) + return { -1, -1 }; + intersections_above += above; + intersections_below += below; + } + + if (node_right.bbox.min()[other_coordinate] <= ray_origin[other_coordinate] && node_right.bbox.max()[other_coordinate] >= ray_origin[other_coordinate]) { + auto [above, below] = coordinate_aligned_ray_hit_count(right_node_idx, tree, lines, + ray_origin); + if (above < 0 || below < 0) + return { -1, -1 }; + intersections_above += above; + intersections_below += below; + } + return { intersections_above, intersections_below }; + } } - size_t m_idx; - BoundingBox m_bbox; - VectorType m_centroid; + template + inline std::vector> get_intersections_with_line(size_t node_idx, + const TreeType& tree, + const std::vector& lines, + const LineType& line, + const typename TreeType::BoundingBox& line_bb) + { + const auto& node = tree.node(node_idx); + assert(node.is_valid()); + if (node.is_leaf()) { + VectorType intersection_pt; + if (line_alg::intersection(line, lines[node.idx], &intersection_pt)) { + return { std::pair(intersection_pt, node.idx) }; + } else { + return {}; + } + } else { + size_t left_node_idx = node_idx * 2 + 1; + size_t right_node_idx = left_node_idx + 1; + const auto& node_left = tree.node(left_node_idx); + const auto& node_right = tree.node(right_node_idx); + assert(node_left.is_valid()); + assert(node_right.is_valid()); + + std::vector> result; + + if (node_left.bbox.intersects(line_bb)) { + std::vector> intersections = get_intersections_with_line(left_node_idx, tree, lines, line, line_bb); + result.insert(result.end(), intersections.begin(), intersections.end()); + } + + if (node_right.bbox.intersects(line_bb)) { + std::vector> intersections = get_intersections_with_line(right_node_idx, tree, lines, line, line_bb); + result.insert(result.end(), intersections.begin(), intersections.end()); + } + + return result; + } + } + + } // namespace detail + + // Build a balanced AABB Tree over a vector of lines, balancing the tree + // on centroids of the lines. + // Epsilon is applied to the bounding boxes of the AABB Tree to cope with numeric inaccuracies + // during tree traversal. + template + inline AABBTreeIndirect::Tree<2, typename LineType::Scalar> build_aabb_tree_over_indexed_lines(const std::vector& lines) + { + using TreeType = AABBTreeIndirect::Tree<2, typename LineType::Scalar>; + // using CoordType = typename TreeType::CoordType; + using VectorType = typename TreeType::VectorType; + using BoundingBox = typename TreeType::BoundingBox; + + struct InputType { + size_t idx() const { return m_idx; } + const BoundingBox& bbox() const { return m_bbox; } + const VectorType& centroid() const { return m_centroid; } + + size_t m_idx; + BoundingBox m_bbox; + VectorType m_centroid; + }; + + std::vector input; + input.reserve(lines.size()); + for (size_t i = 0; i < lines.size(); ++i) { + const LineType& line = lines[i]; + InputType n; + n.m_idx = i; + n.m_centroid = (line.a + line.b) * 0.5; + n.m_bbox = BoundingBox(line.a, line.a); + n.m_bbox.extend(line.b); + input.emplace_back(n); + } + + TreeType out; + out.build(std::move(input)); + return out; + } + + // Finding a closest line, its closest point and squared distance to the closest point + // Returns squared distance to the closest point or -1 if the input is empty. + // or no closer point than max_sq_dist + template + inline typename VectorType::Scalar squared_distance_to_indexed_lines( + const std::vector& lines, + const TreeType& tree, + const VectorType& point, + size_t& hit_idx_out, + Eigen::PlainObjectBase& hit_point_out, + typename VectorType::Scalar max_sqr_dist = std::numeric_limits::infinity()) + { + using Scalar = typename VectorType::Scalar; + if (tree.empty()) + return Scalar(-1); + auto distancer = detail::IndexedLinesDistancer { lines, tree, point }; + return AABBTreeIndirect::detail::squared_distance_to_indexed_primitives_recursive(distancer, size_t(0), Scalar(0), max_sqr_dist, + hit_idx_out, hit_point_out); + } + + // Returns all lines within the given radius limit + template + inline std::vector all_lines_in_radius(const std::vector& lines, + const TreeType& tree, + const VectorType& point, + typename VectorType::Scalar max_distance_squared) + { + auto distancer = detail::IndexedLinesDistancer { lines, tree, point }; + + if (tree.empty()) { + return {}; + } + + std::vector found_lines {}; + AABBTreeIndirect::detail::indexed_primitives_within_distance_squared_recurisve(distancer, size_t(0), max_distance_squared, found_lines); + return found_lines; + } + + // return 1 if true, -1 if false, 0 for point on contour (or if cannot be determined) + template + inline int point_outside_closed_contours(const std::vector& lines, const TreeType& tree, const VectorType& point) + { + if (tree.empty()) { + return 1; + } + + auto [hits_above, hits_below] = detail::coordinate_aligned_ray_hit_count(0, tree, lines, point); + if (hits_above < 0 || hits_below < 0) { + return 0; + } else if (hits_above % 2 == 1 && hits_below % 2 == 1) { + return -1; + } else if (hits_above % 2 == 0 && hits_below % 2 == 0) { + return 1; + } else { // this should not happen with closed contours. lets check it in Y direction + auto [hits_above, hits_below] = detail::coordinate_aligned_ray_hit_count(0, tree, lines, point); + if (hits_above < 0 || hits_below < 0) { + return 0; + } else if (hits_above % 2 == 1 && hits_below % 2 == 1) { + return -1; + } else if (hits_above % 2 == 0 && hits_below % 2 == 0) { + return 1; + } else { // both results were unclear + return 0; + } + } + } + + template + inline std::vector> get_intersections_with_line(const std::vector& lines, + const TreeType& tree, + const LineType& line) + { + if (tree.empty()) { + return {}; + } + auto line_bb = typename TreeType::BoundingBox(line.a, line.a); + line_bb.extend(line.b); + + auto intersections = detail::get_intersections_with_line(0, tree, lines, line, line_bb); + if (sorted) { + using Floating = + typename std::conditional::value, typename LineType::Scalar, double>::type; + + std::vector>> points_with_sq_distance {}; + for (const auto& p : intersections) { + points_with_sq_distance.emplace_back((p.first - line.a).template cast().squaredNorm(), p); + } + std::sort(points_with_sq_distance.begin(), points_with_sq_distance.end(), + [](const std::pair>& left, + std::pair>& right) { return left.first < right.first; }); + for (size_t i = 0; i < points_with_sq_distance.size(); i++) { + intersections[i] = points_with_sq_distance[i].second; + } + } + + return intersections; + } + + template + class LinesDistancer { + public: + using Scalar = typename LineType::Scalar; + using Floating = typename std::conditional::value, Scalar, double>::type; + + private: + std::vector lines; + AABBTreeIndirect::Tree<2, Scalar> tree; + + public: + explicit LinesDistancer(const std::vector& lines) + : lines(lines) + { + tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(this->lines); + } + + explicit LinesDistancer(std::vector&& lines) + : lines(lines) + { + tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(this->lines); + } + + LinesDistancer() = default; + + // 1 true, -1 false, 0 cannot determine + int outside(const Vec<2, Scalar>& point) const { return point_outside_closed_contours(lines, tree, point); } + + // negative sign means inside + template + std::tuple> distance_from_lines_extra(const Vec<2, Scalar>& point) const + { + size_t nearest_line_index_out = size_t(-1); + Vec<2, Floating> nearest_point_out = Vec<2, Floating>::Zero(); + Vec<2, Floating> p = point.template cast(); + auto distance = AABBTreeLines::squared_distance_to_indexed_lines(lines, tree, p, nearest_line_index_out, nearest_point_out); + + if (distance < 0) { + return { std::numeric_limits::infinity(), nearest_line_index_out, nearest_point_out }; + } + distance = sqrt(distance); + + if (SIGNED_DISTANCE) { + distance *= outside(point); + } + + return { distance, nearest_line_index_out, nearest_point_out }; + } + + template + Floating distance_from_lines(const Vec<2, typename LineType::Scalar>& point) const + { + auto [dist, idx, np] = distance_from_lines_extra(point); + return dist; + } + + std::vector all_lines_in_radius(const Vec<2, typename LineType::Scalar>& point, Floating radius) + { + return all_lines_in_radius(this->lines, this->tree, point, radius * radius); + } + + template + std::vector, size_t>> intersections_with_line(const LineType& line) const + { + return get_intersections_with_line>(lines, tree, line); + } + + const LineType& get_line(size_t line_idx) const { return lines[line_idx]; } + + const std::vector& get_lines() const { return lines; } }; - std::vector input; - input.reserve(lines.size()); - const VectorType veps(eps, eps); - for (size_t i = 0; i < lines.size(); ++i) { - const LineType &line = lines[i]; - InputType n; - n.m_idx = i; - n.m_centroid = (line.a + line.b) * 0.5; - n.m_bbox = BoundingBox(line.a, line.a); - n.m_bbox.extend(line.b); - n.m_bbox.min() -= veps; - n.m_bbox.max() += veps; - input.emplace_back(n); - } - - TreeType out; - out.build(std::move(input)); - return out; -} - -// Finding a closest line, its closest point and squared distance to the closest point -// Returns squared distance to the closest point or -1 if the input is empty. -template -inline typename VectorType::Scalar squared_distance_to_indexed_lines( - const std::vector &lines, - const TreeType &tree, - const VectorType &point, - size_t &hit_idx_out, - Eigen::PlainObjectBase &hit_point_out) - { - using Scalar = typename VectorType::Scalar; - auto distancer = detail::IndexedLinesDistancer - { lines, tree, point }; - return tree.empty() ? - Scalar(-1) : - AABBTreeIndirect::detail::squared_distance_to_indexed_primitives_recursive(distancer, size_t(0), Scalar(0), - std::numeric_limits::infinity(), hit_idx_out, hit_point_out); -} - -} - } +} // namespace Slic3r::AABBTreeLines #endif /* SRC_LIBSLIC3R_AABBTREELINES_HPP_ */ diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 5605b36d43..9630bec411 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -134,6 +134,7 @@ set(lisbslic3r_sources GCode/GCodeProcessor.hpp GCode/AvoidCrossingPerimeters.cpp GCode/AvoidCrossingPerimeters.hpp + GCode/ExtrusionProcessor.hpp GCode.cpp GCode.hpp GCodeReader.cpp @@ -246,6 +247,10 @@ set(lisbslic3r_sources SlicingAdaptive.hpp SupportMaterial.cpp SupportMaterial.hpp + PrincipalComponents2D.cpp + PrincipalComponents2D.hpp + SupportSpotsGenerator.cpp + SupportSpotsGenerator.hpp TreeSupport.hpp TreeSupport.cpp MinimumSpanningTree.hpp diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index eac2d44a55..5b03008305 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -84,6 +84,25 @@ public: inline bool operator==(const ExPolygon &lhs, const ExPolygon &rhs) { return lhs.contour == rhs.contour && lhs.holes == rhs.holes; } inline bool operator!=(const ExPolygon &lhs, const ExPolygon &rhs) { return lhs.contour != rhs.contour || lhs.holes != rhs.holes; } +inline size_t count_points(const ExPolygons& expolys) +{ + size_t n_points = 0; + for (const auto& expoly : expolys) { + n_points += expoly.contour.points.size(); + for (const auto& hole : expoly.holes) + n_points += hole.points.size(); + } + return n_points; +} + +inline size_t count_points(const ExPolygon& expoly) +{ + size_t n_points = expoly.contour.points.size(); + for (const auto& hole : expoly.holes) + n_points += hole.points.size(); + return n_points; +} + // Count a nuber of polygons stored inside the vector of expolygons. // Useful for allocating space for polygons when converting expolygons to polygons. inline size_t number_polygons(const ExPolygons &expolys) @@ -130,6 +149,72 @@ inline Lines to_lines(const ExPolygons &src) } return lines; } +// Line is from point index(see to_points) to next point. +// Next point of last point in polygon is first polygon point. +inline Linesf to_linesf(const ExPolygons& src, uint32_t count_lines = 0) +{ + assert(count_lines == 0 || count_lines == count_points(src)); + if (count_lines == 0) + count_lines = count_points(src); + Linesf lines; + lines.reserve(count_lines); + Vec2d prev_pd; + auto to_lines = [&lines, &prev_pd](const Points& pts) { + assert(pts.size() >= 3); + if (pts.size() < 2) + return; + bool is_first = true; + for (const Point& p : pts) { + Vec2d pd = p.cast(); + if (is_first) + is_first = false; + else + lines.emplace_back(prev_pd, pd); + prev_pd = pd; + } + lines.emplace_back(prev_pd, pts.front().cast()); + }; + for (const ExPolygon& expoly : src) { + to_lines(expoly.contour.points); + for (const Polygon& hole : expoly.holes) + to_lines(hole.points); + } + assert(lines.size() == count_lines); + return lines; +} + +inline Linesf to_unscaled_linesf(const ExPolygons& src) +{ + Linesf lines; + lines.reserve(count_points(src)); + for (ExPolygons::const_iterator it_expoly = src.begin(); it_expoly != src.end(); ++it_expoly) { + for (size_t i = 0; i <= it_expoly->holes.size(); ++i) { + const Points& points = ((i == 0) ? it_expoly->contour : it_expoly->holes[i - 1]).points; + Vec2d unscaled_a = unscaled(points.front()); + Vec2d unscaled_b = unscaled_a; + for (Points::const_iterator it = points.begin() + 1; it != points.end(); ++it) { + unscaled_b = unscaled(*(it)); + lines.push_back(Linef(unscaled_a, unscaled_b)); + unscaled_a = unscaled_b; + } + lines.push_back(Linef(unscaled_a, unscaled(points.front()))); + } + } + return lines; +} + +inline Points to_points(const ExPolygons& src) +{ + Points points; + size_t count = count_points(src); + points.reserve(count); + for (const ExPolygon& expolygon : src) { + append(points, expolygon.contour.points); + for (const Polygon& hole : expolygon.holes) + append(points, hole.points); + } + return points; +} inline Polylines to_polylines(const ExPolygon &src) { diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index 2de0dd0444..62c822ffac 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -51,8 +51,7 @@ enum ExtrusionLoopRole { inline bool is_perimeter(ExtrusionRole role) { return role == erPerimeter - || role == erExternalPerimeter - || role == erOverhangPerimeter; + || role == erExternalPerimeter; } inline bool is_internal_perimeter(ExtrusionRole role) @@ -217,12 +216,12 @@ public: double total_volume() const override { return mm3_per_mm * unscale(length()); } void set_overhang_degree(int overhang) { - if (is_perimeter(m_role)) + if (is_perimeter(m_role) || is_bridge(m_role)) overhang_degree = (overhang < 0)?0:(overhang > 10 ? 10 : overhang); }; int get_overhang_degree() const { // only perimeter has overhang degree. Other return 0; - if (is_perimeter(m_role)) + if (is_perimeter(m_role) || is_bridge(m_role)) return overhang_degree; return 0; }; diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 663bba39d6..6ad5ac6415 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -15,6 +15,7 @@ #include "LocalesUtils.hpp" #include "libslic3r/format.hpp" #include "Time.hpp" +#include "GCode/ExtrusionProcessor.hpp" #include #include #include @@ -1661,7 +1662,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato if (m_config.default_jerk.value > 0) { double jerk = m_config.outer_wall_jerk.value; - gcode += m_writer.set_jerk_xy((unsigned int)floor(jerk + 0.5)); + gcode += m_writer.set_jerk_xy(jerk); } calib_pressure_advance pa_test(this); @@ -2461,6 +2462,7 @@ namespace Skirt { #if 0 // Prime just the first printing extruder. This is original Slic3r's implementation. skirt_loops_per_extruder_out[layer_tools.extruders.front()] = std::pair(0, print.config().skirt_loops.value); + skirt_loops_per_extruder_out[layer_tools.extruders.front()] = std::pair(0, print.config().skirt_loops.value); #else // Prime all extruders planned for this layer, see skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out); @@ -2616,14 +2618,10 @@ GCode::LayerResult GCode::process_layer( gcode += writer().set_temperature(print.calib_params().start - offset); } else if (print.calib_mode() == CalibMode::Calib_VFA_Tower) { auto _speed = print.calib_params().start + std::floor(print_z / 5.0) * print.calib_params().step; - DynamicConfig config; - config.set_key_value("outer_wall_speed", new ConfigOptionFloat(std::round(_speed))); - const_cast(&print)->print_regions_mutable()[0]->config_apply_only(config, { "outer_wall_speed" }); + m_calib_config.set_key_value("outer_wall_speed", new ConfigOptionFloat(std::round(_speed))); } else if (print.calib_mode() == CalibMode::Calib_Vol_speed_Tower) { auto _speed = print.calib_params().start + print_z * print.calib_params().step; - DynamicConfig config; - config.set_key_value("outer_wall_speed", new ConfigOptionFloat(std::round(_speed))); - const_cast(&print)->print_regions_mutable()[0]->config_apply_only(config, { "outer_wall_speed" }); + m_calib_config.set_key_value("outer_wall_speed", new ConfigOptionFloat(std::round(_speed))); } //BBS @@ -2636,7 +2634,7 @@ GCode::LayerResult GCode::process_layer( if (m_config.default_jerk.value > 0 && m_config.initial_layer_jerk.value > 0) { double jerk = m_config.initial_layer_jerk.value; - gcode += m_writer.set_jerk_xy((unsigned int)floor(jerk + 0.5)); + gcode += m_writer.set_jerk_xy(jerk); } } @@ -2665,7 +2663,7 @@ GCode::LayerResult GCode::process_layer( if (m_config.default_jerk.value > 0 && m_config.initial_layer_jerk.value > 0) { double jerk = m_config.default_jerk.value; - gcode += m_writer.set_jerk_xy((unsigned int)floor(jerk + 0.5)); + gcode += m_writer.set_jerk_xy(jerk); } // Transition from 1st to 2nd layer. Adjust nozzle temperatures as prescribed by the nozzle dependent @@ -2699,6 +2697,11 @@ GCode::LayerResult GCode::process_layer( Skirt::make_skirt_loops_per_extruder_1st_layer(print, layer_tools, m_skirt_done) : Skirt::make_skirt_loops_per_extruder_other_layers(print, layer_tools, m_skirt_done); + for (const auto& layer_to_print : layers) { + m_extrusion_quality_estimator.prepare_for_new_layer(layer_to_print.object_layer); + } + + // Group extrusions by an extruder, then by an object, an island and a region. std::map> by_extruder; bool is_anything_overridden = const_cast(layer_tools).wiping_extrusions().is_anything_overridden(); @@ -3084,6 +3087,8 @@ GCode::LayerResult GCode::process_layer( if (!m_config.use_relative_e_distances) gcode += m_writer.reset_e(true); } + m_extrusion_quality_estimator.set_current_object(&instance_to_print.print_object); + // When starting a new object, use the external motion planner for the first travel move. const Point &offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift; std::pair this_object_copy(&instance_to_print.print_object, offset); @@ -3450,7 +3455,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou if (m_config.default_acceleration.value > 0) gcode += m_writer.set_acceleration((unsigned int)(m_config.default_acceleration.value + 0.5)); if (m_config.default_jerk.value > 0) - gcode += m_writer.set_jerk_xy((unsigned int)(m_config.default_jerk.value + 0.5)); + gcode += m_writer.set_jerk_xy(m_config.default_jerk.value); } return gcode; } @@ -3480,7 +3485,7 @@ std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string // reset acceleration gcode += m_writer.set_acceleration((unsigned int)floor(m_config.default_acceleration.value + 0.5)); if(m_config.default_jerk.value > 0) - gcode += m_writer.set_jerk_xy((unsigned int)floor(m_config.default_jerk.value + 0.5)); + gcode += m_writer.set_jerk_xy(m_config.default_jerk.value); } return gcode; } @@ -3511,7 +3516,7 @@ std::string GCode::extrude_path(ExtrusionPath path, std::string description, dou // reset acceleration gcode += m_writer.set_acceleration((unsigned int)floor(m_config.default_acceleration.value + 0.5)); if(m_config.default_jerk.value > 0) - gcode += m_writer.set_jerk_xy((unsigned int)floor(m_config.default_jerk.value + 0.5)); + gcode += m_writer.set_jerk_xy(m_config.default_jerk.value); } return gcode; @@ -3698,7 +3703,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, // compensate retraction gcode += this->unretract(); - + m_config.apply(m_calib_config); // adjust acceleration if (m_config.default_acceleration.value > 0) { double acceleration; @@ -3739,7 +3744,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, else { jerk = m_config.default_jerk.value; } - gcode += m_writer.set_jerk_xy((unsigned int)floor(jerk + 0.5)); + gcode += m_writer.set_jerk_xy(jerk); } // calculate extrusion length per distance unit @@ -3752,13 +3757,15 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, int overhang_degree = path.get_overhang_degree(); if (path.role() == erPerimeter) { speed = m_config.get_abs_value("inner_wall_speed"); - if (m_config.enable_overhang_speed.value && overhang_degree > 0 && overhang_degree <= 5) { + if (m_config.overhang_speed_classic.value && m_config.enable_overhang_speed.value && overhang_degree > 0 && + overhang_degree <= 5) { double new_speed = m_config.get_abs_value(overhang_speed_key_map[overhang_degree].c_str()); speed = new_speed == 0.0 ? speed : new_speed; } } else if (path.role() == erExternalPerimeter) { speed = m_config.get_abs_value("outer_wall_speed"); - if (m_config.enable_overhang_speed.value && overhang_degree > 0 && overhang_degree <= 5) { + if (m_config.overhang_speed_classic.value && m_config.enable_overhang_speed.value && + overhang_degree > 0 && overhang_degree <= 5) { double new_speed = m_config.get_abs_value(overhang_speed_key_map[overhang_degree].c_str()); speed = new_speed == 0.0 ? speed : new_speed; } @@ -3812,6 +3819,35 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, EXTRUDER_CONFIG(filament_max_volumetric_speed) / _mm3_per_mm ); } + + + bool variable_speed = false; + std::vector new_points {}; + + if (m_config.enable_overhang_speed && !m_config.overhang_speed_classic && !this->on_first_layer() && + (is_bridge(path.role()) || is_perimeter(path.role()))) { + ConfigOptionPercents overhang_overlap_levels({75, 50, 25, 5}); + ConfigOptionFloatsOrPercents dynamic_overhang_speeds( + {(m_config.get_abs_value("overhang_1_4_speed") < 0.5) + ? FloatOrPercent{100, true} + : FloatOrPercent{m_config.get_abs_value("overhang_1_4_speed"), false}, + (m_config.get_abs_value("overhang_2_4_speed") < 0.5) + ? FloatOrPercent{100, true} + : FloatOrPercent{m_config.get_abs_value("overhang_2_4_speed"), false}, + (m_config.get_abs_value("overhang_3_4_speed") < 0.5) + ? FloatOrPercent{100, true} + : FloatOrPercent{m_config.get_abs_value("overhang_3_4_speed"), false}, + (m_config.get_abs_value("overhang_4_4_speed") < 0.5) + ? FloatOrPercent{100, true} + : FloatOrPercent{m_config.get_abs_value("overhang_4_4_speed"), false}}); + + new_points = m_extrusion_quality_estimator.estimate_extrusion_quality( + path, overhang_overlap_levels, dynamic_overhang_speeds, m_config.get_abs_value("outer_wall_speed"), speed); + + variable_speed = std::any_of(new_points.begin(), new_points.end(), + [speed](const ProcessedPoint &p) { return p.speed != speed; }); + } + double F = speed * 60; // convert mm/sec to mm/min // extrude arc or line @@ -3862,108 +3898,150 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, gcode += buf; } + auto overhang_fan_threshold = EXTRUDER_CONFIG(overhang_fan_threshold); + auto enable_overhang_bridge_fan = EXTRUDER_CONFIG(enable_overhang_bridge_fan); + + + // { "0%", Overhang_threshold_none }, + // { "5%", Overhang_threshold_1_4 }, + // { "25%", Overhang_threshold_2_4 }, + // { "50%", Overhang_threshold_3_4 }, + // { "75%", Overhang_threshold_4_4 }, + // { "95%", Overhang_threshold_bridge } + auto check_overhang_fan = [&overhang_fan_threshold](float overlap) { + switch (overhang_fan_threshold) { + case (int)Overhang_threshold_1_4: + return overlap <= 0.95f; + break; + case (int)Overhang_threshold_2_4: + return overlap <= 0.75f; + break; + case (int)Overhang_threshold_3_4: + return overlap <= 0.5f; + break; + case (int)Overhang_threshold_4_4: + return overlap <= 0.25f; + break; + case (int)Overhang_threshold_bridge: + return overlap <= 0.05f; + break; + case (int)Overhang_threshold_none: + default: + return true; + } + }; + std::string comment; if (m_enable_cooling_markers) { - if (EXTRUDER_CONFIG(enable_overhang_bridge_fan)) { - //BBS: Overhang_threshold_none means Overhang_threshold_1_4 and forcing cooling for all external perimeter - int overhang_threshold = EXTRUDER_CONFIG(overhang_fan_threshold) == Overhang_threshold_none ? - Overhang_threshold_none : EXTRUDER_CONFIG(overhang_fan_threshold) - 1; - if ((EXTRUDER_CONFIG(overhang_fan_threshold) == Overhang_threshold_none && path.role() == erExternalPerimeter)) { - gcode += ";_OVERHANG_FAN_START\n"; - comment = ";_EXTRUDE_SET_SPEED"; - } else if (path.get_overhang_degree() > overhang_threshold || - is_bridge(path.role())) - gcode += ";_OVERHANG_FAN_START\n"; - else - comment = ";_EXTRUDE_SET_SPEED"; - } - else { - comment = ";_EXTRUDE_SET_SPEED"; - } - + comment = ";_EXTRUDE_SET_SPEED"; if (path.role() == erExternalPerimeter) comment += ";_EXTERNAL_PERIMETER"; } - - // F is mm per minute. - gcode += m_writer.set_speed(F, "", comment); - double path_length = 0.; - { - std::string comment = GCodeWriter::full_gcode_comment ? description : ""; - //BBS: use G1 if not enable arc fitting or has no arc fitting result or in spiral_mode mode - //Attention: G2 and G3 is not supported in spiral_mode mode - if (!m_config.enable_arc_fitting || - path.polyline.fitting_result.empty() || - m_config.spiral_mode) { - for (const Line& line : path.polyline.lines()) { - const double line_length = line.length() * SCALING_FACTOR; - path_length += line_length; - gcode += m_writer.extrude_to_xy( - this->point_to_gcode(line.b), - e_per_mm * line_length, - comment, path.is_force_no_extrusion()); - } - } else { - // BBS: start to generate gcode from arc fitting data which includes line and arc - const std::vector& fitting_result = path.polyline.fitting_result; - for (size_t fitting_index = 0; fitting_index < fitting_result.size(); fitting_index++) { - switch (fitting_result[fitting_index].path_type) { - case EMovePathType::Linear_move: { - size_t start_index = fitting_result[fitting_index].start_point_index; - size_t end_index = fitting_result[fitting_index].end_point_index; - for (size_t point_index = start_index + 1; point_index < end_index + 1; point_index++) { - const Line line = Line(path.polyline.points[point_index - 1], path.polyline.points[point_index]); - const double line_length = line.length() * SCALING_FACTOR; - path_length += line_length; - gcode += m_writer.extrude_to_xy( - this->point_to_gcode(line.b), - e_per_mm * line_length, - comment, path.is_force_no_extrusion()); - } - break; + bool is_overhang_fan_on = false; + if (!variable_speed) { + // F is mm per minute. + gcode += m_writer.set_speed(F, "", comment); + double path_length = 0.; + { + if (m_enable_cooling_markers && enable_overhang_bridge_fan && m_config.overhang_speed_classic) { + // BBS: Overhang_threshold_none means Overhang_threshold_1_4 and forcing cooling for all external + // perimeter + int overhang_threshold = overhang_fan_threshold == Overhang_threshold_none ? Overhang_threshold_none + : overhang_fan_threshold - 1; + if ((overhang_fan_threshold == Overhang_threshold_none && is_perimeter(path.role())) || + (path.get_overhang_degree() > overhang_threshold || is_bridge(path.role()))) { + gcode += ";_OVERHANG_FAN_START\n"; + is_overhang_fan_on = true; } - case EMovePathType::Arc_move_cw: - case EMovePathType::Arc_move_ccw: { - const ArcSegment& arc = fitting_result[fitting_index].arc_data; - const double arc_length = fitting_result[fitting_index].arc_data.length * SCALING_FACTOR; - const Vec2d center_offset = this->point_to_gcode(arc.center) - this->point_to_gcode(arc.start_point); - path_length += arc_length; - gcode += m_writer.extrude_arc_to_xy( + } + // BBS: use G1 if not enable arc fitting or has no arc fitting result or in spiral_mode mode + // Attention: G2 and G3 is not supported in spiral_mode mode + if (!m_config.enable_arc_fitting || path.polyline.fitting_result.empty() || m_config.spiral_mode) { + for (const Line& line : path.polyline.lines()) { + const double line_length = line.length() * SCALING_FACTOR; + path_length += line_length; + gcode += m_writer.extrude_to_xy( + this->point_to_gcode(line.b), + e_per_mm * line_length, + GCodeWriter::full_gcode_comment ? description : "", path.is_force_no_extrusion()); + } + } else { + // BBS: start to generate gcode from arc fitting data which includes line and arc + const std::vector& fitting_result = path.polyline.fitting_result; + for (size_t fitting_index = 0; fitting_index < fitting_result.size(); fitting_index++) { + switch (fitting_result[fitting_index].path_type) { + case EMovePathType::Linear_move: { + size_t start_index = fitting_result[fitting_index].start_point_index; + size_t end_index = fitting_result[fitting_index].end_point_index; + for (size_t point_index = start_index + 1; point_index < end_index + 1; point_index++) { + const Line line = Line(path.polyline.points[point_index - 1], path.polyline.points[point_index]); + const double line_length = line.length() * SCALING_FACTOR; + path_length += line_length; + gcode += m_writer.extrude_to_xy( + this->point_to_gcode(line.b), + e_per_mm * line_length, + GCodeWriter::full_gcode_comment ? description : "", path.is_force_no_extrusion()); + } + break; + } + case EMovePathType::Arc_move_cw: + case EMovePathType::Arc_move_ccw: { + const ArcSegment& arc = fitting_result[fitting_index].arc_data; + const double arc_length = fitting_result[fitting_index].arc_data.length * SCALING_FACTOR; + const Vec2d center_offset = this->point_to_gcode(arc.center) - this->point_to_gcode(arc.start_point); + path_length += arc_length; + gcode += m_writer.extrude_arc_to_xy( this->point_to_gcode(arc.end_point), center_offset, e_per_mm * arc_length, arc.direction == ArcDirection::Arc_Dir_CCW, - comment, path.is_force_no_extrusion()); - break; + GCodeWriter::full_gcode_comment ? description : "", path.is_force_no_extrusion()); + break; + } + default: + // BBS: should never happen that a empty path_type has been stored + assert(0); + break; + } } - default: - //BBS: should never happen that a empty path_type has been stored - assert(0); - break; + } + if (is_overhang_fan_on) { + is_overhang_fan_on = false; + gcode += ";_OVERHANG_FAN_END\n"; + } + } + } else { + double last_set_speed = std::max((float)EXTRUDER_CONFIG(slow_down_min_speed), new_points[0].speed) * 60.0; + + gcode += m_writer.set_speed(last_set_speed, "", comment); + Vec2d prev = this->point_to_gcode_quantized(new_points[0].p); + for (size_t i = 1; i < new_points.size(); i++) { + const ProcessedPoint &processed_point = new_points[i]; + Vec2d p = this->point_to_gcode_quantized(processed_point.p); + const double line_length = (p - prev).norm(); + if (m_enable_cooling_markers && enable_overhang_bridge_fan) { + if (is_bridge(path.role()) || check_overhang_fan(new_points[i - 1].overlap)) { + gcode += ";_OVERHANG_FAN_START\n"; + is_overhang_fan_on = true; } } + gcode += + m_writer.extrude_to_xy(p, e_per_mm * line_length, GCodeWriter::full_gcode_comment ? description : ""); + if (is_overhang_fan_on) { + is_overhang_fan_on = false; + gcode += ";_OVERHANG_FAN_END\n"; + } + prev = p; + double new_speed = std::max((float)EXTRUDER_CONFIG(slow_down_min_speed), processed_point.speed) * 60.0; + if (last_set_speed != new_speed) { + gcode += m_writer.set_speed(new_speed, "", comment); + last_set_speed = new_speed; + } } } if (m_enable_cooling_markers) { - if (EXTRUDER_CONFIG(enable_overhang_bridge_fan)) { - //BBS: Overhang_threshold_none means Overhang_threshold_1_4 and forcing cooling for all external perimeter - int overhang_threshold = EXTRUDER_CONFIG(overhang_fan_threshold) == Overhang_threshold_none ? - Overhang_threshold_none : EXTRUDER_CONFIG(overhang_fan_threshold) - 1; - if ((EXTRUDER_CONFIG(overhang_fan_threshold) == Overhang_threshold_none && path.role() == erExternalPerimeter)) { - gcode += ";_EXTRUDE_END\n"; - gcode += ";_OVERHANG_FAN_END\n"; - - } else if (path.get_overhang_degree() > overhang_threshold || - is_bridge(path.role())) - gcode += ";_OVERHANG_FAN_END\n"; - else - gcode += ";_EXTRUDE_END\n"; - } - else { - gcode += ";_EXTRUDE_END\n"; - } + gcode += ";_EXTRUDE_END\n"; } - this->set_last_pos(path.last_point()); return gcode; } @@ -3986,22 +4064,22 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string // SoftFever if (this->on_first_layer()) { - if(m_config.default_acceleration.value > 0) - { - auto jerk = (unsigned int)floor(m_config.initial_layer_jerk.value + 0.5); + if (m_config.default_acceleration.value > 0) { auto accel = (unsigned int)floor(m_config.initial_layer_acceleration.value + 0.5); - if(jerk > 0) - gcode += m_writer.set_jerk_xy(jerk); - - if(accel > 0) + if (accel > 0) gcode += m_writer.set_acceleration(accel); } + if (m_config.default_jerk.value > 0) { + auto jerk = m_config.initial_layer_jerk.value; + if (jerk > 0) + gcode += m_writer.set_jerk_xy(jerk); + } } else { if(m_config.default_jerk.value > 0) { - auto jerk = (unsigned int)floor(m_config.travel_jerk.value + 0.5); + auto jerk = m_config.travel_jerk.value; auto accel = (unsigned int)floor(m_config.travel_acceleration.value + 0.5); if(jerk > 0) gcode += m_writer.set_jerk_xy(jerk); @@ -4337,6 +4415,13 @@ Point GCode::gcode_to_point(const Vec2d &point) const scale_(point(1) - m_origin(1) + extruder_offset(1))); } +Vec2d GCode::point_to_gcode_quantized(const Point& point) const +{ + Vec2d p = this->point_to_gcode(point); + return { GCodeFormatter::quantize_xyzf(p.x()), GCodeFormatter::quantize_xyzf(p.y()) }; +} + + // Goes through by_region std::vector and returns reference to a subvector of entities, that are to be printed // during infill/perimeter wiping, or normally (depends on wiping_entities parameter) // Fills in by_region_per_copy_cache and returns its reference. diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 52ae3b8d5e..fca73b1782 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -18,6 +18,7 @@ #include "EdgeGrid.hpp" #include "GCode/ThumbnailData.hpp" #include "libslic3r/ObjectID.hpp" +#include "GCode/ExtrusionProcessor.hpp" #include #include @@ -178,6 +179,7 @@ public: const Point& last_pos() const { return m_last_pos; } Vec2d point_to_gcode(const Point &point) const; Point gcode_to_point(const Vec2d &point) const; + Vec2d point_to_gcode_quantized(const Point& point) const; const FullPrintConfig &config() const { return m_config; } const Layer* layer() const { return m_layer; } GCodeWriter& writer() { return m_writer; } @@ -412,11 +414,15 @@ private: // Cache for custom seam enforcers/blockers for each layer. SeamPlacer m_seam_placer; + ExtrusionQualityEstimator m_extrusion_quality_estimator; + + /* Origin of print coordinates expressed in unscaled G-code coordinates. This affects the input arguments supplied to the extrude*() and travel_to() methods. */ Vec2d m_origin; FullPrintConfig m_config; + DynamicConfig m_calib_config; // scaled G-code resolution double m_scaled_resolution; GCodeWriter m_writer; diff --git a/src/libslic3r/GCode/ExtrusionProcessor.hpp b/src/libslic3r/GCode/ExtrusionProcessor.hpp new file mode 100644 index 0000000000..bf219600d8 --- /dev/null +++ b/src/libslic3r/GCode/ExtrusionProcessor.hpp @@ -0,0 +1,329 @@ +#ifndef slic3r_ExtrusionProcessor_hpp_ +#define slic3r_ExtrusionProcessor_hpp_ + +#include "../AABBTreeLines.hpp" +//#include "../SupportSpotsGenerator.hpp" +#include "../libslic3r.h" +#include "../ExtrusionEntity.hpp" +#include "../Layer.hpp" +#include "../Point.hpp" +#include "../SVG.hpp" +#include "../BoundingBox.hpp" +#include "../Polygon.hpp" +#include "../ClipperUtils.hpp" +#include "../Flow.hpp" +#include "../Config.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Slic3r { + +class SlidingWindowCurvatureAccumulator +{ + float window_size; + float total_distance = 0; // accumulated distance + float total_curvature = 0; // accumulated signed ccw angles + deque distances; + deque angles; + +public: + SlidingWindowCurvatureAccumulator(float window_size) : window_size(window_size) {} + + void add_point(float distance, float angle) + { + total_distance += distance; + total_curvature += angle; + distances.push_back(distance); + angles.push_back(angle); + + while (distances.size() > 1 && total_distance > window_size) { + total_distance -= distances.front(); + total_curvature -= angles.front(); + distances.pop_front(); + angles.pop_front(); + } + } + + float get_curvature() const + { + return total_curvature / window_size; + } + + void reset() + { + total_curvature = 0; + total_distance = 0; + distances.clear(); + angles.clear(); + } +}; + +class CurvatureEstimator +{ + static const size_t sliders_count = 3; + SlidingWindowCurvatureAccumulator sliders[sliders_count] = {{1.0},{4.0}, {10.0}}; + +public: + void add_point(float distance, float angle) + { + if (distance < EPSILON) + return; + for (SlidingWindowCurvatureAccumulator &slider : sliders) { + slider.add_point(distance, angle); + } + } + float get_curvature() + { + float max_curvature = 0.0f; + for (const SlidingWindowCurvatureAccumulator &slider : sliders) { + if (abs(slider.get_curvature()) > abs(max_curvature)) { + max_curvature = slider.get_curvature(); + } + } + return max_curvature; + } + void reset() + { + for (SlidingWindowCurvatureAccumulator &slider : sliders) { + slider.reset(); + } + } +}; + +struct ExtendedPoint +{ + ExtendedPoint(Vec2d position, float distance = 0.0, size_t nearest_prev_layer_line = size_t(-1), float curvature = 0.0) + : position(position), distance(distance), nearest_prev_layer_line(nearest_prev_layer_line), curvature(curvature) + {} + + Vec2d position; + float distance; + size_t nearest_prev_layer_line; + float curvature; +}; + +template +std::vector estimate_points_properties(const std::vector

&input_points, + const AABBTreeLines::LinesDistancer &unscaled_prev_layer, + float flow_width, + float max_line_length = -1.0f) +{ + using AABBScalar = typename AABBTreeLines::LinesDistancer::Scalar; + if (input_points.empty()) + return {}; + float boundary_offset = PREV_LAYER_BOUNDARY_OFFSET ? 0.5 * flow_width : 0.0f; + CurvatureEstimator cestim; + auto maybe_unscale = [](const P &p) { return SCALED_INPUT ? unscaled(p) : p.template cast(); }; + + std::vector points; + points.reserve(input_points.size() * (ADD_INTERSECTIONS ? 1.5 : 1)); + + { + ExtendedPoint start_point{maybe_unscale(input_points.front())}; + auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra(start_point.position.cast()); + start_point.distance = distance + boundary_offset; + start_point.nearest_prev_layer_line = nearest_line; + points.push_back(start_point); + } + for (size_t i = 1; i < input_points.size(); i++) { + ExtendedPoint next_point{maybe_unscale(input_points[i])}; + auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra(next_point.position.cast()); + next_point.distance = distance + boundary_offset; + next_point.nearest_prev_layer_line = nearest_line; + + if (ADD_INTERSECTIONS && + ((points.back().distance > boundary_offset + EPSILON) != (next_point.distance > boundary_offset + EPSILON))) { + const ExtendedPoint &prev_point = points.back(); + auto intersections = unscaled_prev_layer.template intersections_with_line(L{prev_point.position.cast(), next_point.position.cast()}); + for (const auto &intersection : intersections) { + points.emplace_back(intersection.first.template cast(), boundary_offset, intersection.second); + } + } + points.push_back(next_point); + } + + if (PREV_LAYER_BOUNDARY_OFFSET && ADD_INTERSECTIONS) { + std::vector new_points; + new_points.reserve(points.size()*2); + new_points.push_back(points.front()); + for (int point_idx = 0; point_idx < int(points.size()) - 1; ++point_idx) { + const ExtendedPoint &curr = points[point_idx]; + const ExtendedPoint &next = points[point_idx + 1]; + + if ((curr.distance > 0 && curr.distance < boundary_offset + 2.0f) || + (next.distance > 0 && next.distance < boundary_offset + 2.0f)) { + double line_len = (next.position - curr.position).norm(); + if (line_len > 4.0f) { + double a0 = std::clamp((curr.distance + 2 * boundary_offset) / line_len, 0.0, 1.0); + double a1 = std::clamp(1.0f - (next.distance + 2 * boundary_offset) / line_len, 0.0, 1.0); + double t0 = std::min(a0, a1); + double t1 = std::max(a0, a1); + + if (t0 < 1.0) { + auto p0 = curr.position + t0 * (next.position - curr.position); + auto [p0_dist, p0_near_l, p0_x] = unscaled_prev_layer.template distance_from_lines_extra(p0.cast()); + new_points.push_back(ExtendedPoint{p0, float(p0_dist + boundary_offset), p0_near_l}); + } + if (t1 > 0.0) { + auto p1 = curr.position + t1 * (next.position - curr.position); + auto [p1_dist, p1_near_l, p1_x] = unscaled_prev_layer.template distance_from_lines_extra(p1.cast()); + new_points.push_back(ExtendedPoint{p1, float(p1_dist + boundary_offset), p1_near_l}); + } + } + } + new_points.push_back(next); + } + points = new_points; + } + + if (max_line_length > 0) { + std::vector new_points; + new_points.reserve(points.size()*2); + { + for (size_t i = 0; i + 1 < points.size(); i++) { + const ExtendedPoint &curr = points[i]; + const ExtendedPoint &next = points[i + 1]; + new_points.push_back(curr); + double len = (next.position - curr.position).squaredNorm(); + double t = sqrt((max_line_length * max_line_length) / len); + size_t new_point_count = 1.0 / t; + for (size_t j = 1; j < new_point_count + 1; j++) { + Vec2d pos = curr.position * (1.0 - j * t) + next.position * (j * t); + auto [p_dist, p_near_l, + p_x] = unscaled_prev_layer.template distance_from_lines_extra(pos.cast()); + new_points.push_back(ExtendedPoint{pos, float(p_dist + boundary_offset), p_near_l}); + } + } + new_points.push_back(points.back()); + } + points = new_points; + } + + for (int point_idx = 0; point_idx < int(points.size()); ++point_idx) { + ExtendedPoint &a = points[point_idx]; + ExtendedPoint &prev = points[point_idx > 0 ? point_idx - 1 : point_idx]; + + int prev_point_idx = point_idx; + while (prev_point_idx > 0) { + prev_point_idx--; + if ((a.position - points[prev_point_idx].position).squaredNorm() > EPSILON) { break; } + } + + int next_point_index = point_idx; + while (next_point_index < int(points.size()) - 1) { + next_point_index++; + if ((a.position - points[next_point_index].position).squaredNorm() > EPSILON) { break; } + } + + if (prev_point_idx != point_idx && next_point_index != point_idx) { + float distance = (prev.position - a.position).norm(); + float alfa = angle(a.position - points[prev_point_idx].position, points[next_point_index].position - a.position); + cestim.add_point(distance, alfa); + } + + a.curvature = cestim.get_curvature(); + } + + return points; +} + +struct ProcessedPoint +{ + Point p; + float speed = 1.0f; + float overlap = 1.0f; +}; + +class ExtrusionQualityEstimator +{ + std::unordered_map> prev_layer_boundaries; + std::unordered_map> next_layer_boundaries; + const PrintObject *current_object; + +public: + void set_current_object(const PrintObject *object) { current_object = object; } + + void prepare_for_new_layer(const Layer *layer) + { + if (layer == nullptr) return; + const PrintObject *object = layer->object(); + prev_layer_boundaries[object] = next_layer_boundaries[object]; + next_layer_boundaries[object] = AABBTreeLines::LinesDistancer{to_unscaled_linesf(layer->lslices)}; + } + + std::vector estimate_extrusion_quality(const ExtrusionPath &path, + const ConfigOptionPercents &overlaps, + const ConfigOptionFloatsOrPercents &speeds, + float ext_perimeter_speed, + float original_speed) + { + size_t speed_sections_count = std::min(overlaps.values.size(), speeds.values.size()); + std::vector> speed_sections; + for (size_t i = 0; i < speed_sections_count; i++) { + float distance = path.width * (1.0 - (overlaps.get_at(i) / 100.0)); + float speed = speeds.get_at(i).percent ? (ext_perimeter_speed * speeds.get_at(i).value / 100.0) : speeds.get_at(i).value; + speed_sections.push_back({distance, speed}); + } + std::sort(speed_sections.begin(), speed_sections.end(), + [](const std::pair &a, const std::pair &b) { + if (a.first == b.first) { + return a.second > b.second; + } + return a.first < b.first; }); + + std::pair last_section{INFINITY, 0}; + for (auto §ion : speed_sections) { + if (section.first == last_section.first) { + section.second = last_section.second; + } else { + last_section = section; + } + } + + std::vector extended_points = + estimate_points_properties(path.polyline.points, prev_layer_boundaries[current_object], path.width); + const auto width_inv = 1.0f / path.width; + std::vector processed_points; + processed_points.reserve(extended_points.size()); + for (size_t i = 0; i < extended_points.size(); i++) { + const ExtendedPoint &curr = extended_points[i]; + const ExtendedPoint &next = extended_points[i + 1 < extended_points.size() ? i + 1 : i]; + + auto calculate_speed = [&speed_sections, &original_speed](float distance) { + float final_speed; + if (distance <= speed_sections.front().first) { + final_speed = original_speed; + } else if (distance >= speed_sections.back().first) { + final_speed = speed_sections.back().second; + } else { + size_t section_idx = 0; + while (distance > speed_sections[section_idx + 1].first) { + section_idx++; + } + float t = (distance - speed_sections[section_idx].first) / + (speed_sections[section_idx + 1].first - speed_sections[section_idx].first); + t = std::clamp(t, 0.0f, 1.0f); + final_speed = (1.0f - t) * speed_sections[section_idx].second + t * speed_sections[section_idx + 1].second; + } + return final_speed; + }; + + float extrusion_speed = std::min(calculate_speed(curr.distance), calculate_speed(next.distance)); + float overlap = std::min(1 - curr.distance * width_inv, 1 - next.distance * width_inv); + + processed_points.push_back({ scaled(curr.position), extrusion_speed, overlap }); + } + return processed_points; + } +}; + +} // namespace Slic3r + +#endif // slic3r_ExtrusionProcessor_hpp_ diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index f0c8102a6f..a82c2a47d8 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -28,6 +28,8 @@ void GCodeWriter::apply_print_config(const PrintConfig &print_config) print_config.gcode_flavor.value == gcfRepRapFirmware; m_max_acceleration = std::lrint(use_mach_limits ? print_config.machine_max_acceleration_extruding.values.front() : 0); m_max_jerk = std::lrint(use_mach_limits ? std::min(print_config.machine_max_jerk_x.values.front(), print_config.machine_max_jerk_y.values.front()) : 0); + m_max_jerk_z = print_config.machine_max_jerk_z.values.front(); + m_max_jerk_e = print_config.machine_max_jerk_e.values.front(); } void GCodeWriter::set_extruders(std::vector extruder_ids) @@ -190,13 +192,13 @@ std::string GCodeWriter::set_acceleration(unsigned int acceleration) return gcode.str(); } -std::string GCodeWriter::set_jerk_xy(unsigned int jerk) +std::string GCodeWriter::set_jerk_xy(double jerk) { // Clamp the jerk to the allowed maximum. if (m_max_jerk > 0 && jerk > m_max_jerk) jerk = m_max_jerk; - if (jerk < 1 || jerk == m_last_jerk) + if (jerk < 0.01 || is_approx(jerk, m_last_jerk)) return std::string(); m_last_jerk = jerk; @@ -206,7 +208,10 @@ std::string GCodeWriter::set_jerk_xy(unsigned int jerk) gcode << "SET_VELOCITY_LIMIT SQUARE_CORNER_VELOCITY=" << jerk; else gcode << "M205 X" << jerk << " Y" << jerk; - + + if (m_is_bbl_printers) + gcode << std::setprecision(2) << " Z" << m_max_jerk_z << " E" << m_max_jerk_e; + if (GCodeWriter::full_gcode_comment) gcode << " ; adjust jerk"; gcode << "\n"; diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCodeWriter.hpp index 09d0e76843..9648223f2b 100644 --- a/src/libslic3r/GCodeWriter.hpp +++ b/src/libslic3r/GCodeWriter.hpp @@ -46,7 +46,7 @@ public: std::string set_temperature(unsigned int temperature, bool wait = false, int tool = -1) const; std::string set_bed_temperature(int temperature, bool wait = false); std::string set_acceleration(unsigned int acceleration); - std::string set_jerk_xy(unsigned int jerk); + std::string set_jerk_xy(double jerk); std::string set_pressure_advance(double pa) const; std::string reset_e(bool force = false); std::string update_progress(unsigned int num, unsigned int tot, bool allow_100 = false) const; @@ -107,8 +107,10 @@ private: // Limit for setting the acceleration, to respect the machine limits set for the Marlin firmware. // If set to zero, the limit is not in action. unsigned int m_max_acceleration; - unsigned int m_max_jerk; - unsigned int m_last_jerk; + double m_max_jerk; + double m_last_jerk; + double m_max_jerk_z; + double m_max_jerk_e; unsigned int m_travel_acceleration; unsigned int m_travel_jerk; @@ -170,6 +172,13 @@ public: // static constexpr const int XYZF_EXPORT_DIGITS = 6; // static constexpr const int E_EXPORT_DIGITS = 9; #endif + static constexpr const std::array pow_10 { 1., 10., 100., 1000., 10000., 100000., 1000000., 10000000., 100000000., 1000000000. }; + static constexpr const std::array pow_10_inv { 1. / 1., 1. / 10., 1. / 100., 1. / 1000., 1. / 10000., 1. / 100000., 1. / 1000000., 1. / 10000000., 1. / 100000000., 1. / 1000000000. }; + + // Quantize doubles to a resolution of the G-code. + static double quantize(double v, size_t ndigits) { return std::round(v * pow_10[ndigits]) * pow_10_inv[ndigits]; } + static double quantize_xyzf(double v) { return quantize(v, XYZF_EXPORT_DIGITS); } + static double quantize_e(double v) { return quantize(v, E_EXPORT_DIGITS); } void emit_axis(const char axis, const double v, size_t digits); diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index 781d8cb140..ecd1b9cb74 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -1,8 +1,8 @@ #ifndef slic3r_Line_hpp_ #define slic3r_Line_hpp_ -#include "libslic3r.h" #include "Point.hpp" +#include "libslic3r.h" #include @@ -22,149 +22,201 @@ Linef3 transform(const Linef3& line, const Transform3d& t); namespace line_alg { -template struct Traits { - static constexpr int Dim = L::Dim; - using Scalar = typename L::Scalar; + template + struct Traits { + static constexpr int Dim = L::Dim; + using Scalar = typename L::Scalar; - static Vec& get_a(L &l) { return l.a; } - static Vec& get_b(L &l) { return l.b; } - static const Vec& get_a(const L &l) { return l.a; } - static const Vec& get_b(const L &l) { return l.b; } -}; + static Vec& get_a(L& l) { return l.a; } + static Vec& get_b(L& l) { return l.b; } + static const Vec& get_a(const L& l) { return l.a; } + static const Vec& get_b(const L& l) { return l.b; } + }; -template const constexpr int Dim = Traits>::Dim; -template using Scalar = typename Traits>::Scalar; + template + const constexpr int Dim = Traits>::Dim; + template + using Scalar = typename Traits>::Scalar; -template auto get_a(L &&l) { return Traits>::get_a(l); } -template auto get_b(L &&l) { return Traits>::get_b(l); } + template + auto get_a(L&& l) { return Traits>::get_a(l); } + template + auto get_b(L&& l) { return Traits>::get_b(l); } -// Distance to the closest point of line. -template -double distance_to_squared(const L &line, const Vec, Scalar> &point, Vec, Scalar> *nearest_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.0) { - // a == b case - *nearest_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; - if (t < 0.0) { - // beyond the 'a' end of the segment - *nearest_point = get_a(line); - return va.squaredNorm(); - } else if (t > 1.0) { - // beyond the 'b' end of the segment - *nearest_point = get_b(line); - return (point - get_b(line)).template cast().squaredNorm(); + // Distance to the closest point of line. + template + double distance_to_squared(const L& line, const Vec, Scalar>& point, Vec, Scalar>* nearest_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.0) { + // a == b case + *nearest_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; + if (t <= 0.0) { + // beyond the 'a' end of the segment + *nearest_point = get_a(line); + return va.squaredNorm(); + } else if (t >= 1.0) { + // beyond the 'b' end of the segment + *nearest_point = get_b(line); + return (point - get_b(line)).template cast().squaredNorm(); + } + + *nearest_point = (get_a(line).template cast() + t * v).template cast>(); + return (t * v - va).squaredNorm(); } - *nearest_point = (get_a(line).template cast() + t * v).template cast>(); - return (t * v - va).squaredNorm(); -} - -// Distance to the closest point of line. -template -double distance_to_squared(const L &line, const Vec, Scalar> &point) -{ - Vec, Scalar> nearest_point; - return distance_to_squared(line, point, &nearest_point); -} - -template -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(); + // Distance to the closest point of line. + template + double distance_to_squared(const L& line, const Vec, Scalar>& point) + { + Vec, Scalar> nearest_point; + return distance_to_squared(line, point, &nearest_point); } - // 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); -} + template + double distance_to(const L& line, const Vec, Scalar>& point) + { + return std::sqrt(distance_to_squared(line, 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)); -} + // 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)); + } + + template + bool intersection(const L& l1, const L& l2, Vec, Scalar>* intersection_pt) + { + using Floating = typename std::conditional>::value, Scalar, double>::type; + using VecType = const Vec, Floating>; + const VecType v1 = (l1.b - l1.a).template cast(); + const VecType v2 = (l2.b - l2.a).template cast(); + Floating denom = cross2(v1, v2); + if (fabs(denom) < EPSILON) +#if 0 + // Lines are collinear. Return true if they are coincident (overlappign). + return ! (fabs(nume_a) < EPSILON && fabs(nume_b) < EPSILON); +#else + return false; +#endif + const VecType v12 = (l1.a - l2.a).template cast(); + Floating nume_a = cross2(v2, v12); + Floating nume_b = cross2(v1, v12); + Floating t1 = nume_a / denom; + Floating t2 = nume_b / denom; + if (t1 >= 0 && t1 <= 1.0f && t2 >= 0 && t2 <= 1.0f) { + // Get the intersection point. + (*intersection_pt) = (l1.a.template cast() + t1 * v1).template cast>(); + return true; + } + return false; // not intersecting + } } // namespace line_alg -class Line -{ +class Line { public: - Line() {} - Line(const Point& _a, const Point& _b) : a(_a), b(_b) {} - explicit operator Lines() const { Lines lines; lines.emplace_back(*this); return lines; } - void scale(double factor) { this->a *= factor; this->b *= factor; } - void translate(const Point &v) { this->a += v; this->b += v; } - void translate(double x, double y) { this->translate(Point(x, y)); } - void rotate(double angle, const Point ¢er) { this->a.rotate(angle, center); this->b.rotate(angle, center); } - void reverse() { std::swap(this->a, this->b); } + Line() { } + Line(const Point& _a, const Point& _b) + : a(_a) + , b(_b) + { + } + explicit operator Lines() const + { + Lines lines; + lines.emplace_back(*this); + return lines; + } + void scale(double factor) + { + this->a *= factor; + this->b *= factor; + } + void translate(const Point& v) + { + this->a += v; + this->b += v; + } + void translate(double x, double y) { this->translate(Point(x, y)); } + void rotate(double angle, const Point& center) + { + this->a.rotate(angle, center); + this->b.rotate(angle, center); + } + void reverse() { std::swap(this->a, this->b); } double length() const { return (b - a).cast().norm(); } - Point midpoint() const { return (this->a + this->b) / 2; } - bool intersection_infinite(const Line &other, Point* point) const; - bool operator==(const Line &rhs) const { return this->a == rhs.a && this->b == rhs.b; } - 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; - bool perpendicular_to(double angle) const; - bool perpendicular_to(const Line& line) const; + Point midpoint() const { return (this->a + this->b) / 2; } + bool intersection_infinite(const Line& other, Point* point) const; + bool operator==(const Line& rhs) const { return this->a == rhs.a && this->b == rhs.b; } + 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; + bool perpendicular_to(double angle) const; + bool perpendicular_to(const Line& line) const; double atan2_() const { return atan2(this->b(1) - this->a(1), this->b(0) - this->a(0)); } double orientation() const; double direction() const; Vector vector() const { return this->b - this->a; } Vector normal() const { return Vector((this->b(1) - this->a(1)), -(this->b(0) - this->a(0))); } - bool intersection(const Line& line, Point* intersection) const; - double ccw(const Point& point) const { return point.ccw(*this); } + bool intersection(const Line& line, Point* intersection) const; // Clip a line with a bounding box. Returns false if the line is completely outside of the bounding box. - bool clip_with_bbox(const BoundingBox &bbox); + bool clip_with_bbox(const BoundingBox& bbox); // Extend the line from both sides by an offset. - void extend(double offset); + void extend(double offset); - 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)); } + 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)); } + 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; @@ -173,23 +225,43 @@ public: using Scalar = Point::Scalar; }; -class ThickLine : public Line -{ +class ThickLine : public Line { public: - ThickLine() : a_width(0), b_width(0) {} - ThickLine(const Point& a, const Point& b) : Line(a, b), a_width(0), b_width(0) {} - ThickLine(const Point& a, const Point& b, double wa, double wb) : Line(a, b), a_width(wa), b_width(wb) {} + ThickLine() + : a_width(0) + , b_width(0) + { + } + ThickLine(const Point& a, const Point& b) + : Line(a, b) + , a_width(0) + , b_width(0) + { + } + ThickLine(const Point& a, const Point& b, double wa, double wb) + : Line(a, b) + , a_width(wa) + , b_width(wb) + { + } double a_width, b_width; }; -class Line3 -{ +class Line3 { public: - Line3() : a(Vec3crd::Zero()), b(Vec3crd::Zero()) {} - Line3(const Vec3crd& _a, const Vec3crd& _b) : a(_a), b(_b) {} + Line3() + : a(Vec3crd::Zero()) + , b(Vec3crd::Zero()) + { + } + Line3(const Vec3crd& _a, const Vec3crd& _b) + : a(_a) + , b(_b) + { + } - double length() const { return (this->a - this->b).cast().norm(); } + double length() const { return (this->a - this->b).cast().norm(); } Vec3crd vector() const { return this->b - this->a; } Vec3crd a; @@ -199,11 +271,18 @@ public: using Scalar = Vec3crd::Scalar; }; -class Linef -{ +class Linef { public: - Linef() : a(Vec2d::Zero()), b(Vec2d::Zero()) {} - Linef(const Vec2d& _a, const Vec2d& _b) : a(_a), b(_b) {} + Linef() + : a(Vec2d::Zero()) + , b(Vec2d::Zero()) + { + } + Linef(const Vec2d& _a, const Vec2d& _b) + : a(_a) + , b(_b) + { + } Vec2d a; Vec2d b; @@ -211,18 +290,30 @@ public: static const constexpr int Dim = 2; using Scalar = Vec2d::Scalar; }; +using Linesf = std::vector; -class Linef3 -{ +class Linef3 { public: - Linef3() : a(Vec3d::Zero()), b(Vec3d::Zero()) {} - Linef3(const Vec3d& _a, const Vec3d& _b) : a(_a), b(_b) {} + Linef3() + : a(Vec3d::Zero()) + , b(Vec3d::Zero()) + { + } + Linef3(const Vec3d& _a, const Vec3d& _b) + : a(_a) + , b(_b) + { + } - Vec3d intersect_plane(double z) const; - void scale(double factor) { this->a *= factor; this->b *= factor; } - Vec3d vector() const { return this->b - this->a; } - Vec3d unit_vector() const { return (length() == 0.0) ? Vec3d::Zero() : vector().normalized(); } - double length() const { return vector().norm(); } + Vec3d intersect_plane(double z) const; + void scale(double factor) + { + this->a *= factor; + this->b *= factor; + } + Vec3d vector() const { return this->b - this->a; } + Vec3d unit_vector() const { return (length() == 0.0) ? Vec3d::Zero() : vector().normalized(); } + double length() const { return vector().norm(); } Vec3d a; Vec3d b; @@ -231,26 +322,31 @@ public: using Scalar = Vec3d::Scalar; }; -BoundingBox get_extents(const Lines &lines); +BoundingBox get_extents(const Lines& lines); } // namespace Slic3r // start Boost #include -namespace boost { namespace polygon { +namespace boost { +namespace polygon { template <> - struct geometry_concept { typedef segment_concept type; }; + 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::Line& line, direction_1d dir) { + + static inline point_type get(const Slic3r::Line& line, direction_1d dir) + { return dir.to_int() ? line.b : line.a; } }; -} } +} +} // end Boost #endif // slic3r_Line_hpp_ diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index a88b79fea0..986938dd38 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -271,7 +271,7 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime Polylines remain_polines; //BBS: don't calculate overhang degree when enable fuzzy skin. It's unmeaning - if (perimeter_generator.config->enable_overhang_speed && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None) { + if (perimeter_generator.config->overhang_speed_classic && perimeter_generator.config->enable_overhang_speed && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None) { for (auto it = lower_polygons_series->begin(); it != lower_polygons_series->end(); it++) { @@ -631,10 +631,11 @@ void PerimeterGenerator::process_classic() m_mm3_per_mm = this->perimeter_flow.mm3_per_mm(); coord_t perimeter_width = this->perimeter_flow.scaled_width(); coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing(); - + // external perimeters m_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm(); coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width(); + coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing(); coord_t ext_perimeter_spacing2; if(config->precise_outer_wall) @@ -1114,7 +1115,6 @@ void PerimeterGenerator::process_arachne() coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width(); coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing(); coord_t ext_perimeter_spacing2 = scaled(0.5f * (this->ext_perimeter_flow.spacing() + this->perimeter_flow.spacing())); - // overhang perimeters m_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm(); @@ -1133,11 +1133,16 @@ void PerimeterGenerator::process_arachne() // we need to process each island separately because we might have different // extra perimeters for each one for (const Surface& surface : this->slices->surfaces) { + coord_t bead_width_0 = ext_perimeter_spacing; + if (config->precise_outer_wall) + bead_width_0 = ext_perimeter_width + this->perimeter_flow.scaled_width() - perimeter_spacing; // detect how many perimeters must be generated for this island int loop_number = this->config->wall_loops + surface.extra_perimeters - 1; // 0-indexed loops - ExPolygons last = offset_ex(surface.expolygon.simplify_p(m_scaled_resolution), -float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.)); + ExPolygons last = offset_ex(surface.expolygon.simplify_p(m_scaled_resolution), + config->precise_outer_wall ? -float(ext_perimeter_width / 2. - bead_width_0 / 2.) + : -float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.)); Polygons last_p = to_polygons(last); - + double min_nozzle_diameter = *std::min_element(print_config->nozzle_diameter.values.begin(), print_config->nozzle_diameter.values.end()); Arachne::WallToolPathsParams input_params; { @@ -1156,8 +1161,14 @@ void PerimeterGenerator::process_arachne() input_params.wall_transition_angle = this->object_config->wall_transition_angle.value; input_params.wall_distribution_count = this->object_config->wall_distribution_count.value; } + coord_t wall_0_inset = 0; + //if (config->precise_outer_wall) + // wall_0_inset = 0.5 * (ext_perimeter_width + this->perimeter_flow.scaled_width() - ext_perimeter_spacing - + // perimeter_spacing); + + Arachne::WallToolPaths wallToolPaths(last_p, bead_width_0, perimeter_spacing, coord_t(loop_number + 1), + wall_0_inset, layer_height, input_params); - Arachne::WallToolPaths wallToolPaths(last_p, ext_perimeter_spacing, perimeter_spacing, coord_t(loop_number + 1), 0, layer_height, input_params); std::vector perimeters = wallToolPaths.getToolPaths(); loop_number = int(perimeters.size()) - 1; @@ -1399,6 +1410,7 @@ std::map PerimeterGenerator::generate_lower_polygons_series(float // BBS: increase start_offset a little to avoid to calculate 90 degree as overhang offset_series[0] = start_offset + 0.5 * (end_offset - start_offset) / (overhang_sampling_number - 1); offset_series[overhang_sampling_number - 2] = end_offset; + offset_series.back() = 0.1 * nozzle_diameter; std::map lower_polygons_series; if (this->lower_slices == NULL) { diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 82ad753bec..bef791567e 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -96,6 +96,18 @@ inline typename Derived::Scalar cross2(const Eigen::MatrixBase &v1, con template inline Eigen::Matrix perp(const Eigen::MatrixBase> &v) { return Eigen::Matrix(- v.y(), v.x()); } +// Angle from v1 to v2, returning double atan2(y, x) normalized to <-PI, PI>. +template +inline double angle(const Eigen::MatrixBase& v1, const Eigen::MatrixBase& v2) +{ + static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "angle(): first parameter is not a 2D vector"); + static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "angle(): second parameter is not a 2D vector"); + auto v1d = v1.template cast(); + auto v2d = v2.template cast(); + return atan2(cross2(v1d, v2d), v1d.dot(v2d)); +} + + template Eigen::Matrix to_2d(const Eigen::MatrixBase> &ptN) { return { ptN.x(), ptN.y() }; } diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 10e662025f..f5c0711187 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -752,7 +752,8 @@ static std::vector s_Preset_print_options { "small_perimeter_speed", "small_perimeter_threshold","bridge_angle", "filter_out_gap_fill", "post_process", "travel_acceleration","inner_wall_acceleration", "default_jerk", "outer_wall_jerk", "inner_wall_jerk", "infill_jerk", "top_surface_jerk", "initial_layer_jerk","travel_jerk", "top_solid_infill_flow_ratio","bottom_solid_infill_flow_ratio","only_one_wall_first_layer", - "print_flow_ratio","seam_gap","role_based_wipe_speed","wipe_speed","accel_to_decel_enable", "accel_to_decel_factor", "wipe_on_loops", "bridge_density", "precise_outer_wall" + "print_flow_ratio","seam_gap","role_based_wipe_speed","wipe_speed","accel_to_decel_enable", "accel_to_decel_factor", "wipe_on_loops", "bridge_density", "precise_outer_wall", + "overhang_speed_classic" }; diff --git a/src/libslic3r/PrincipalComponents2D.cpp b/src/libslic3r/PrincipalComponents2D.cpp new file mode 100644 index 0000000000..7bdf793157 --- /dev/null +++ b/src/libslic3r/PrincipalComponents2D.cpp @@ -0,0 +1,140 @@ +#include "PrincipalComponents2D.hpp" +#include "Point.hpp" + +namespace Slic3r { + + + +// returns triangle area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance +// none of the values is divided/normalized by area. +// The function computes intgeral over the area of the triangle, with function f(x,y) = x for first moments of area (y is analogous) +// f(x,y) = x^2 for second moment of area +// and f(x,y) = x*y for second moment of area covariance +std::tuple compute_moments_of_area_of_triangle(const Vec2f &a, const Vec2f &b, const Vec2f &c) +{ + // based on the following guide: + // Denote the vertices of S by a, b, c. Then the map + // g:(u,v)↦a+u(b−a)+v(c−a) , + // which in coordinates appears as + // g:(u,v)↦{x(u,v)y(u,v)=a1+u(b1−a1)+v(c1−a1)=a2+u(b2−a2)+v(c2−a2) ,(1) + // obviously maps S′ bijectively onto S. Therefore the transformation formula for multiple integrals steps into action, and we obtain + // ∫Sf(x,y)d(x,y)=∫S′f(x(u,v),y(u,v))∣∣Jg(u,v)∣∣ d(u,v) . + // In the case at hand the Jacobian determinant is a constant: From (1) we obtain + // Jg(u,v)=det[xuyuxvyv]=(b1−a1)(c2−a2)−(c1−a1)(b2−a2) . + // Therefore we can write + // ∫Sf(x,y)d(x,y)=∣∣Jg∣∣∫10∫1−u0f~(u,v) dv du , + // where f~ denotes the pullback of f to S′: + // f~(u,v):=f(x(u,v),y(u,v)) . + // Don't forget taking the absolute value of Jg! + + float jacobian_determinant_abs = std::abs((b.x() - a.x()) * (c.y() - a.y()) - (c.x() - a.x()) * (b.y() - a.y())); + + // coordinate transform: gx(u,v) = a.x + u * (b.x - a.x) + v * (c.x - a.x) + // coordinate transform: gy(u,v) = a.y + u * (b.y - a.y) + v * (c.y - a.y) + // second moment of area for x: f(x, y) = x^2; + // f(gx(u,v), gy(u,v)) = gx(u,v)^2 = ... (long expanded form) + + // result is Int_T func = jacobian_determinant_abs * Int_0^1 Int_0^1-u func(gx(u,v), gy(u,v)) dv du + // integral_0^1 integral_0^(1 - u) (a + u (b - a) + v (c - a))^2 dv du = 1/12 (a^2 + a (b + c) + b^2 + b c + c^2) + + Vec2f second_moment_of_area_xy = jacobian_determinant_abs * + (a.cwiseProduct(a) + b.cwiseProduct(b) + b.cwiseProduct(c) + c.cwiseProduct(c) + + a.cwiseProduct(b + c)) / + 12.0f; + // second moment of area covariance : f(x, y) = x*y; + // f(gx(u,v), gy(u,v)) = gx(u,v)*gy(u,v) = ... (long expanded form) + //(a_1 + u * (b_1 - a_1) + v * (c_1 - a_1)) * (a_2 + u * (b_2 - a_2) + v * (c_2 - a_2)) + // == (a_1 + u (b_1 - a_1) + v (c_1 - a_1)) (a_2 + u (b_2 - a_2) + v (c_2 - a_2)) + + // intermediate result: integral_0^(1 - u) (a_1 + u (b_1 - a_1) + v (c_1 - a_1)) (a_2 + u (b_2 - a_2) + v (c_2 - a_2)) dv = + // 1/6 (u - 1) (-c_1 (u - 1) (a_2 (u - 1) - 3 b_2 u) - c_2 (u - 1) (a_1 (u - 1) - 3 b_1 u + 2 c_1 (u - 1)) + 3 b_1 u (a_2 (u - 1) - 2 + // b_2 u) + a_1 (u - 1) (3 b_2 u - 2 a_2 (u - 1))) result = integral_0^1 1/6 (u - 1) (-c_1 (u - 1) (a_2 (u - 1) - 3 b_2 u) - c_2 (u - + // 1) (a_1 (u - 1) - 3 b_1 u + 2 c_1 (u - 1)) + 3 b_1 u (a_2 (u - 1) - 2 b_2 u) + a_1 (u - 1) (3 b_2 u - 2 a_2 (u - 1))) du = + // 1/24 (a_2 (b_1 + c_1) + a_1 (2 a_2 + b_2 + c_2) + b_2 c_1 + b_1 c_2 + 2 b_1 b_2 + 2 c_1 c_2) + // result is Int_T func = jacobian_determinant_abs * Int_0^1 Int_0^1-u func(gx(u,v), gy(u,v)) dv du + float second_moment_of_area_covariance = jacobian_determinant_abs * (1.0f / 24.0f) * + (a.y() * (b.x() + c.x()) + a.x() * (2.0f * a.y() + b.y() + c.y()) + b.y() * c.x() + + b.x() * c.y() + 2.0f * b.x() * b.y() + 2.0f * c.x() * c.y()); + + float area = jacobian_determinant_abs * 0.5f; + + Vec2f first_moment_of_area_xy = jacobian_determinant_abs * (a + b + c) / 6.0f; + + return {area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance}; +}; + +// returns two eigenvectors of the area covered by given polygons. The vectors are sorted by their corresponding eigenvalue, largest first +std::tuple compute_principal_components(const Polygons &polys) +{ + Vec2f centroid_accumulator = Vec2f::Zero(); + Vec2f second_moment_of_area_accumulator = Vec2f::Zero(); + float second_moment_of_area_covariance_accumulator = 0.0f; + float area = 0.0f; + + for (const Polygon &poly : polys) { + Vec2f p0 = unscaled(poly.first_point()).cast(); + for (size_t i = 2; i < poly.points.size(); i++) { + Vec2f p1 = unscaled(poly.points[i - 1]).cast(); + Vec2f p2 = unscaled(poly.points[i]).cast(); + + float sign = cross2(p1 - p0, p2 - p1) > 0 ? 1.0f : -1.0f; + + auto [triangle_area, first_moment_of_area, second_moment_area, + second_moment_of_area_covariance] = compute_moments_of_area_of_triangle(p0, p1, p2); + area += sign * triangle_area; + centroid_accumulator += sign * first_moment_of_area; + second_moment_of_area_accumulator += sign * second_moment_area; + second_moment_of_area_covariance_accumulator += sign * second_moment_of_area_covariance; + } + } + + if (area <= 0.0) { + return {Vec2f::Zero(), Vec2f::Zero()}; + } + + Vec2f centroid = centroid_accumulator / area; + Vec2f variance = second_moment_of_area_accumulator / area - centroid.cwiseProduct(centroid); + double covariance = second_moment_of_area_covariance_accumulator / area - centroid.x() * centroid.y(); +#if 0 + std::cout << "area : " << area << std::endl; + std::cout << "variancex : " << variance.x() << std::endl; + std::cout << "variancey : " << variance.y() << std::endl; + std::cout << "covariance : " << covariance << std::endl; +#endif + if (abs(covariance) < EPSILON) { + std::tuple result{Vec2f{variance.x(), 0.0}, Vec2f{0.0, variance.y()}}; + if (variance.y() > variance.x()) { + return {std::get<1>(result), std::get<0>(result)}; + } else + return result; + } + + // now we find the first principal component of the covered area by computing max eigenvalue and the correspoding eigenvector of + // covariance matrix + // covaraince matrix C is : | VarX Cov | + // | Cov VarY | + // Eigenvalues are solutions to det(C - lI) = 0, where l is the eigenvalue and I unit matrix + // Eigenvector for eigenvalue l is any vector v such that Cv = lv + + float eigenvalue_a = 0.5f * (variance.x() + variance.y() + + sqrt((variance.x() - variance.y()) * (variance.x() - variance.y()) + 4.0f * covariance * covariance)); + float eigenvalue_b = 0.5f * (variance.x() + variance.y() - + sqrt((variance.x() - variance.y()) * (variance.x() - variance.y()) + 4.0f * covariance * covariance)); + Vec2f eigenvector_a{(eigenvalue_a - variance.y()) / covariance, 1.0f}; + Vec2f eigenvector_b{(eigenvalue_b - variance.y()) / covariance, 1.0f}; + +#if 0 + std::cout << "eigenvalue_a: " << eigenvalue_a << std::endl; + std::cout << "eigenvalue_b: " << eigenvalue_b << std::endl; + std::cout << "eigenvectorA: " << eigenvector_a.x() << " " << eigenvector_a.y() << std::endl; + std::cout << "eigenvectorB: " << eigenvector_b.x() << " " << eigenvector_b.y() << std::endl; +#endif + + if (eigenvalue_a > eigenvalue_b) { + return {eigenvector_a, eigenvector_b}; + } else { + return {eigenvector_b, eigenvector_a}; + } +} + +} \ No newline at end of file diff --git a/src/libslic3r/PrincipalComponents2D.hpp b/src/libslic3r/PrincipalComponents2D.hpp new file mode 100644 index 0000000000..dc8897a7a7 --- /dev/null +++ b/src/libslic3r/PrincipalComponents2D.hpp @@ -0,0 +1,24 @@ +#ifndef slic3r_PrincipalComponents2D_hpp_ +#define slic3r_PrincipalComponents2D_hpp_ + +#include "AABBTreeLines.hpp" +#include "BoundingBox.hpp" +#include "libslic3r.h" +#include +#include "Polygon.hpp" + +namespace Slic3r { + +// returns triangle area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance +// none of the values is divided/normalized by area. +// The function computes intgeral over the area of the triangle, with function f(x,y) = x for first moments of area (y is analogous) +// f(x,y) = x^2 for second moment of area +// and f(x,y) = x*y for second moment of area covariance +std::tuple compute_moments_of_area_of_triangle(const Vec2f &a, const Vec2f &b, const Vec2f &c); + +// returns two eigenvectors of the area covered by given polygons. The vectors are sorted by their corresponding eigenvalue, largest first +std::tuple compute_principal_components(const Polygons &polys); + +} + +#endif \ No newline at end of file diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 4e9dd58a0c..d2409ead52 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -670,7 +670,6 @@ public: const PrintConfig& config() const { return m_config; } const PrintObjectConfig& default_object_config() const { return m_default_object_config; } const PrintRegionConfig& default_region_config() const { return m_default_region_config; } - PrintRegionConfig& default_region_config() { return m_default_region_config; } ConstPrintObjectPtrsAdaptor objects() const { return ConstPrintObjectPtrsAdaptor(&m_objects); } PrintObject* get_object(size_t idx) { return const_cast(m_objects[idx]); } const PrintObject* get_object(size_t idx) const { return m_objects[idx]; } diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index c4de3ad83f..e24fe88ebf 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -659,7 +659,7 @@ void PrintConfigDef::init_fff_params() def->enum_values.emplace_back("75%"); def->enum_values.emplace_back("95%"); def->enum_labels.emplace_back("0%"); - def->enum_labels.emplace_back("10%"); + def->enum_labels.emplace_back("5%"); def->enum_labels.emplace_back("25%"); def->enum_labels.emplace_back("50%"); def->enum_labels.emplace_back("75%"); @@ -718,10 +718,10 @@ void PrintConfigDef::init_fff_params() def = this->add("precise_outer_wall",coBool); - def->label = L("Precise wall"); + def->label = L("Precise wall(experimental)"); def->category = L("Quality"); - def->tooltip = L("Improve outer wall precesion by adjusting outer wall spacing"); - def->set_default_value(new ConfigOptionBool{true}); + def->tooltip = L("Improve shell precesion by adjusting outer wall spacing. This also improves layer consistency."); + def->set_default_value(new ConfigOptionBool{false}); def = this->add("only_one_wall_top", coBool); def->label = L("Only one wall on top surfaces"); @@ -735,6 +735,13 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Use only one wall on first layer, to give more space to the bottom infill pattern"); def->set_default_value(new ConfigOptionBool(false)); + def = this->add("overhang_speed_classic", coBool); + def->label = L("Classic mode"); + def->category = L("Speed"); + def->tooltip = L("Enable this option to use classic mode"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool{ false }); + def = this->add("enable_overhang_speed", coBool); def->label = L("Slow down for overhang"); def->category = L("Speed"); @@ -1019,7 +1026,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Hilbert Curve")); def->enum_labels.push_back(L("Archimedean Chords")); def->enum_labels.push_back(L("Octagram Spiral")); - def->set_default_value(new ConfigOptionEnum(ipRectilinear)); + def->set_default_value(new ConfigOptionEnum(ipMonotonic)); def = this->add("bottom_surface_pattern", coEnum); def->label = L("Bottom surface pattern"); @@ -1052,23 +1059,23 @@ void PrintConfigDef::init_fff_params() def = this->add("small_perimeter_speed", coFloatOrPercent); def->label = L("Small perimeters"); def->category = L("Speed"); - def->tooltip = L("This separate setting will affect the speed of perimeters having radius <= 6.5mm " + def->tooltip = L("This separate setting will affect the speed of perimeters having radius <= small_perimeter_threshold " "(usually holes). If expressed as percentage (for example: 80%) it will be calculated " "on the outer wall speed setting above. Set to zero for auto."); def->sidetext = L("mm/s or %"); def->ratio_over = "outer_wall_speed"; def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloatOrPercent(100, true)); + def->set_default_value(new ConfigOptionFloatOrPercent(50, true)); def = this->add("small_perimeter_threshold", coFloat); def->label = L("Small perimeters threshold"); def->category = L("Speed"); - def->tooltip = L("This sets the threshold for small perimeter length. Default threshold is 6.5mm"); + def->tooltip = L("This sets the threshold for small perimeter length. Default threshold is 0mm"); def->sidetext = L("mm"); def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(6.5)); + def->set_default_value(new ConfigOptionFloat(0)); def = this->add("wall_infill_order", coEnum); def->label = L("Order of inner wall/outer wall/infil"); @@ -3280,7 +3287,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Classic")); def->enum_labels.push_back(L("Arachne")); def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnum(PerimeterGeneratorType::Classic)); + def->set_default_value(new ConfigOptionEnum(PerimeterGeneratorType::Arachne)); def = this->add("wall_transition_length", coPercent); def->label = L("Wall transition length"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 0f8418e76c..39c8e993a7 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -763,6 +763,8 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionBool, wipe_on_loops)) ((ConfigOptionEnum, wall_infill_order)) ((ConfigOptionBool, precise_outer_wall)) + ((ConfigOptionBool, overhang_speed_classic)) + diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index dadf392078..ae510885c5 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -826,7 +826,8 @@ bool PrintObject::invalidate_state_by_config_options( //BBS || opt_key == "enable_overhang_speed" || opt_key == "detect_thin_wall" - || opt_key == "precise_outer_wall") { + || opt_key == "precise_outer_wall" + || opt_key == "overhang_speed_classic") { steps.emplace_back(posPerimeters); steps.emplace_back(posSupportMaterial); } else if (opt_key == "bridge_flow" || opt_key == "bridge_density") { diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp new file mode 100644 index 0000000000..0b10a6b755 --- /dev/null +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -0,0 +1,1267 @@ +#include "SupportSpotsGenerator.hpp" +/* +#include "BoundingBox.hpp" +#include "ExPolygon.hpp" +#include "ExtrusionEntity.hpp" +#include "ExtrusionEntityCollection.hpp" +#include "GCode/ExtrusionProcessor.hpp" +#include "Line.hpp" +#include "Point.hpp" +#include "Polygon.hpp" +#include "PrincipalComponents2D.hpp" +#include "Print.hpp" +#include "PrintBase.hpp" +#include "PrintConfig.hpp" +#include "Tesselate.hpp" +#include "libslic3r.h" +#include "tbb/parallel_for.h" +#include "tbb/blocked_range.h" +#include "tbb/blocked_range2d.h" +#include "tbb/parallel_reduce.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AABBTreeLines.hpp" +#include "KDTreeIndirect.hpp" +#include "libslic3r/Layer.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "Geometry/ConvexHull.hpp" + +// #define DETAILED_DEBUG_LOGS +// #define DEBUG_FILES + +#ifdef DEBUG_FILES +#include +#include "libslic3r/Color.hpp" +#endif + +namespace Slic3r { + +class ExtrusionLine +{ +public: + ExtrusionLine() : a(Vec2f::Zero()), b(Vec2f::Zero()), len(0.0), origin_entity(nullptr) {} + ExtrusionLine(const Vec2f &a, const Vec2f &b, float len, const ExtrusionEntity *origin_entity) + : a(a), b(b), len(len), origin_entity(origin_entity) + {} + + ExtrusionLine(const Vec2f &a, const Vec2f &b) + : a(a), b(b), len((a-b).norm()), origin_entity(nullptr) + {} + + bool is_external_perimeter() const + { + assert(origin_entity != nullptr); + return origin_entity->role() == erExternalPerimeter; + } + + Vec2f a; + Vec2f b; + float len; + const ExtrusionEntity *origin_entity; + + std::optional support_point_generated = {}; + float form_quality = 1.0f; + float curled_up_height = 0.0f; + + static const constexpr int Dim = 2; + using Scalar = Vec2f::Scalar; +}; + +auto get_a(ExtrusionLine &&l) { return l.a; } +auto get_b(ExtrusionLine &&l) { return l.b; } + +namespace SupportSpotsGenerator { + +using LD = AABBTreeLines::LinesDistancer; + +struct SupportGridFilter +{ +private: + Vec3f cell_size; + Vec3f origin; + Vec3f size; + Vec3i cell_count; + + std::unordered_set taken_cells{}; + +public: + SupportGridFilter(const PrintObject *po, float voxel_size) + { + cell_size = Vec3f(voxel_size, voxel_size, voxel_size); + + Vec2crd size_half = po->size().head<2>().cwiseQuotient(Vec2crd(2, 2)) + Vec2crd::Ones(); + Vec3f min = unscale(Vec3crd(-size_half.x(), -size_half.y(), 0)).cast() - cell_size; + Vec3f max = unscale(Vec3crd(size_half.x(), size_half.y(), po->height())).cast() + cell_size; + + origin = min; + size = max - min; + cell_count = size.cwiseQuotient(cell_size).cast() + Vec3i::Ones(); + } + + Vec3i to_cell_coords(const Vec3f &position) const + { + Vec3i cell_coords = (position - this->origin).cwiseQuotient(this->cell_size).cast(); + return cell_coords; + } + + size_t to_cell_index(const Vec3i &cell_coords) const + { +#ifdef DETAILED_DEBUG_LOGS + assert(cell_coords.x() >= 0); + assert(cell_coords.x() < cell_count.x()); + assert(cell_coords.y() >= 0); + assert(cell_coords.y() < cell_count.y()); + assert(cell_coords.z() >= 0); + assert(cell_coords.z() < cell_count.z()); +#endif + return cell_coords.z() * cell_count.x() * cell_count.y() + cell_coords.y() * cell_count.x() + cell_coords.x(); + } + + Vec3f get_cell_center(const Vec3i &cell_coords) const + { + return origin + cell_coords.cast().cwiseProduct(this->cell_size) + this->cell_size.cwiseQuotient(Vec3f(2.0f, 2.0f, 2.0f)); + } + + void take_position(const Vec3f &position) { taken_cells.insert(to_cell_index(to_cell_coords(position))); } + + bool position_taken(const Vec3f &position) const + { + return taken_cells.find(to_cell_index(to_cell_coords(position))) != taken_cells.end(); + } +}; + +struct SliceConnection +{ + float area{}; + Vec3f centroid_accumulator = Vec3f::Zero(); + Vec2f second_moment_of_area_accumulator = Vec2f::Zero(); + float second_moment_of_area_covariance_accumulator{}; + + void add(const SliceConnection &other) + { + this->area += other.area; + this->centroid_accumulator += other.centroid_accumulator; + this->second_moment_of_area_accumulator += other.second_moment_of_area_accumulator; + this->second_moment_of_area_covariance_accumulator += other.second_moment_of_area_covariance_accumulator; + } + + void print_info(const std::string &tag) + { + Vec3f centroid = centroid_accumulator / area; + Vec2f variance = (second_moment_of_area_accumulator / area - centroid.head<2>().cwiseProduct(centroid.head<2>())); + float covariance = second_moment_of_area_covariance_accumulator / area - centroid.x() * centroid.y(); + std::cout << tag << std::endl; + std::cout << "area: " << area << std::endl; + std::cout << "centroid: " << centroid.x() << " " << centroid.y() << " " << centroid.z() << std::endl; + std::cout << "variance: " << variance.x() << " " << variance.y() << std::endl; + std::cout << "covariance: " << covariance << std::endl; + } +}; + +float get_flow_width(const LayerRegion *region, ExtrusionRole role) +{ + if (role == ExtrusionRole::erBridgeInfill) return region->flow(FlowRole::frExternalPerimeter).width(); + if (role == ExtrusionRole::erExternalPerimeter) return region->flow(FlowRole::frExternalPerimeter).width(); + if (role == ExtrusionRole::erGapFill) return region->flow(FlowRole::frInfill).width(); + if (role == ExtrusionRole::erPerimeter) return region->flow(FlowRole::frPerimeter).width(); + if (role == ExtrusionRole::erSolidInfill) return region->flow(FlowRole::frSolidInfill).width(); + if (role == ExtrusionRole::erInternalInfill) return region->flow(FlowRole::frInfill).width(); + if (role == ExtrusionRole::erTopSolidInfill) return region->flow(FlowRole::frTopSolidInfill).width(); + // default + return region->flow(FlowRole::frPerimeter).width(); +} + +std::vector to_short_lines(const ExtrusionEntity *e, float length_limit) +{ + assert(!e->is_collection()); + Polyline pl = e->as_polyline(); + std::vector lines; + lines.reserve(pl.points.size() * 1.5f); + for (int point_idx = 0; point_idx < int(pl.points.size()) - 1; ++point_idx) { + Vec2f start = unscaled(pl.points[point_idx]).cast(); + Vec2f next = unscaled(pl.points[point_idx + 1]).cast(); + Vec2f v = next - start; // vector from next to current + float dist_to_next = v.norm(); + v.normalize(); + int lines_count = int(std::ceil(dist_to_next / length_limit)); + float step_size = dist_to_next / lines_count; + for (int i = 0; i < lines_count; ++i) { + Vec2f a(start + v * (i * step_size)); + Vec2f b(start + v * ((i + 1) * step_size)); + lines.emplace_back(a, b, (a-b).norm(), e); + } + } + return lines; +} + +float estimate_curled_up_height( + const ExtendedPoint &point, float layer_height, float flow_width, float prev_line_curled_height, Params params) +{ + float curled_up_height = 0.0f; + if (fabs(point.distance) < 1.5 * flow_width) { + curled_up_height = 0.85 * prev_line_curled_height; + } + if (point.distance > params.malformation_distance_factors.first * flow_width && + point.distance < params.malformation_distance_factors.second * flow_width && point.curvature > -0.1f) { + float dist_factor = std::max(point.distance - params.malformation_distance_factors.first * flow_width, 0.01f) / + ((params.malformation_distance_factors.second - params.malformation_distance_factors.first) * flow_width); + + curled_up_height = layer_height * sqrt(sqrt(dist_factor)) * std::clamp(3.0f * point.curvature, 1.0f, 3.0f); + curled_up_height = std::min(curled_up_height, params.max_curled_height_factor * layer_height); + } + + return curled_up_height; +} + +std::vector check_extrusion_entity_stability(const ExtrusionEntity *entity, + const LayerRegion *layer_region, + const LD &prev_layer_lines, + const AABBTreeLines::LinesDistancer &prev_layer_boundary, + const Params ¶ms) +{ + if (entity->is_collection()) { + std::vector checked_lines_out; + checked_lines_out.reserve(prev_layer_lines.get_lines().size() / 3); + for (const auto *e : static_cast(entity)->entities) { + auto tmp = check_extrusion_entity_stability(e, layer_region, prev_layer_lines, prev_layer_boundary, params); + checked_lines_out.insert(checked_lines_out.end(), tmp.begin(), tmp.end()); + } + return checked_lines_out; + } else if (entity->role().is_bridge() && !entity->role().is_perimeter()) { + // pure bridges are handled separately, beacuse we need to align the forward and backward direction support points + if (entity->length() < scale_(params.min_distance_to_allow_local_supports)) { + return {}; + } + const float flow_width = get_flow_width(layer_region, entity->role()); + std::vector annotated_points = estimate_points_properties(entity->as_polyline().points, + prev_layer_boundary, flow_width, + params.bridge_distance); + + std::vector lines_out; + lines_out.reserve(annotated_points.size()); + float bridged_distance = 0.0f; + + std::optional bridging_dir{}; + + for (size_t i = 0; i < annotated_points.size(); ++i) { + ExtendedPoint &curr_point = annotated_points[i]; + const ExtendedPoint &prev_point = i > 0 ? annotated_points[i - 1] : annotated_points[i]; + + SupportPointCause potential_cause = std::abs(curr_point.curvature) > 0.1 ? SupportPointCause::FloatingBridgeAnchor : + SupportPointCause::LongBridge; + float line_len = (prev_point.position - curr_point.position).norm(); + Vec2d line_dir = line_len > EPSILON ? Vec2d((curr_point.position - prev_point.position) / double(line_len)) : Vec2d::Zero(); + + ExtrusionLine line_out{prev_point.position.cast(), curr_point.position.cast(), line_len, entity}; + + float max_bridge_len = std::max(params.support_points_interface_radius * 2.0f, + params.bridge_distance / + ((1.0f + std::abs(curr_point.curvature)) * (1.0f + std::abs(curr_point.curvature)) * + (1.0f + std::abs(curr_point.curvature)))); + + if (!bridging_dir.has_value() && curr_point.distance > flow_width && line_len > params.bridge_distance * 0.6) { + bridging_dir = line_dir; + } + + if (curr_point.distance > flow_width && potential_cause == SupportPointCause::LongBridge && bridging_dir.has_value() && + bridging_dir->dot(line_dir) < 0.8) { // skip backward direction of bridge - supported by forward points enough + bridged_distance += line_len; + } else if (curr_point.distance > flow_width) { + bridged_distance += line_len; + if (bridged_distance > max_bridge_len) { + bridged_distance = 0.0f; + line_out.support_point_generated = potential_cause; + } + } else { + bridged_distance = 0.0f; + } + + lines_out.push_back(line_out); + } + return lines_out; + + } else { // single extrusion path, with possible varying parameters + if (entity->length() < scale_(params.min_distance_to_allow_local_supports)) { + return {}; + } + + const float flow_width = get_flow_width(layer_region, entity->role()); + // Compute only unsigned distance - prev_layer_lines can contain unconnected paths, thus the sign of the distance is unreliable + std::vector annotated_points = estimate_points_properties(entity->as_polyline().points, + prev_layer_lines, flow_width, + params.bridge_distance); + + std::vector lines_out; + lines_out.reserve(annotated_points.size()); + float bridged_distance = annotated_points.front().position != annotated_points.back().position ? (params.bridge_distance + 1.0f) : + 0.0f; + for (size_t i = 0; i < annotated_points.size(); ++i) { + ExtendedPoint &curr_point = annotated_points[i]; + const ExtendedPoint &prev_point = i > 0 ? annotated_points[i - 1] : annotated_points[i]; + float line_len = (prev_point.position - curr_point.position).norm(); + ExtrusionLine line_out{prev_point.position.cast(), curr_point.position.cast(), line_len, entity}; + + const ExtrusionLine nearest_prev_layer_line = prev_layer_lines.get_lines().size() > 0 ? + prev_layer_lines.get_line(curr_point.nearest_prev_layer_line) : + ExtrusionLine{}; + + // correctify the distance sign using slice polygons + float sign = (prev_layer_boundary.distance_from_lines(curr_point.position) + 0.5f * flow_width) < 0.0f ? -1.0f : 1.0f; + curr_point.distance *= sign; + + SupportPointCause potential_cause = SupportPointCause::FloatingExtrusion; + if (bridged_distance + line_len > params.bridge_distance * 0.8 && std::abs(curr_point.curvature) < 0.1) { + potential_cause = SupportPointCause::FloatingExtrusion; + } + + float max_bridge_len = std::max(params.support_points_interface_radius * 2.0f, + params.bridge_distance / + ((1.0f + std::abs(curr_point.curvature)) * (1.0f + std::abs(curr_point.curvature)) * + (1.0f + std::abs(curr_point.curvature)))); + + if (curr_point.distance > 1.2f * flow_width) { + line_out.form_quality = 0.8f; + bridged_distance += line_len; + if (bridged_distance > max_bridge_len) { + line_out.support_point_generated = potential_cause; + bridged_distance = 0.0f; + } + } else if (curr_point.distance > flow_width * 0.8f) { + bridged_distance += line_len; + line_out.form_quality = nearest_prev_layer_line.form_quality - 0.3f; + if (line_out.form_quality < 0 && bridged_distance > max_bridge_len) { + line_out.support_point_generated = potential_cause; + line_out.form_quality = 0.5f; + bridged_distance = 0.0f; + } + } else { + bridged_distance = 0.0f; + } + + line_out.curled_up_height = estimate_curled_up_height(curr_point, layer_region->layer()->height, flow_width, + nearest_prev_layer_line.curled_up_height, params); + + lines_out.push_back(line_out); + } + + return lines_out; + } +} + +SliceConnection estimate_slice_connection(size_t slice_idx, const Layer *layer) +{ + SliceConnection connection; + + const LayerSlice &slice = layer->lslices_ex[slice_idx]; + Polygons slice_polys = to_polygons(layer->lslices[slice_idx]); + BoundingBox slice_bb = get_extents(slice_polys); + const Layer *lower_layer = layer->lower_layer; + + ExPolygons below{}; + for (const auto &link : slice.overlaps_below) { below.push_back(lower_layer->lslices[link.slice_idx]); } + Polygons below_polys = to_polygons(below); + + BoundingBox below_bb = get_extents(below_polys); + + Polygons overlap = intersection(ClipperUtils::clip_clipper_polygons_with_subject_bbox(slice_polys, below_bb), + ClipperUtils::clip_clipper_polygons_with_subject_bbox(below_polys, slice_bb)); + + for (const Polygon &poly : overlap) { + Vec2f p0 = unscaled(poly.first_point()).cast(); + for (size_t i = 2; i < poly.points.size(); i++) { + Vec2f p1 = unscaled(poly.points[i - 1]).cast(); + Vec2f p2 = unscaled(poly.points[i]).cast(); + + float sign = cross2(p1 - p0, p2 - p1) > 0 ? 1.0f : -1.0f; + + auto [area, first_moment_of_area, second_moment_area, + second_moment_of_area_covariance] = compute_moments_of_area_of_triangle(p0, p1, p2); + connection.area += sign * area; + connection.centroid_accumulator += sign * Vec3f(first_moment_of_area.x(), first_moment_of_area.y(), layer->print_z * area); + connection.second_moment_of_area_accumulator += sign * second_moment_area; + connection.second_moment_of_area_covariance_accumulator += sign * second_moment_of_area_covariance; + } + } + + return connection; +}; + +class ObjectPart +{ +public: + float volume{}; + Vec3f volume_centroid_accumulator = Vec3f::Zero(); + float sticking_area{}; + Vec3f sticking_centroid_accumulator = Vec3f::Zero(); + Vec2f sticking_second_moment_of_area_accumulator = Vec2f::Zero(); + float sticking_second_moment_of_area_covariance_accumulator{}; + bool connected_to_bed = false; + + ObjectPart() = default; + + void add(const ObjectPart &other) + { + this->connected_to_bed = this->connected_to_bed || other.connected_to_bed; + this->volume_centroid_accumulator += other.volume_centroid_accumulator; + this->volume += other.volume; + this->sticking_area += other.sticking_area; + this->sticking_centroid_accumulator += other.sticking_centroid_accumulator; + this->sticking_second_moment_of_area_accumulator += other.sticking_second_moment_of_area_accumulator; + this->sticking_second_moment_of_area_covariance_accumulator += other.sticking_second_moment_of_area_covariance_accumulator; + } + + void add_support_point(const Vec3f &position, float sticking_area) + { + this->sticking_area += sticking_area; + this->sticking_centroid_accumulator += sticking_area * position; + this->sticking_second_moment_of_area_accumulator += sticking_area * position.head<2>().cwiseProduct(position.head<2>()); + this->sticking_second_moment_of_area_covariance_accumulator += sticking_area * position.x() * position.y(); + } + + float compute_directional_xy_variance(const Vec2f &line_dir, + const Vec3f ¢roid_accumulator, + const Vec2f &second_moment_of_area_accumulator, + const float &second_moment_of_area_covariance_accumulator, + const float &area) const + { + assert(area > 0); + Vec3f centroid = centroid_accumulator / area; + Vec2f variance = (second_moment_of_area_accumulator / area - centroid.head<2>().cwiseProduct(centroid.head<2>())); + float covariance = second_moment_of_area_covariance_accumulator / area - centroid.x() * centroid.y(); + // Var(aX+bY)=a^2*Var(X)+b^2*Var(Y)+2*a*b*Cov(X,Y) + float directional_xy_variance = line_dir.x() * line_dir.x() * variance.x() + line_dir.y() * line_dir.y() * variance.y() + + 2.0f * line_dir.x() * line_dir.y() * covariance; +#ifdef DETAILED_DEBUG_LOGS + BOOST_LOG_TRIVIAL(debug) << "centroid: " << centroid.x() << " " << centroid.y() << " " << centroid.z(); + BOOST_LOG_TRIVIAL(debug) << "variance: " << variance.x() << " " << variance.y(); + BOOST_LOG_TRIVIAL(debug) << "covariance: " << covariance; + BOOST_LOG_TRIVIAL(debug) << "directional_xy_variance: " << directional_xy_variance; +#endif + return directional_xy_variance; + } + + float compute_elastic_section_modulus(const Vec2f &line_dir, + const Vec3f &extreme_point, + const Vec3f ¢roid_accumulator, + const Vec2f &second_moment_of_area_accumulator, + const float &second_moment_of_area_covariance_accumulator, + const float &area) const + { + float directional_xy_variance = compute_directional_xy_variance(line_dir, centroid_accumulator, second_moment_of_area_accumulator, + second_moment_of_area_covariance_accumulator, area); + if (directional_xy_variance < EPSILON) { return 0.0f; } + Vec3f centroid = centroid_accumulator / area; + float extreme_fiber_dist = line_alg::distance_to(Linef(centroid.head<2>().cast(), + (centroid.head<2>() + Vec2f(line_dir.y(), -line_dir.x())).cast()), + extreme_point.head<2>().cast()); + float elastic_section_modulus = area * directional_xy_variance / extreme_fiber_dist; + +#ifdef DETAILED_DEBUG_LOGS + BOOST_LOG_TRIVIAL(debug) << "extreme_fiber_dist: " << extreme_fiber_dist; + BOOST_LOG_TRIVIAL(debug) << "elastic_section_modulus: " << elastic_section_modulus; +#endif + + return elastic_section_modulus; + } + + std::tuple is_stable_while_extruding(const SliceConnection &connection, + const ExtrusionLine &extruded_line, + const Vec3f &extreme_point, + float layer_z, + const Params ¶ms) const + { + Vec2f line_dir = (extruded_line.b - extruded_line.a).normalized(); + const Vec3f &mass_centroid = this->volume_centroid_accumulator / this->volume; + float mass = this->volume * params.filament_density; + float weight = mass * params.gravity_constant; + + float movement_force = params.max_acceleration * mass; + + float extruder_conflict_force = params.standard_extruder_conflict_force + + std::min(extruded_line.curled_up_height, 1.0f) * params.malformations_additive_conflict_extruder_force; + + // section for bed calculations + { + if (this->sticking_area < EPSILON) return {1.0f, SupportPointCause::UnstableFloatingPart}; + + Vec3f bed_centroid = this->sticking_centroid_accumulator / this->sticking_area; + float bed_yield_torque = -compute_elastic_section_modulus(line_dir, extreme_point, this->sticking_centroid_accumulator, + this->sticking_second_moment_of_area_accumulator, + this->sticking_second_moment_of_area_covariance_accumulator, + this->sticking_area) * + params.get_bed_adhesion_yield_strength(); + + Vec2f bed_weight_arm = (mass_centroid.head<2>() - bed_centroid.head<2>()); + float bed_weight_arm_len = bed_weight_arm.norm(); + float bed_weight_dir_xy_variance = compute_directional_xy_variance(bed_weight_arm, this->sticking_centroid_accumulator, + this->sticking_second_moment_of_area_accumulator, + this->sticking_second_moment_of_area_covariance_accumulator, + this->sticking_area); + float bed_weight_sign = bed_weight_arm_len < 2.0f * sqrt(bed_weight_dir_xy_variance) ? -1.0f : 1.0f; + float bed_weight_torque = bed_weight_sign * bed_weight_arm_len * weight; + + float bed_movement_arm = std::max(0.0f, mass_centroid.z() - bed_centroid.z()); + float bed_movement_torque = movement_force * bed_movement_arm; + + float bed_conflict_torque_arm = layer_z - bed_centroid.z(); + float bed_extruder_conflict_torque = extruder_conflict_force * bed_conflict_torque_arm; + + float bed_total_torque = bed_movement_torque + bed_extruder_conflict_torque + bed_weight_torque + bed_yield_torque; + +#ifdef DETAILED_DEBUG_LOGS + BOOST_LOG_TRIVIAL(debug) << "bed_centroid: " << bed_centroid.x() << " " << bed_centroid.y() << " " << bed_centroid.z(); + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_yield_torque: " << bed_yield_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_weight_arm: " << bed_weight_arm_len; + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_weight_torque: " << bed_weight_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_movement_arm: " << bed_movement_arm; + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_movement_torque: " << bed_movement_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_conflict_torque_arm: " << bed_conflict_torque_arm; + BOOST_LOG_TRIVIAL(debug) << "SSG: extruded_line.curled_up_height: " << extruded_line.curled_up_height; + BOOST_LOG_TRIVIAL(debug) << "SSG: extruded_line.form_quality: " << extruded_line.form_quality; + BOOST_LOG_TRIVIAL(debug) << "SSG: extruder_conflict_force: " << extruder_conflict_force; + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_extruder_conflict_torque: " << bed_extruder_conflict_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: total_torque: " << bed_total_torque << " layer_z: " << layer_z; +#endif + + if (bed_total_torque > 0) { + return {bed_total_torque / bed_conflict_torque_arm, + (this->connected_to_bed ? SupportPointCause::SeparationFromBed : SupportPointCause::UnstableFloatingPart)}; + } + } + + // section for weak connection calculations + { + if (connection.area < EPSILON) return {1.0f, SupportPointCause::UnstableFloatingPart}; + + Vec3f conn_centroid = connection.centroid_accumulator / connection.area; + + if (layer_z - conn_centroid.z() < 3.0f) { return {-1.0f, SupportPointCause::WeakObjectPart}; } + float conn_yield_torque = compute_elastic_section_modulus(line_dir, extreme_point, connection.centroid_accumulator, + connection.second_moment_of_area_accumulator, + connection.second_moment_of_area_covariance_accumulator, + connection.area) * + params.material_yield_strength; + + float conn_weight_arm = (conn_centroid.head<2>() - mass_centroid.head<2>()).norm(); + if (layer_z - conn_centroid.z() < 30.0) { + conn_weight_arm = 0.0f; // Given that we do not have very good info about the weight distribution between the connection and current layer, + // do not consider the weight until quite far away from the weak connection segment + } + float conn_weight_torque = conn_weight_arm * weight * (1.0f - conn_centroid.z() / layer_z) * (1.0f - conn_centroid.z() / layer_z); + + float conn_movement_arm = std::max(0.0f, mass_centroid.z() - conn_centroid.z()); + float conn_movement_torque = movement_force * conn_movement_arm; + + float conn_conflict_torque_arm = layer_z - conn_centroid.z(); + float conn_extruder_conflict_torque = extruder_conflict_force * conn_conflict_torque_arm; + + float conn_total_torque = conn_movement_torque + conn_extruder_conflict_torque + conn_weight_torque - conn_yield_torque; + +#ifdef DETAILED_DEBUG_LOGS + BOOST_LOG_TRIVIAL(debug) << "conn_centroid: " << conn_centroid.x() << " " << conn_centroid.y() << " " << conn_centroid.z(); + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_yield_torque: " << conn_yield_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_weight_arm: " << conn_weight_arm; + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_weight_torque: " << conn_weight_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_movement_arm: " << conn_movement_arm; + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_movement_torque: " << conn_movement_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_conflict_torque_arm: " << conn_conflict_torque_arm; + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_extruder_conflict_torque: " << conn_extruder_conflict_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: total_torque: " << conn_total_torque << " layer_z: " << layer_z; +#endif + + return {conn_total_torque / conn_conflict_torque_arm, SupportPointCause::WeakObjectPart}; + } + } +}; + +// return new object part and actual area covered by extrusions +std::tuple build_object_part_from_slice(const size_t &slice_idx, const Layer *layer, const Params& params) +{ + ObjectPart new_object_part; + float area_covered_by_extrusions = 0; + const LayerSlice& slice = layer->lslices_ex.at(slice_idx); + + auto add_extrusions_to_object = [&new_object_part, &area_covered_by_extrusions, ¶ms](const ExtrusionEntity *e, + const LayerRegion *region) { + float flow_width = get_flow_width(region, e->role()); + const Layer *l = region->layer(); + float slice_z = l->slice_z; + float height = l->height; + std::vector lines = to_short_lines(e, 5.0); + for (const ExtrusionLine &line : lines) { + float volume = line.len * height * flow_width * PI / 4.0f; + area_covered_by_extrusions += line.len * flow_width; + new_object_part.volume += volume; + new_object_part.volume_centroid_accumulator += to_3d(Vec2f((line.a + line.b) / 2.0f), slice_z) * volume; + + if (l->id() == params.raft_layers_count) { // layer attached on bed/raft + new_object_part.connected_to_bed = true; + float sticking_area = line.len * flow_width; + new_object_part.sticking_area += sticking_area; + Vec2f middle = Vec2f((line.a + line.b) / 2.0f); + new_object_part.sticking_centroid_accumulator += sticking_area * to_3d(middle, slice_z); + // Bottom infill lines can be quite long, and algined, so the middle approximaton used above does not work + Vec2f dir = (line.b - line.a).normalized(); + float segment_length = flow_width; // segments of size flow_width + for (float segment_middle_dist = std::min(line.len, segment_length * 0.5f); segment_middle_dist < line.len; + segment_middle_dist += segment_length) { + Vec2f segment_middle = line.a + segment_middle_dist * dir; + new_object_part.sticking_second_moment_of_area_accumulator += segment_length * flow_width * + segment_middle.cwiseProduct(segment_middle); + new_object_part.sticking_second_moment_of_area_covariance_accumulator += segment_length * flow_width * + segment_middle.x() * segment_middle.y(); + } + } + } + }; + + for (const auto &island : slice.islands) { + const LayerRegion *perimeter_region = layer->get_region(island.perimeters.region()); + for (const auto &perimeter_idx : island.perimeters) { + for (const ExtrusionEntity *perimeter : + static_cast(perimeter_region->perimeters().entities[perimeter_idx])->entities) { + add_extrusions_to_object(perimeter, perimeter_region); + } + } + for (const LayerExtrusionRange &fill_range : island.fills) { + const LayerRegion *fill_region = layer->get_region(fill_range.region()); + for (const auto &fill_idx : fill_range) { + for (const ExtrusionEntity *fill : + static_cast(fill_region->fills().entities[fill_idx])->entities) { + add_extrusions_to_object(fill, fill_region); + } + } + } + for (const auto &thin_fill_idx : island.thin_fills) { + add_extrusions_to_object(perimeter_region->thin_fills().entities[thin_fill_idx], perimeter_region); + } + } + + // BRIM HANDLING + if (layer->id() == params.raft_layers_count && params.raft_layers_count == 0 && params.brim_type != BrimType::btNoBrim && + params.brim_width > 0.0) { + // TODO: The algorithm here should take into account that multiple slices may have coliding Brim areas and the final brim area is + // smaller, + // thus has lower adhesion. For now this effect will be neglected. + ExPolygon slice_poly = layer->lslices[slice_idx]; + ExPolygons brim; + if (params.brim_type == BrimType::btOuterAndInner || params.brim_type == BrimType::btOuterOnly) { + Polygon brim_hole = slice_poly.contour; + brim_hole.reverse(); + Polygons c = expand(slice_poly.contour, scale_(params.brim_width)); // For very small polygons, the expand may result in empty vector, even thought the input is correct. + if (!c.empty()) { + brim.push_back(ExPolygon{c.front(), brim_hole}); + } + } + if (params.brim_type == BrimType::btOuterAndInner || params.brim_type == BrimType::btInnerOnly) { + Polygons brim_contours = slice_poly.holes; + polygons_reverse(brim_contours); + for (const Polygon &brim_contour : brim_contours) { + Polygons brim_holes = shrink({brim_contour}, scale_(params.brim_width)); + polygons_reverse(brim_holes); + ExPolygon inner_brim{brim_contour}; + inner_brim.holes = brim_holes; + brim.push_back(inner_brim); + } + } + + for (const Polygon &poly : to_polygons(brim)) { + Vec2f p0 = unscaled(poly.first_point()).cast(); + for (size_t i = 2; i < poly.points.size(); i++) { + Vec2f p1 = unscaled(poly.points[i - 1]).cast(); + Vec2f p2 = unscaled(poly.points[i]).cast(); + + float sign = cross2(p1 - p0, p2 - p1) > 0 ? 1.0f : -1.0f; + + auto [area, first_moment_of_area, second_moment_area, + second_moment_of_area_covariance] = compute_moments_of_area_of_triangle(p0, p1, p2); + new_object_part.sticking_area += sign * area; + new_object_part.sticking_centroid_accumulator += sign * Vec3f(first_moment_of_area.x(), first_moment_of_area.y(), + layer->print_z * area); + new_object_part.sticking_second_moment_of_area_accumulator += sign * second_moment_area; + new_object_part.sticking_second_moment_of_area_covariance_accumulator += sign * second_moment_of_area_covariance; + } + } + } + + return {new_object_part, area_covered_by_extrusions}; +} + +class ActiveObjectParts +{ + size_t next_part_idx = 0; + std::unordered_map active_object_parts; + std::unordered_map active_object_parts_id_mapping; + +public: + size_t get_flat_id(size_t id) + { + size_t index = active_object_parts_id_mapping.at(id); + while (index != active_object_parts_id_mapping.at(index)) { index = active_object_parts_id_mapping.at(index); } + size_t i = id; + while (index != active_object_parts_id_mapping.at(i)) { + size_t next = active_object_parts_id_mapping[i]; + active_object_parts_id_mapping[i] = index; + i = next; + } + return index; + } + + ObjectPart &access(size_t id) { return this->active_object_parts.at(this->get_flat_id(id)); } + + size_t insert(const ObjectPart &new_part) + { + this->active_object_parts.emplace(next_part_idx, new_part); + this->active_object_parts_id_mapping.emplace(next_part_idx, next_part_idx); + return next_part_idx++; + } + + void merge(size_t from, size_t to) + { + size_t to_flat = this->get_flat_id(to); + size_t from_flat = this->get_flat_id(from); + active_object_parts.at(to_flat).add(active_object_parts.at(from_flat)); + active_object_parts.erase(from_flat); + active_object_parts_id_mapping[from] = to_flat; + } +}; + +std::tuple check_stability(const PrintObject *po, const PrintTryCancel &cancel_func, const Params ¶ms) +{ + SupportPoints supp_points{}; + SupportGridFilter supports_presence_grid(po, params.min_distance_between_support_points); + ActiveObjectParts active_object_parts{}; + PartialObjects partial_objects{}; + LD prev_layer_ext_perim_lines; + + std::unordered_map prev_slice_idx_to_object_part_mapping; + std::unordered_map next_slice_idx_to_object_part_mapping; + std::unordered_map prev_slice_idx_to_weakest_connection; + std::unordered_map next_slice_idx_to_weakest_connection; + + auto remember_partial_object = [&active_object_parts, &partial_objects](size_t object_part_id) { + auto object_part = active_object_parts.access(object_part_id); + if (object_part.volume > EPSILON) { + partial_objects.emplace_back(object_part.volume_centroid_accumulator / object_part.volume, object_part.volume, + object_part.connected_to_bed); + } + }; + + for (size_t layer_idx = 0; layer_idx < po->layer_count(); ++layer_idx) { + cancel_func(); + const Layer *layer = po->get_layer(layer_idx); + float bottom_z = layer->bottom_z(); + auto create_support_point_position = [bottom_z](const Vec2f &layer_pos) { return Vec3f{layer_pos.x(), layer_pos.y(), bottom_z}; }; + + for (size_t slice_idx = 0; slice_idx < layer->lslices_ex.size(); ++slice_idx) { + const LayerSlice &slice = layer->lslices_ex.at(slice_idx); + auto [new_part, covered_area] = build_object_part_from_slice(slice_idx, layer, params); + SliceConnection connection_to_below = estimate_slice_connection(slice_idx, layer); + +#ifdef DETAILED_DEBUG_LOGS + std::cout << "SLICE IDX: " << slice_idx << std::endl; + for (const auto &link : slice.overlaps_below) { + std::cout << "connected to slice below: " << link.slice_idx << " by area : " << link.area << std::endl; + } + connection_to_below.print_info("CONNECTION TO BELOW"); +#endif + + if (connection_to_below.area < EPSILON) { // new object part emerging + size_t part_id = active_object_parts.insert(new_part); + next_slice_idx_to_object_part_mapping.emplace(slice_idx, part_id); + next_slice_idx_to_weakest_connection.emplace(slice_idx, connection_to_below); + } else { + size_t final_part_id{}; + SliceConnection transfered_weakest_connection{}; + // MERGE parts + { + std::unordered_set parts_ids; + for (const auto &link : slice.overlaps_below) { + size_t part_id = active_object_parts.get_flat_id(prev_slice_idx_to_object_part_mapping.at(link.slice_idx)); + parts_ids.insert(part_id); + transfered_weakest_connection.add(prev_slice_idx_to_weakest_connection.at(link.slice_idx)); + } + + final_part_id = *parts_ids.begin(); + for (size_t part_id : parts_ids) { + if (final_part_id != part_id) { + remember_partial_object(part_id); + active_object_parts.merge(part_id, final_part_id); + } + } + } + auto estimate_conn_strength = [bottom_z](const SliceConnection &conn) { + if (conn.area < EPSILON) { // connection is empty, does not exists. Return max strength so that it is not picked as the + // weakest connection. + return INFINITY; + } + Vec3f centroid = conn.centroid_accumulator / conn.area; + Vec2f variance = (conn.second_moment_of_area_accumulator / conn.area - + centroid.head<2>().cwiseProduct(centroid.head<2>())); + float xy_variance = variance.x() + variance.y(); + float arm_len_estimate = std::max(1.0f, bottom_z - (conn.centroid_accumulator.z() / conn.area)); + return conn.area * sqrt(xy_variance) / arm_len_estimate; + }; + +#ifdef DETAILED_DEBUG_LOGS + connection_to_below.print_info("new_weakest_connection"); + transfered_weakest_connection.print_info("transfered_weakest_connection"); +#endif + + if (estimate_conn_strength(transfered_weakest_connection) > estimate_conn_strength(connection_to_below)) { + transfered_weakest_connection = connection_to_below; + } + next_slice_idx_to_weakest_connection.emplace(slice_idx, transfered_weakest_connection); + next_slice_idx_to_object_part_mapping.emplace(slice_idx, final_part_id); + ObjectPart &part = active_object_parts.access(final_part_id); + part.add(new_part); + } + } + + prev_slice_idx_to_object_part_mapping = next_slice_idx_to_object_part_mapping; + next_slice_idx_to_object_part_mapping.clear(); + prev_slice_idx_to_weakest_connection = next_slice_idx_to_weakest_connection; + next_slice_idx_to_weakest_connection.clear(); + + std::vector current_layer_ext_perims_lines{}; + current_layer_ext_perims_lines.reserve(prev_layer_ext_perim_lines.get_lines().size()); + // All object parts updated, and for each slice we have coresponding weakest connection. + // We can now check each slice and its corresponding weakest connection and object part for stability. + for (size_t slice_idx = 0; slice_idx < layer->lslices_ex.size(); ++slice_idx) { + const LayerSlice &slice = layer->lslices_ex.at(slice_idx); + ObjectPart &part = active_object_parts.access(prev_slice_idx_to_object_part_mapping[slice_idx]); + SliceConnection &weakest_conn = prev_slice_idx_to_weakest_connection[slice_idx]; + + std::vector boundary_lines; + for (const auto &link : slice.overlaps_below) { + auto ls = to_unscaled_linesf({layer->lower_layer->lslices[link.slice_idx]}); + boundary_lines.insert(boundary_lines.end(), ls.begin(), ls.end()); + } + AABBTreeLines::LinesDistancer prev_layer_boundary{std::move(boundary_lines)}; + + + std::vector current_slice_ext_perims_lines{}; + current_slice_ext_perims_lines.reserve(prev_layer_ext_perim_lines.get_lines().size() / layer->lslices_ex.size()); +#ifdef DETAILED_DEBUG_LOGS + weakest_conn.print_info("weakest connection info: "); +#endif + // Function that is used when new support point is generated. It will update the ObjectPart stability, weakest conneciton info, + // and the support presence grid and add the point to the issues. + auto reckon_new_support_point = [&part, &weakest_conn, &supp_points, &supports_presence_grid, ¶ms, + &layer_idx](SupportPointCause cause, const Vec3f &support_point, float force, + const Vec2f &dir) { + // if position is taken and point is for global stability (force > 0) or we are too close to the bed, do not add + // This allows local support points (e.g. bridging) to be generated densely + if ((supports_presence_grid.position_taken(support_point) && force > 0) || layer_idx <= 1) { + return; + } + + float area = params.support_points_interface_radius * params.support_points_interface_radius * float(PI); + // add the stability effect of the point only if the spot is not taken, so that the densely created local support points do + // not add unrealistic amount of stability to the object (due to overlaping of local support points) + if (!(supports_presence_grid.position_taken(support_point))) { + part.add_support_point(support_point, area); + } + + float radius = params.support_points_interface_radius; + supp_points.emplace_back(cause, support_point, force, radius, dir); + supports_presence_grid.take_position(support_point); + + // The support point also increases the stability of the weakest connection of the object, which should be reflected + if (weakest_conn.area > EPSILON) { // Do not add it to the weakest connection if it is not valid - does not exist + weakest_conn.area += area; + weakest_conn.centroid_accumulator += support_point * area; + weakest_conn.second_moment_of_area_accumulator += area * support_point.head<2>().cwiseProduct(support_point.head<2>()); + weakest_conn.second_moment_of_area_covariance_accumulator += area * support_point.x() * support_point.y(); + } + }; + + // first we will check local extrusion stability of bridges, then of perimeters. Perimeters are more important, they + // account for most of the curling and possible crashes, so on them we will run also global stability check + for (const auto &island : slice.islands) { + // Support bridges where needed. + for (const LayerExtrusionRange &fill_range : island.fills) { + const LayerRegion *fill_region = layer->get_region(fill_range.region()); + for (const auto &fill_idx : fill_range) { + const ExtrusionEntity *entity = fill_region->fills().entities[fill_idx]; + if (entity->role() == ExtrusionRole::BridgeInfill) { + for (const ExtrusionLine &bridge : + check_extrusion_entity_stability(entity, fill_region, prev_layer_ext_perim_lines, prev_layer_boundary, + params)) { + if (bridge.support_point_generated.has_value()) { + reckon_new_support_point(*bridge.support_point_generated, create_support_point_position(bridge.b), + -EPSILON, Vec2f::Zero()); + } + } + } + } + } + + const LayerRegion *perimeter_region = layer->get_region(island.perimeters.region()); + for (const auto &perimeter_idx : island.perimeters) { + const ExtrusionEntity *entity = perimeter_region->perimeters().entities[perimeter_idx]; + std::vector perims = check_extrusion_entity_stability(entity, perimeter_region, + prev_layer_ext_perim_lines, prev_layer_boundary, + params); + for (const ExtrusionLine &perim : perims) { + if (perim.support_point_generated.has_value()) { + reckon_new_support_point(*perim.support_point_generated, create_support_point_position(perim.b), -EPSILON, + Vec2f::Zero()); + } + if (perim.is_external_perimeter()) { + current_slice_ext_perims_lines.push_back(perim); + } + } + } + // DEBUG EXPORT, NOT USED NOW + // if (BR_bridge) { + // Lines scaledl; + // for (const auto &l : prev_layer_boundary.get_lines()) { + // scaledl.emplace_back(Point::new_scale(l.a), Point::new_scale(l.b)); + // } + + // Lines perimsl; + // for (const auto &l : current_slice_ext_perims_lines) { + // perimsl.emplace_back(Point::new_scale(l.a), Point::new_scale(l.b)); + // } + + // BoundingBox bb = get_extents(scaledl); + // bb.merge(get_extents(perimsl)); + + // ::Slic3r::SVG svg(debug_out_path( + // ("slice" + std::to_string(slice_idx) + "_" + std::to_string(layer_idx).c_str()).c_str()), + // get_extents(scaledl)); + // svg.draw(scaledl, "red", scale_(0.4)); + // svg.draw(perimsl, "blue", scale_(0.25)); + + + // svg.Close(); + // } + } + + LD current_slice_lines_distancer(current_slice_ext_perims_lines); + float unchecked_dist = params.min_distance_between_support_points + 1.0f; + + for (const ExtrusionLine &line : current_slice_ext_perims_lines) { + if ((unchecked_dist + line.len < params.min_distance_between_support_points && line.curled_up_height < 0.3f) || + line.len < EPSILON) { + unchecked_dist += line.len; + } else { + unchecked_dist = line.len; + Vec2f pivot_site_search_point = Vec2f(line.b + (line.b - line.a).normalized() * 300.0f); + auto [dist, nidx, + nearest_point] = current_slice_lines_distancer.distance_from_lines_extra(pivot_site_search_point); + Vec3f support_point = create_support_point_position(nearest_point); + auto [force, cause] = part.is_stable_while_extruding(weakest_conn, line, support_point, bottom_z, params); + if (force > 0) { + reckon_new_support_point(cause, support_point, force, (line.b - line.a).normalized()); + } + } + } + current_layer_ext_perims_lines.insert(current_layer_ext_perims_lines.end(), current_slice_ext_perims_lines.begin(), + current_slice_ext_perims_lines.end()); + } // slice iterations + prev_layer_ext_perim_lines = LD(current_layer_ext_perims_lines); + } // layer iterations + + for (const auto& active_obj_pair : prev_slice_idx_to_object_part_mapping) { + remember_partial_object(active_obj_pair.second); + } + + return {supp_points, partial_objects}; +} + +#ifdef DEBUG_FILES +void debug_export(const SupportPoints& support_points,const PartialObjects& objects, std::string file_name) +{ + Slic3r::CNumericLocalesSetter locales_setter; + { + FILE *fp = boost::nowide::fopen(debug_out_path((file_name + "_supports.obj").c_str()).c_str(), "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) << "Debug files: Couldn't open " << file_name << " for writing"; + return; + } + + for (size_t i = 0; i < support_points.size(); ++i) { + Vec3f color{1.0f, 1.0f, 1.0f}; + switch (support_points[i].cause) { + case SupportPointCause::FloatingBridgeAnchor: color = {0.863281f, 0.109375f, 0.113281f}; break; //RED + case SupportPointCause::LongBridge: color = {0.960938f, 0.90625f, 0.0625f}; break; // YELLOW + case SupportPointCause::FloatingExtrusion: color = {0.921875f, 0.515625f, 0.101563f}; break; // ORANGE + case SupportPointCause::SeparationFromBed: color = {0.0f, 1.0f, 0.0}; break; // GREEN + case SupportPointCause::UnstableFloatingPart: color = {0.105469f, 0.699219f, 0.84375f}; break; // BLUE + case SupportPointCause::WeakObjectPart: color = {0.609375f, 0.210938f, 0.621094f}; break; // PURPLE + } + + fprintf(fp, "v %f %f %f %f %f %f\n", support_points[i].position(0), support_points[i].position(1), + support_points[i].position(2), color[0], color[1], color[2]); + } + + for (size_t i = 0; i < objects.size(); ++i) { + Vec3f color{1.0f, 0.0f, 1.0f}; + if (objects[i].connected_to_bed) { + color = {1.0f, 0.0f, 0.0f}; + } + fprintf(fp, "v %f %f %f %f %f %f\n", objects[i].centroid(0), objects[i].centroid(1), objects[i].centroid(2), color[0], + color[1], color[2]); + } + + fclose(fp); + } +} +#endif + +std::tuple full_search(const PrintObject *po, const PrintTryCancel& cancel_func, const Params ¶ms) +{ + auto results = check_stability(po, cancel_func, params); +#ifdef DEBUG_FILES + auto [supp_points, objects] = results; + debug_export(supp_points, objects, "issues"); +#endif + + return results; +} + +void estimate_supports_malformations(SupportLayerPtrs &layers, float flow_width, const Params ¶ms) +{ +#ifdef DEBUG_FILES + FILE *debug_file = boost::nowide::fopen(debug_out_path("supports_malformations.obj").c_str(), "w"); + FILE *full_file = boost::nowide::fopen(debug_out_path("supports_full.obj").c_str(), "w"); +#endif + + AABBTreeLines::LinesDistancer prev_layer_lines{}; + + for (SupportLayer *l : layers) { + std::vector current_layer_lines; + + for (const ExtrusionEntity *extrusion : l->support_fills.flatten().entities) { + Polyline pl = extrusion->as_polyline(); + Polygon pol(pl.points); + pol.make_counter_clockwise(); + + auto annotated_points = estimate_points_properties(pol.points, prev_layer_lines, flow_width); + + for (size_t i = 0; i < annotated_points.size(); ++i) { + ExtendedPoint &curr_point = annotated_points[i]; + float line_len = i > 0 ? ((annotated_points[i - 1].position - curr_point.position).norm()) : 0.0f; + ExtrusionLine line_out{i > 0 ? annotated_points[i - 1].position.cast() : curr_point.position.cast(), + curr_point.position.cast(), line_len, extrusion}; + + const ExtrusionLine nearest_prev_layer_line = prev_layer_lines.get_lines().size() > 0 ? + prev_layer_lines.get_line(curr_point.nearest_prev_layer_line) : + ExtrusionLine{}; + + Vec2f v1 = (nearest_prev_layer_line.b - nearest_prev_layer_line.a); + Vec2f v2 = (curr_point.position.cast() - nearest_prev_layer_line.a); + auto d = (v1.x() * v2.y()) - (v1.y() * v2.x()); + if (d > 0) { + curr_point.distance *= -1.0f; + } + + line_out.curled_up_height = estimate_curled_up_height(curr_point, l->height, flow_width, + nearest_prev_layer_line.curled_up_height, params); + + current_layer_lines.push_back(line_out); + } + } + + for (const ExtrusionLine &line : current_layer_lines) { + if (line.curled_up_height > 0.3f) { + l->malformed_lines.push_back(Line{Point::new_scale(line.a), Point::new_scale(line.b)}); + } + } + +#ifdef DEBUG_FILES + for (const ExtrusionLine &line : current_layer_lines) { + if (line.curled_up_height > 0.3f) { + Vec3f color = value_to_rgbf(-EPSILON, l->height * params.max_curled_height_factor, line.curled_up_height); + fprintf(debug_file, "v %f %f %f %f %f %f\n", line.b[0], line.b[1], l->print_z, color[0], color[1], color[2]); + } + } + for (const ExtrusionLine &line : current_layer_lines) { + Vec3f color = value_to_rgbf(-EPSILON, l->height * params.max_curled_height_factor, line.curled_up_height); + fprintf(full_file, "v %f %f %f %f %f %f\n", line.b[0], line.b[1], l->print_z, color[0], color[1], color[2]); + } +#endif + + prev_layer_lines = LD{current_layer_lines}; + } + +#ifdef DEBUG_FILES + fclose(debug_file); + fclose(full_file); +#endif +} + +void estimate_malformations(LayerPtrs &layers, const Params ¶ms) +{ +#ifdef DEBUG_FILES + FILE *debug_file = boost::nowide::fopen(debug_out_path("object_malformations.obj").c_str(), "w"); + FILE *full_file = boost::nowide::fopen(debug_out_path("object_full.obj").c_str(), "w"); +#endif + + LD prev_layer_lines{}; + + for (Layer *l : layers) { + std::vector boundary_lines = l->lower_layer != nullptr ? to_unscaled_linesf(l->lower_layer->lslices) : std::vector(); + AABBTreeLines::LinesDistancer prev_layer_boundary{std::move(boundary_lines)}; + std::vector current_layer_lines; + for (const LayerRegion *layer_region : l->regions()) { + for (const ExtrusionEntity *extrusion : layer_region->perimeters().flatten().entities) { + + if (!extrusion->role().is_external_perimeter()) continue; + + Points extrusion_pts; + extrusion->collect_points(extrusion_pts); + float flow_width = get_flow_width(layer_region, extrusion->role()); + auto annotated_points = estimate_points_properties(extrusion_pts, prev_layer_lines, flow_width, + params.bridge_distance); + for (size_t i = 0; i < annotated_points.size(); ++i) { + ExtendedPoint &curr_point = annotated_points[i]; + float line_len = i > 0 ? ((annotated_points[i - 1].position - curr_point.position).norm()) : 0.0f; + ExtrusionLine line_out{i > 0 ? annotated_points[i - 1].position.cast() : curr_point.position.cast(), + curr_point.position.cast(), line_len, extrusion}; + + const ExtrusionLine nearest_prev_layer_line = prev_layer_lines.get_lines().size() > 0 ? + prev_layer_lines.get_line(curr_point.nearest_prev_layer_line) : + ExtrusionLine{}; + + float sign = (prev_layer_boundary.distance_from_lines(curr_point.position) + 0.5f * flow_width) < 0.0f ? -1.0f : + 1.0f; + curr_point.distance *= sign; + + line_out.curled_up_height = estimate_curled_up_height(curr_point, layer_region->layer()->height, flow_width, + nearest_prev_layer_line.curled_up_height, params); + + current_layer_lines.push_back(line_out); + } + } + } + + for (const ExtrusionLine &line : current_layer_lines) { + if (line.curled_up_height > 0.3f) { + l->malformed_lines.push_back(Line{Point::new_scale(line.a), Point::new_scale(line.b)}); + } + } + +#ifdef DEBUG_FILES + for (const ExtrusionLine &line : current_layer_lines) { + if (line.curled_up_height > 0.3f) { + Vec3f color = value_to_rgbf(-EPSILON, l->height * params.max_curled_height_factor, line.curled_up_height); + fprintf(debug_file, "v %f %f %f %f %f %f\n", line.b[0], line.b[1], l->print_z, color[0], color[1], color[2]); + } + } + for (const ExtrusionLine &line : current_layer_lines) { + Vec3f color = value_to_rgbf(-EPSILON, l->height * params.max_curled_height_factor, line.curled_up_height); + fprintf(full_file, "v %f %f %f %f %f %f\n", line.b[0], line.b[1], l->print_z, color[0], color[1], color[2]); + } +#endif + + prev_layer_lines = LD{current_layer_lines}; + } + +#ifdef DEBUG_FILES + fclose(debug_file); + fclose(full_file); +#endif +} + +std::vector> gather_issues(const SupportPoints &support_points, PartialObjects &partial_objects) +{ + std::vector> result; + // The partial object are most likely sorted from smaller to larger as the print continues, so this should save some sorting time + std::reverse(partial_objects.begin(), partial_objects.end()); + std::sort(partial_objects.begin(), partial_objects.end(), + [](const PartialObject &left, const PartialObject &right) { return left.volume > right.volume; }); + + // Object may have zero extrusions and thus no partial objects. (e.g. very tiny object) + float max_volume_part = partial_objects.empty() ? 0.0f : partial_objects.front().volume; + for (const PartialObject &p : partial_objects) { + if (p.volume > max_volume_part / 200.0f && !p.connected_to_bed) { + result.emplace_back(SupportPointCause::UnstableFloatingPart, true); + break; + } + } + + // should be detected in previous step + // if (!unstable_floating_part_added) { + // for (const SupportPoint &sp : support_points) { + // if (sp.cause == SupportPointCause::UnstableFloatingPart) { + // result.emplace_back(SupportPointCause::UnstableFloatingPart, true); + // break; + // } + // } + // } + + std::vector ext_supp_points{}; + ext_supp_points.reserve(support_points.size()); + for (const SupportPoint &sp : support_points) { + switch (sp.cause) { + case SupportPointCause::FloatingBridgeAnchor: + case SupportPointCause::FloatingExtrusion: ext_supp_points.push_back(sp); break; + default: break; + } + } + + auto coord_fn = [&ext_supp_points](size_t idx, size_t dim) { return ext_supp_points[idx].position[dim]; }; + KDTreeIndirect<3, float, decltype(coord_fn)> ext_points_tree{coord_fn, ext_supp_points.size()}; + for (const SupportPoint &sp : ext_supp_points) { + auto cluster = find_nearby_points(ext_points_tree, sp.position, 3.0); + int score = 0; + bool floating_bridge = false; + for (size_t idx : cluster) { + score += ext_supp_points[idx].cause == SupportPointCause::FloatingBridgeAnchor ? 3 : 1; + floating_bridge = floating_bridge || ext_supp_points[idx].cause == SupportPointCause::FloatingBridgeAnchor; + } + if (score > 5) { + if (floating_bridge) { + result.emplace_back(SupportPointCause::FloatingBridgeAnchor, true); + } else { + result.emplace_back(SupportPointCause::FloatingExtrusion, true); + } + break; + } + } + + for (const SupportPoint &sp : support_points) { + if (sp.cause == SupportPointCause::SeparationFromBed) { + result.emplace_back(SupportPointCause::SeparationFromBed, true); + break; + } + } + + for (const SupportPoint &sp : support_points) { + if (sp.cause == SupportPointCause::WeakObjectPart) { + result.emplace_back(SupportPointCause::WeakObjectPart, true); + break; + } + } + + if (ext_supp_points.size() > max_volume_part / 200.0f) { + result.emplace_back(SupportPointCause::FloatingExtrusion, false); + } + + for (const SupportPoint &sp : support_points) { + if (sp.cause == SupportPointCause::LongBridge) { + result.emplace_back(SupportPointCause::LongBridge, false); + break; + } + } + + return result; +} + +} // namespace SupportSpotsGenerator +} // namespace Slic3r +*/ \ No newline at end of file diff --git a/src/libslic3r/SupportSpotsGenerator.hpp b/src/libslic3r/SupportSpotsGenerator.hpp new file mode 100644 index 0000000000..c3fde4d6ae --- /dev/null +++ b/src/libslic3r/SupportSpotsGenerator.hpp @@ -0,0 +1,159 @@ +#ifndef SRC_LIBSLIC3R_SUPPORTABLEISSUESSEARCH_HPP_ +#define SRC_LIBSLIC3R_SUPPORTABLEISSUESSEARCH_HPP_ +/* +#include "Layer.hpp" +#include "Line.hpp" +#include "PrintBase.hpp" +#include "PrintConfig.hpp" +#include +#include +#include + +namespace Slic3r { + +namespace SupportSpotsGenerator { + +struct Params +{ + Params( + const std::vector &filament_types, float max_acceleration, int raft_layers_count, BrimType brim_type, float brim_width) + : max_acceleration(max_acceleration), raft_layers_count(raft_layers_count), brim_type(brim_type), brim_width(brim_width) + { + if (filament_types.size() > 1) { + BOOST_LOG_TRIVIAL(warning) + << "SupportSpotsGenerator does not currently handle different materials properly, only first will be used"; + } + if (filament_types.empty() || filament_types[0].empty()) { + BOOST_LOG_TRIVIAL(error) << "SupportSpotsGenerator error: empty filament_type"; + filament_type = std::string("PLA"); + } else { + filament_type = filament_types[0]; + BOOST_LOG_TRIVIAL(debug) << "SupportSpotsGenerator: applying filament type: " << filament_type; + } + } + + // the algorithm should use the following units for all computations: distance [mm], mass [g], time [s], force [g*mm/s^2] + const float bridge_distance = 16.0f; // mm + const float max_acceleration; // mm/s^2 ; max acceleration of object in XY -- should be applicable only to printers with bed slinger, + // however we do not have such info yet. The force is usually small anyway, so not such a big deal to include it everytime + const int raft_layers_count; + std::string filament_type; + + BrimType brim_type; + const float brim_width; + + const std::pair malformation_distance_factors = std::pair { 0.5, 1.1 }; + const float max_curled_height_factor = 10.0f; + + const float min_distance_between_support_points = 3.0f; //mm + const float support_points_interface_radius = 1.5f; // mm + const float min_distance_to_allow_local_supports = 1.0f; //mm + + const float gravity_constant = 9806.65f; // mm/s^2; gravity acceleration on Earth's surface, algorithm assumes that printer is in upwards position. + const double filament_density = 1.25e-3f; // g/mm^3 ; Common filaments are very lightweight, so precise number is not that important + const double material_yield_strength = 33.0f * 1e6f; // (g*mm/s^2)/mm^2; 33 MPa is yield strength of ABS, which has the lowest yield strength from common materials. + const float standard_extruder_conflict_force = 10.0f * gravity_constant; // force that can occasionally push the model due to various factors (filament leaks, small curling, ... ); + const float malformations_additive_conflict_extruder_force = 65.0f * gravity_constant; // for areas with possible high layered curled filaments + + // MPa * 1e^6 = (g*mm/s^2)/mm^2 = g/(mm*s^2); yield strength of the bed surface + double get_bed_adhesion_yield_strength() const { + if (raft_layers_count > 0) { + return get_support_spots_adhesion_strength() * 2.0; + } + + if (filament_type == "PLA") { + return 0.02 * 1e6; + } else if (filament_type == "PET" || filament_type == "PETG") { + return 0.3 * 1e6; + } else if (filament_type == "ABS" || filament_type == "ASA") { + return 0.1 * 1e6; //TODO do measurements + } else { //PLA default value - defensive approach, PLA has quite low adhesion + return 0.02 * 1e6; + } + } + + double get_support_spots_adhesion_strength() const { + return 0.016f * 1e6; + } +}; + +enum class SupportPointCause { + LongBridge, // point generated on bridge and straight perimeter extrusion longer than the allowed length + FloatingBridgeAnchor, // point generated on unsupported bridge endpoint + FloatingExtrusion, // point generated on extrusion that does not hold on its own + SeparationFromBed, // point generated for object parts that are connected to the bed, but the area is too small and there is a risk of separation (brim may help) + UnstableFloatingPart, // point generated for object parts not connected to the bed, holded only by the other support points (brim will not help here) + WeakObjectPart // point generated when some part of the object is too weak to hold the upper part and may break (imagine hourglass) + }; + +// The support points can be sorted into two groups +// 1. Local extrusion support for extrusions that are printed in the air and would not +// withstand on their own (too long bridges, sharp turns in large overhang, concave bridge holes, etc.) +// These points have negative force (-EPSILON) and Vec2f::Zero() direction +// The algorithm still expects that these points will be supported and accounts for them in the global stability check. +// 2. Global stability support points are generated at each spot, where the algorithm detects that extruding the current line +// may cause separation of the object part from the bed and/or its support spots or crack in the weak connection of the object parts. +// The generated point's direction is the estimated falling direction of the object part, and the force is equal to te difference +// between forces that destabilize the object (extruder conflicts with curled filament, weight if instable center of mass, bed movements etc) +// and forces that stabilize the object (bed adhesion, other support spots adhesion, weight if stable center of mass). +// Note that the force is only the difference - the amount needed to stabilize the object again. +struct SupportPoint +{ + SupportPoint(SupportPointCause cause, const Vec3f &position, float force, float spot_radius, const Vec2f &direction) + : cause(cause), position(position), force(force), spot_radius(spot_radius), direction(direction) + {} + + bool is_local_extrusion_support() const + { + return cause == SupportPointCause::LongBridge || cause == SupportPointCause::FloatingExtrusion; + } + bool is_global_object_support() const { return !is_local_extrusion_support(); } + + SupportPointCause cause; // reason why this support point was generated. Used for the user alerts + // position is in unscaled coords. The z coordinate is aligned with the layers bottom_z coordiantes + Vec3f position; + // force that destabilizes the object to the point of falling/breaking. g*mm/s^2 units + // It is valid only for global_object_support. For local extrusion support points, the force is -EPSILON + // values gathered from large XL model: Min : 0 | Max : 18713800 | Average : 1361186 | Median : 329103 + // For reference 18713800 is weight of 1.8 Kg object, 329103 is weight of 0.03 Kg + // The final sliced object weight was approx 0.5 Kg + float force; + // Expected spot size. The support point strength is calculated from the area defined by this value. + // Currently equal to the support_points_interface_radius parameter above + float spot_radius; + // direction of the fall of the object (z part is neglected) + Vec2f direction; +}; + +using SupportPoints = std::vector; + +struct Malformations { + std::vector layers; //for each layer +}; + +struct PartialObject +{ + PartialObject(Vec3f centroid, float volume, bool connected_to_bed) + : centroid(centroid), volume(volume), connected_to_bed(connected_to_bed) + {} + + Vec3f centroid; + float volume; + bool connected_to_bed; +}; + +using PartialObjects = std::vector; + +std::tuple full_search(const PrintObject *po, const PrintTryCancel& cancel_func, const Params ¶ms); + +void estimate_supports_malformations(std::vector &layers, float supports_flow_width, const Params ¶ms); +void estimate_malformations(std::vector &layers, const Params ¶ms); + + +// NOTE: the boolean marks if the issue is critical or not for now. +std::vector> gather_issues(const SupportPoints &support_points, + PartialObjects &partial_objects); + +}} // namespace Slic3r::SupportSpotsGenerator +*/ +#endif /* SRC_LIBSLIC3R_SUPPORTABLEISSUESSEARCH_HPP_ */ diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index b50fc97c9a..de4503b632 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -323,31 +323,30 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con //BBS if (config->opt_enum("wall_generator") == PerimeterGeneratorType::Arachne && - config->opt_bool("enable_overhang_speed")) + config->opt_bool("overhang_speed_classic")) { - wxString msg_text = _(L("Arachne engine only works when overhang slowing down is disabled.\n" - "This may cause decline in the quality of overhang surface when print fastly")) + "\n"; + wxString msg_text = _(L("Arachne engine doesn't work with classic overhang speed mode.\n")) + "\n"; if (is_global_config) - msg_text += "\n" + _(L("Disable overhang slowing down automatically? \n" - "Yes - Enable arachne and disable overhang slowing down\n" + msg_text += "\n" + _(L("Turn off classic mode automatically? \n" + "Yes - Enable arachne with classic mode off\n" "No - Give up using arachne this time")); MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK)); DynamicPrintConfig new_conf = *config; is_msg_dlg_already_exist = true; auto answer = dialog.ShowModal(); - bool enable_overhang_slow_down = true; + bool enable_overhang_slow_down_legacy = false; if (!is_global_config || answer == wxID_YES) { - new_conf.set_key_value("enable_overhang_speed", new ConfigOptionBool(false)); - enable_overhang_slow_down = false; + new_conf.set_key_value("overhang_speed_classic", new ConfigOptionBool(false)); + enable_overhang_slow_down_legacy = true; } else { new_conf.set_key_value("wall_generator", new ConfigOptionEnum(PerimeterGeneratorType::Classic)); } apply(config, &new_conf); if (cb_value_change) { - if (!enable_overhang_slow_down) - cb_value_change("enable_overhang_speed", false); + if (!enable_overhang_slow_down_legacy) + cb_value_change("overhang_speed_classic", false); } is_msg_dlg_already_exist = false; } @@ -639,7 +638,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co toggle_line("max_travel_detour_distance", have_avoid_crossing_perimeters); bool has_overhang_speed = config->opt_bool("enable_overhang_speed"); - for (auto el : { "overhang_1_4_speed", "overhang_2_4_speed", "overhang_3_4_speed", "overhang_4_4_speed"}) + for (auto el : + {"overhang_speed_classic", "overhang_1_4_speed", + "overhang_2_4_speed", "overhang_3_4_speed", "overhang_4_4_speed"}) toggle_line(el, has_overhang_speed); toggle_line("flush_into_objects", !is_global_config); @@ -657,7 +658,6 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co "min_feature_size", "min_bead_width", "wall_distribution_count" }) toggle_line(el, have_arachne); toggle_field("detect_thin_wall", !have_arachne); - toggle_field("enable_overhang_speed", !have_arachne); toggle_field("only_one_wall_top", !have_arachne); // SoftFever diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 5aaefa7076..a1c50773cf 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -98,7 +98,7 @@ std::map> SettingsFactory::PART_CAT {"infill_combination", "",1}, {"infill_wall_overlap", "",1}, {"infill_direction", "",1}, {"bridge_angle", "",1}, {"minimum_sparse_infill_area", "",1} }}, { L("Speed"), {{"outer_wall_speed", "",1},{"inner_wall_speed", "",2},{"sparse_infill_speed", "",3},{"top_surface_speed", "",4}, {"internal_solid_infill_speed", "",5}, - {"enable_overhang_speed", "",6}, {"overhang_1_4_speed", "",7}, {"overhang_2_4_speed", "",8}, {"overhang_3_4_speed", "",9}, {"overhang_4_4_speed", "",10}, + {"enable_overhang_speed", "",6}, {"overhang_speed_classic", "",6}, {"overhang_1_4_speed", "",7}, {"overhang_2_4_speed", "",8}, {"overhang_3_4_speed", "",9}, {"overhang_4_4_speed", "",10}, {"bridge_speed", "",11}, {"gap_infill_speed", "",12} }} }; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 77fb97b5e9..4a5350cd81 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -2515,7 +2515,7 @@ void MainFrame::init_menubar_as_editor() }, "", nullptr, [this]() {return m_plater->is_view3D_shown();; }, this); - m_topbar->GetCalibMenu()->AppendSubMenu(advance_menu, _L("More")); + m_topbar->GetCalibMenu()->AppendSubMenu(advance_menu, _L("More...")); // help append_menu_item(m_topbar->GetCalibMenu(), wxID_ANY, _L("Tutorial"), _L("Calibration help"), @@ -2581,7 +2581,7 @@ void MainFrame::init_menubar_as_editor() m_vfa_test_dlg->ShowModal(); }, "", nullptr, [this]() {return m_plater->is_view3D_shown();; }, this); - append_submenu(calib_menu, advance_menu, wxID_ANY, _L("More"), _L("More calibrations"), "", + append_submenu(calib_menu, advance_menu, wxID_ANY, _L("More..."), _L("More calibrations"), "", [this]() {return m_plater->is_view3D_shown();; }); // help append_menu_item(calib_menu, wxID_ANY, _L("Tutorial"), _L("Calibration help"), diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 361529e354..d6df9062d0 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1919,7 +1919,12 @@ void TabPrint::build() optgroup->append_single_option_line("sparse_infill_speed"); optgroup->append_single_option_line("internal_solid_infill_speed"); optgroup->append_single_option_line("top_surface_speed"); + optgroup->append_single_option_line("gap_infill_speed"); + optgroup->append_single_option_line("support_speed"); + optgroup->append_single_option_line("support_interface_speed"); + optgroup = page->new_optgroup(L("Overhang speed"), L"param_speed", 15); optgroup->append_single_option_line("enable_overhang_speed", "slow-down-for-overhang"); + optgroup->append_single_option_line("overhang_speed_classic", "slow-down-for-overhang"); Line line = { L("Overhang speed"), L("This is the speed for various overhang degrees. Overhang degrees are expressed as a percentage of line width. 0 speed means no slowing down for the overhang degree range and wall speed is used") }; line.label_path = "slow-down-for-overhang"; line.append_option(optgroup->get_option("overhang_1_4_speed")); @@ -1928,9 +1933,6 @@ void TabPrint::build() line.append_option(optgroup->get_option("overhang_4_4_speed")); optgroup->append_line(line); optgroup->append_single_option_line("bridge_speed"); - optgroup->append_single_option_line("gap_infill_speed"); - optgroup->append_single_option_line("support_speed"); - optgroup->append_single_option_line("support_interface_speed"); optgroup = page->new_optgroup(L("Travel speed"), L"param_travel_speed", 15); optgroup->append_single_option_line("travel_speed"); @@ -1945,7 +1947,7 @@ void TabPrint::build() optgroup->append_single_option_line("accel_to_decel_enable"); optgroup->append_single_option_line("accel_to_decel_factor"); - optgroup = page->new_optgroup(L("Jerk(XY)")); + optgroup = page->new_optgroup(L("Jerk(XY)"), L"param_speed", 15); optgroup->append_single_option_line("default_jerk"); optgroup->append_single_option_line("outer_wall_jerk"); optgroup->append_single_option_line("inner_wall_jerk"); diff --git a/src/slic3r/GUI/calib_dlg.cpp b/src/slic3r/GUI/calib_dlg.cpp index bd8707bf99..03b065d267 100644 --- a/src/slic3r/GUI/calib_dlg.cpp +++ b/src/slic3r/GUI/calib_dlg.cpp @@ -238,7 +238,7 @@ Temp_Calibration_Dlg::Temp_Calibration_Dlg(wxWindow* parent, wxWindowID id, Plat // end temp auto end_temp_sizer = new wxBoxSizer(wxHORIZONTAL); auto end_temp_text = new wxStaticText(this, wxID_ANY, end_temp_str, wxDefaultPosition, st_size, wxALIGN_LEFT); - m_tiEnd = new TextInput(this, std::to_string(200), _L("\u2103"), "", wxDefaultPosition, ti_size, wxTE_CENTRE); + m_tiEnd = new TextInput(this, std::to_string(190), _L("\u2103"), "", wxDefaultPosition, ti_size, wxTE_CENTRE); m_tiStart->GetTextCtrl()->SetValidator(wxTextValidator(wxFILTER_NUMERIC)); end_temp_sizer->Add(end_temp_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2); end_temp_sizer->Add(m_tiEnd, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2); @@ -338,11 +338,11 @@ void Temp_Calibration_Dlg::on_filament_type_changed(wxCommandEvent& event) { switch(selection) { case tABS_ASA: - start = 260; + start = 270; end = 230; break; case tPETG: - start = 250; + start = 260; end = 230; break; case tTPU: @@ -360,7 +360,7 @@ void Temp_Calibration_Dlg::on_filament_type_changed(wxCommandEvent& event) { case tPLA: case tCustom: start = 230; - end = 200; + end = 190; break; }